第11章 オブジェクトを改良する: Decorator
Ruby
Published: 2019-06-27

「Rubyによるデザインパターン」の読書メモです。

Decoratorパターンについて

既存のオブジェクトに対して簡単に機能を追加するためのパターンです。Decoratorパターンを使うとレイヤ状に機能を積み重ねていくことができ、それぞれの状況でまさに必要なだけの機能をもつオブジェクトを作ることができます。

今回、ファイルに下記の形式で書き出すことができるクラスを作ります。

  • プレーンテキスト
  • チェックサム付き
  • タイムスタンプ付き
  • 行番号付き

継承ベースのアプローチ

継承ベースのアプローチでは、あり得るすべての機能の組み合わせを前もって設計時に考えなければならない,ということです。

クラスの数が増えすぎるので、不採用とします。

Decoratorパターンで実装した例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

class WriterDecorator
  def initialize(real_writer)
    @real_writer = real_writer
  end

  def write_line(line)
    @real_writer.write_line(line)
  end

  def pos
    @real_writer.pos
  end

  def rewind
    @real_writer.rewind
  end

  def close
    @real_writer.close
  end
end

class NumberingWriter < WriterDecorator
  def initialize(real_writer)
    super(real_writer)
    @line_number = 1
  end

  def write_line(line)
    @real_writer.write_line("#{@line_number}: #{line}")
    @line_number += 1
  end
end

class CheckSummingWriter < WriterDecorator
  attr_reader :check_sum

  def initialize(real_writer)
    @real_writer = real_writer
    @check_sum = 0
  end

  def write_line(line)
    line.each_byte {|byte| @check_sum = (@check_sum + byte) % 256}
     # check_sum の計算...
    @real_writer.write_line("#{line} (checksum: #{@check_sum})")
  end
end

class TimeStampingWriter < WriterDecorator
  def write_line(line)
    @real_writer.write_line("#{Time.new}: #{line}")
  end
end

writer = CheckSummingWriter.new(
  TimeStampingWriter.new(
    NumberingWriter.new(SimpleWriter.new('final.txt'))
  )
)
writer.write_line('Hello out there')

出力された final.txt

1: 2019-06-23 13:28:36 +0900: Hello out there (checksum: 164)

Forwardable を使った例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

出力された final2.txt

2019-06-23 13:34:25 +0900: Hello out there

ラッピングする例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

w = SimpleWriter.new('final3.txt')

class << w
  alias old_write_line write_line

  def write_line(line)
    old_write_line("#{Time.new}: #{line}")
  end
end

w.write_line('write_line')
w.old_write_line('old_write_line')

出力された final3.txt

2019-06-23 13:42:53 +0900: write_line
old_write_line

モジュールを使った例

class SimpleWriter
  def initialize(path)
    @file = File.open(path, 'w')
  end

  def write_line(line)
    @file.print(line)
    @file.print("\n")
  end

  def pos
    @file.pos
  end

  def rewind
    @file.rewind
  end

  def close
    @file.close
  end
end

module TimeStampingWriter
  def write_line(line)
    super("#{Time.new}: #{line}")
  end
end

w = SimpleWriter.new('final4.txt')
w.extend(TimeStampingWriter)
w.write_line('hello')

出力された final4.txt

2019-06-23 13:45:02 +0900: hello