はじめに
この記事は Ruby Advent Calendar 2019 の11日目の記事です。
現在、エンジニアになって9ヶ月が経ったのですが、最近は、オブジェクト指向やデザインパターンのような基礎的な部分を理解しようと努めており、ここ1~2ヶ月で下記5冊の本を読みました。
- オブジェクト指向実践ガイド
- オブジェクト指向でなぜつくるのか 第2版
- Rubyによるデザインパターン
- Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
- Java言語で学ぶデザインパターン入門
これらの本を読むことで、オブジェクト指向というものの輪郭がぼんやりと掴めてきた気がするのと、改めてRubyという言語の特徴を知るキッカケになったと感じております。
本記事では、書籍「Java言語で学ぶデザインパターン入門」の1章(Iterator)のJavaで書かれたコードサンプルをRubyで書くとどうなるかを自分なりに咀嚼しながら実践してみたこと、および、学んだことについてアウトプットしていきます。
※ 以下で書くことは、個人的には「そういうことか!」と点と点が繋がる感覚を覚えた出来事だったのですが、Rubyをやっている方にとっては当たり前の話かもしれません。温かい目でご覧いただければ幸いです。
サンプルコード
GitHubはこちらです。
https://github.com/MasashiFukuzawa/DesignPatternLearnedWithRuby
今回のサンプルの概要
本棚に本を入れ、その本の名前を順番に表示するサンプルをRubyを使って、Iteratorパターンで記述していきます。
JavaサンプルをRubyで直訳してみる
まずはJavaのサンプルコードを直訳したものを示します。
この実装では、Aggregate
クラスとIterator
クラスという基底クラスを明示しています。
こうすることで、例えば、新たにCDラックににCDを入れ、そのCDの名前を順番に表示したくなった場合も、Aggregate
クラスとIterator
クラスを継承し、本棚とCDラックが同じインターフェースを共有していることが一目で理解できるようになります。
# interface
class Aggregate
def iterator
end
end
# interface
class Iterator
def has_next?
end
def next
end
end
# interfaceの実装
class Bookshelf < Aggregate
def initialize(maxsize)
@books = Array.new(maxsize)
@last = 0
end
def append_book(book)
@books << book
@last += 1
end
def getBookAt(index)
@books[index]
end
def getLength
@last
end
def iterator
BookshelfIterator.new(self)
end
end
# interfaceの実装
class BookshelfIterator < Iterator
def initialize(bookshelf)
@bookshelf = bookshelf
@index = 0
end
def has_next?
@index < @bookshelf.getLength
end
def next
book = @bookshelf.getBookAt(@index)
@index += 1
book
end
end
# 動作テスト
bookshelf = Bookshelf.new(4)
bookshelf.append_book('1st book')
bookshelf.append_book('2nd book')
bookshelf.append_book('3rd book')
bookshelf.append_book('4th book')
it = bookshelf.iterator
while it.has_next? do
book = it.next
puts book
end
# 実行結果
> 1st book
> 2nd book
> 3rd book
> 4th book
しかしながら、上記のコードでは、Aggregate
クラスとIterator
クラスは何もせず、ただインターフェースを規定するためだけに存在しているため、Ruby的な手法ではないようです。
そこで、次にRubyらしく書いたコードを見ていきます。
Rubyらしく書いてみる
修正点としては、上記のサンプルからインターフェースクラスおよび継承部分を削除しただけです。
この場合、基底クラスを明示していないため、先程挙げた例のように、新たにCDラック用の実装が追加された時などに、本棚でもCDラックでも同じような動きはするものの、それぞれが同じインターフェースを共有していることが分からなくなるのでは?とどうしても最初は心配になってしまいました。
しかしながら、ずっと考えているうちに、そのような心配をせずとも、いずれもiterator
メソッドを通して同じ挙動を示す(アヒルのように歩き、アヒルのように鳴く)ことで、同じインターフェースを共有していることを見抜けるってことか!ということがスッと腹落ちしたのでした。
そして、これこそが今までずっと上手く咀嚼できずにいた「ダックタイピング」という言葉の意味であり、Rubyという言語の特徴なのだと理解した瞬間に、個人的には「そういうことか!」と点と点が繋がったのでした。
class Bookshelf
def initialize(maxsize)
@books = Array.new(maxsize)
@last = 0
end
def append_book(book)
@books << book
@last += 1
end
def getBookAt(index)
@books[index]
end
def getLength
@last
end
def iterator
BookshelfIterator.new(self)
end
end
class BookshelfIterator
def initialize(bookshelf)
@bookshelf = bookshelf
@index = 0
end
def has_next?
@index < @bookshelf.getLength
end
def next
book = @bookshelf.getBookAt(@index)
@index += 1
book
end
end
# 動作テスト
bookshelf = Bookshelf.new(4)
bookshelf.append_book('1st book')
bookshelf.append_book('2nd book')
bookshelf.append_book('3rd book')
bookshelf.append_book('4th book')
it = bookshelf.iterator
while it.has_next? do
book = it.next
puts book
end
# 実行結果
> 1st book
> 2nd book
> 3rd book
> 4th book
オブジェクト指向・デザインパターンを勉強して理解したこと
- Rubyの場合は、わざわざ何もしない基底クラスを作って押し込めるようなことはせずとも大丈夫
- 例えば、
Bookshelf
クラスと同様の形でCDRack
クラスも実装した場合、基底クラスが明示されていなくても、いずれもiterator
メソッドを通して同じ挙動を示す(アヒルのように歩き、アヒルのように鳴く)ことで、同じインターフェースを共有していることを見抜ける - そしてこれこそがよく耳にする「ダックタイピング」である
まとめ
- Java言語で学ぶデザインパターン入門をRubyで書くとどうなるかについて試してみました。
- ここ1~2ヶ月間、オブジェクト指向およびデザインパターンを重点的に勉強した結果、遅ればせながら、Rubyやダックタイピングへの理解が深まり、点と点が繋がる感覚を覚えることができました。
- やっとオブジェクト指向の入口に立てたかなという感じなので、今後も引き続き、咀嚼&アウトプットしていきたいと思います。