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 tecxt.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を作ることができました。
今回はここで終了です。引き続き、デザインパターンをここでアウトプットしていきたいと思います。