LoginSignup
0
0

More than 3 years have passed since last update.

Rubyによるデザインパターン(1)

Posted at

Rubyによるデザインパターン(1)

 本記事は初級エンジニアがRubyを使用したデザインパターンをアウトプットしたものになります。また、デザインパターンは種類が多いため、何回かに分けて掲載していきたいと思います。今回はIteratorパターンとAdapterパターンをご紹介します。なお、こちら記事は次のサイトを参考にしております。
TECHSCORE
酒と涙とRubyとRailsと

Iteratorパターン

 Iteratorパターンは要素(オブジェクト)の集まりを保有するオブジェクトの各要素(オブジェクト)に順番にアクセスする方法を提供するデザインパターンです。各オブジェクトの名前、または各オブジェクトのパラメータを利用するなど、各オブジェクトへのアクセス方法は異なります。つまり走査方法(アクセス方法)を与えるクラスを別に用意することで、より柔軟な設計をする事ができるようになります。
 イテレータには内部イテレータと外部イテレータがありますが、内部イテレータはRubyのeachメソッドのようなもの(コードブロックベースのイテレータ)であるため、説明を割愛します。

外部イテレータ

 それでは次のモデルで外部イテレータを説明します。

  • Book class(集約オブジェクト):複数のChapterクラスをもつオブジェクト
  • Chapter class(集約オブジェクト中の要素):Bookクラスの個別の要素
  • BookIterator class(外部イテレータ): Bookの要素Chapterにアクセスするためクラス

章を表すChapterクラスは以下のとおり

# 章を表す(集約オブジェクトが保有する要素)
class Chapter
  attr_reader :title

  # 各章のタイトル
  def initialize(title)
    @title = title
  end
end

続いて、本を表すBookクラス(章を複数保有)は以下のとおり

# 本を表す(集約オブジェクト)
class Book
  def initialize
    @chapters = []
  end

  # 指定の要素(index)を返す
  def get_chapter_at(index)
    @chapters[index]
  end

  # 要素(Chapter)を追加する
  def add_chapter(chapter)
    @chapters << chapter
  end

  # 要素(Chapter)数を返す
  def length
    @chapters.length
  end

  # イテレータを生成する
  def iterator
    BookIterator.new(self)
  end
end

最後に外部イテレータのBookIteratorクラスは以下のとおり

# 外部イテレータ
class BookIterator
  def initialize(book)
    @book = book
    @index = 0
  end

  # 次のindexの要素の有無をbooleanで返す
  def has_next?
    @index < @book.length
  end

  # indexを1増加させ、次のChapterクラスを返す
  def next_chapter
    chapter = self.has_next? ? @book.get_chapter_at(@index) : nil
    @index = @index + 1
    chapter
  end
end

以上のコードを実際に操作すると次のようになります。

book = Book.new
book.add_Chapter(Chapter.new("第1章"))
book.add_Chapter(Chapter.new("第2章"))
book.add_Chapter(Chapter.new("第3章"))
book.add_Chapter(Chapter.new("第4章"))

iterator = book.iterator
while iterator.has_next?
  chapter = iterator.next_chapter
  puts chapter.title
end
# 第1章
# 第2章
# 第3章
# 第4章

 book.iteratorで生成した外部イテレータにより、Bookクラスの要素(Chapter)に順番にアクセスしている事が確認できます。続いて、Adapterパターンを説明します。

Adapterパターン

 Adapterパターンは、インタフェースに互換性の無いクラス同士を組み合わせることを目的としたパターンです。これまで使用していたメソッドと同じ機能を、より優れた方法で提供するメソッドを持つクラスがあったとします。この優れたメソッドは、これまで使用していたメソッドとは異なるインタフェースであるため、乗り換えるためには大きな変更が必要となることがあります。そこで、この2つのメソッドのインタフェースの差異を埋める Adapter を提供することで、優れたメソッドを少しの変更で利用できるようにします。

アダプタの構成要素は次の4つです。

  • 利用者(Client):ターゲットのメソッドを呼び出します
  • ターゲット(Target):インターフェースを規定します
  • アダプタ(Adapter):アダプティのインタフェースを変換し、ターゲット向けのインタフェースを提供します
  • アダプティ(Adaptee):実際に動作する既定クラスです

 ClientはPrinterクラスのメソッドを使うことはできますが、メソッド名前/定義が異なるためOldPrinterクラスを使うことはできません。そこでAdapterパターンを適用することでClientがOldPrinterクラスを使えるようにします。

まずはPrinterクラスを確認

# 利用者(Client)へのインタフェース (Target)
class Printer
  def initialize(obj)
    @obj = obj
  end

  def print_weak
    @obj.print_weak
  end

  def print_strong
    @obj.print_strong
  end
end

次にOldPrinterクラスを確認

# Targetにはないインタフェースを持つ (Adaptee)
class OldPrinter
  def initialize(string)
    @string = string.dup
  end

  # カッコに囲って文字列を表示する
  def show_with_paren
    puts "(#{@string})"
  end

  # アスタリスクで囲って文字列を表示する
  def show_with_aster
    puts "*#{@string}*"
  end
end

 このOldPrinterクラスのメソッドをClientが使えるPrinterクラスのインタフェースにするAdapterクラスを作成します。このクラスには、次の2つのメソッドを定義します。
 #print_weak:OldPrinter#show_with_parenを呼び出します
 #print_strong:OldPrinter#show_with_asterを呼び出します

# Targetが利用できるインタフェースに変換 (Adapter)
class Adapter
  def initialize(string)
    @old_printer = OldPrinter.new(string)
  end

  def print_weak
    @old_printer.show_with_paren
  end

  def print_strong
    @old_printer.show_with_aster
  end
end

 以上がソースコードになります。それでは、結果を確認するために、実際にClientにTargetを動かしてもらいます。

# 利用者(Client)
p = Printer.new(Adapter.new("Hello"))
p.print_weak
#=> (Hello)

p.print_strong
#=> *Hello*

 Adapterを利用する事でOldPrinterクラスのメソッドに適合させる事ができました。
 以上で、Adapterパターンの説明は一旦終了となりますが、ここでRubyの特異メソッドを使用してAdapterを作ってみたいと思います。(特異メソッドはオブジェクト固有のメソッド)

# Targetにはないインターフェイスを持つ (Adaptee)
class OldPrinter
  def initialize(string)
    @string = string.dup
  end

  def show_with_bracket
    puts "[#{@string}]"
  end

  def show_with_asterisk
    puts "**#{@string}**"
  end
end

# 利用者(Client)へのインタフェース (Target)
class Printer
  def initialize(obj)
    @obj = obj
  end

  def print_weak
    @obj.print_weak
  end

  def print_strong
    @obj.print_strong
  end
end

# textオブジェクト(OldPrinter)にAdapterの役割を持つ特異メソッドを追加
text = OldPrinter.new("Hello")
def text.print_weak
  show_with_bracket
end
def text.print_strong
  show_with_asterisk
end

# 利用者(Client)
p = Printer.new(text)

p.print_weak
#=> [Hello]

p.print_strong
#=> **Hello**

 textオブジェクト固有のメソッドを作ることでAdapterを作ることができました。
 今回はここで終了です。引き続き、デザインパターンをここでアウトプットしていきたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0