Adapter
Rubyによるデザインパターン生活。若干飽きてきたけど続けます。
本日はAdapterパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Adapterパターンは、インターフェースの不一致を埋めるためのパターンです。
ここでいうインターフェースの不一致というのは、
メソッドのネーミング、シグネチャの不一致のことを意味しています。
例えば、次のようなプログラムがあるとします。
画面に文字列を出力するだけのシンプルなものです。
#今回は本に載ってるプログラムをもろパクリではなく、ちょっとアレンジしています。
class Renderer def execute(text_object) text_object.print end end class TextObject def print puts "TextObject print!" end end
このプログラムは、Rendererクラスのexecuteメソッドの中で、
引数に受け取ったオブジェクトのprintメソッドを呼び出します。
引数のオブジェクトには、TextObjectを渡すことができます。
renderer = Renderer.new text_object = TextObject.new renderer.execute(text_object)
実行結果は以下のとおりです。
TextObject print!
次に、Rendererクラスのexecuteメソッドの引数に与えるオブジェクトを、
さきほどのTextObjectからLegacyTextObjectに変更します。
LegacyTextObjectクラスは以下のとおりです。
class LegacyTextObject def show_message puts "LegacyTextObject show_message!" end end
ここで問題が発生します。
LegacyTextObjectクラスにはprintメソッドが定義されていません。
その代わりに類似したメソッドであるshow_messageが定義されています。
このままの状態で、さきほどと同じようにプログラムを実行するとエラーが発生してしまいます。
renderer = Renderer.new lto = LegacyTextObject.new renderer.execute(lto)
実行結果(NoMethodError)
NoMethodError: private method ‘print’ called for #<LegacyTextObject:0x1ce6c>
エラーの原因はLegacyTextObjectクラスにprintメソッドが定義されていないためです。
一番簡単な解決法はLegacyTextObjectクラスのshow_messageメソッドを、
printメソッドに名前を変更することですが、
LegacyTextObjectクラスが既に別のプログラムから利用されている場合は、変更箇所が増えてしまいます。
上記のような問題の解決策となるのがAdapterパターンです。
Adapterパターンでは、インタフェースの不一致を吸収するためのAdapterクラスを作成します。
class LegacyTextObjectAdapter def initialize(legacty_text_object) @lto = legacty_text_object end def print @lto.show_message end end
実行時には、LegacyTextObjectAdapterを使ってLegacyTextObjectをラップします。
renderer = Renderer.new lto = LegacyTextObject.new renderer.execute(LegacyTextObjectAdapter.new(lto))
すると上手く動作します。(実行結果です)
LegacyTextObject show_message!
Adapterパターンのクラス図は、以下のようになります。
各クラスの役割は以下のとおりです。
- Client
- Targetのメソッドを呼び出すクラス。
- Target
- 実行対象となるクラス(インタフェース)。
- Adapter
- Targetインタフェースと、Adapteeクラスの差分を埋めるクラス。
- Adaptee
- 実際に動作するクラス。
先ほどのプログラムの場合、以下のような構成になります。
- Client
- Rendererクラス。
- Target
- Adapter
- LegacyTextObjectAdapterクラス。
- Adaptee
- LegacyTextObjectクラス。
以上がAdapterパターンの説明です。
なんですが、この本の面白いところはここからです。
Rubyは動的型付け言語です。
Javaのようにコンパイル時に変数の型をチェックするような仕組みはありません。
そのため、Adapterパターンにおいても、
Ruby特有の、動的型付け言語特有の解決策が存在します。
例えば、実行時にクラスを修正することで、メソッドを追加することができます。
LegacyTextObjectがロード済みの場合、以下のようにクラス自身を再定義することができます。
class LegacyTextObject def print show_message end end
これにより実行時にprintメソッドが追加されます。
printメソッドの内部では、show_messageメソッドを呼んでいるので、
結果的にshow_messageメソッドが呼び出されることになります。
上記の方法はクラス定義自体に変更が発生します。
LegacyTextObjectクラスのすべてのオブジェクトはprintメソッドを保持することになります。
クラスに直接メソッドを追加するのではなく、
特定のオブジェクトにだけメソッドを追加することもできます。
lto = LegacyTextObject.new class << lto def print show_message end end
この方法ではlto変数に格納されているオブジェクトのみがprintメソッドを保持することになります。
Rubyではオブジェクトに固有なメソッドのことを特異メソッド(Singletonメソッド)と呼びます。
特異メソッドは、以下のように実装することもできます。
lto = LegacyTextObject.new def lto.print show_message end
以上で説明はおしまいです。
Adapterパターンは理解しやすいパターンだと思います。
ただ、僕の場合はこれまでJavaの考え方(静的型付け)で理解していました。
そのため、Rubyのような動的型付けの世界ではいろんな代替手段があるんだなー、と、
改めて感じました。
プログラミングってほんとうにおもしろいですね、みたいな。
*1:ダックタイピングというやつ
Command
Rubyによるデザインパターン生活。継続中。
本日はCommandパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
CommandパターンのCommand(コマンド)とは命令の意味です。
この本には、
コマンドはある特定の何かをするための命令です。
と定義されています。
あ、今回からクラス図をアップすることにしました。
それではCommandパターンのクラス図です。
Commandパターンはとてもシンプルな構成になっています。
上記のクラス図では、抽象的なCommandクラス(インタフェース)を実装する、
ConcreteCommand1、ConcreteCommand2クラスを示しています。
Commandインタフェースにはexecuteメソッドが定義されています。
このexecuteメソッドが「ある特定の何かをするための命令」になります。
今回のサンプルに登場するクラスです。
- Command
- Commandクラス。名前そのままでCommandパターンのCommandを担当します。
- CreateFile
- Commandのサブクラス。ファイルを作成するクラス。
- DeleteFile
- Commandのサブクラス。ファイルを削除するクラス。
- CopyFile
- Commandのサブクラス。ファイルをコピーするクラス。
※CreateFile、DeleteFile、CopyFileクラスは、ConcreteCommand1(2)クラスのように振る舞います。
- CompositCommand
- 複数のコマンドを集約するクラス。自身もCommandのサブクラスであり、保持する複数のコマンドをまとめて実行します。
つづいて、ソースコードです。
require "fileutils" class Command attr_reader :description def initialize(description) @description = description end def execute end end class CreateFile < Command def initialize(path, contents) super("Create file : #{path}") @path = path @contents = contents end def execute f = File.open(@path, "w") f.write(@contents) f.close end end class DeleteFile < Command def initialize(path) super("Delete file : #{path}") @path = path end def execute File.delete(@path) end end class CopyFile < Command def initialize(source, target) super("Copy file : #{source} to #{target}") @source = source @target = target end def execute FileUtils.copy(@source, @target) end end class CompositCommand < Command def initialize @commands = [] end def add_command(cmd) @commands << cmd end def execute @commands.each { |cmd| cmd.execute } end def description description = "" @commands.each { |cmd| description += cmd.description + "\n"} description end end cmds = CompositCommand.new cmds.add_command(CreateFile.new("file1.txt", "hello world\n")) cmds.add_command(CopyFile.new("file1.txt", "file2.txt")) cmds.add_command(DeleteFile.new("file1.txt")) cmds.execute
Commandクラスとそのサブクラスを定義したあと、CompositCommandオブジェクトを生成し、
CreateFile、CopyFile、DeleteFileオブジェクトを追加しています。
その後、追加したCommandオブジェクトを追加した順にまとめて実行しています。
実行結果は、カレントディレクトリに作成したfile1.txtがコピーされてfile2.txtとなります。
また、コピー元となったfile1.txtは削除されます。
結果としてfile2.txtのみが残ります。
また、さきほどのプログラムの最後で、
puts cmds.description
と実行すると、画面に集約したコマンドの一覧を出力します。
Create file : file1.txt Copy file : file1.txt to file2.txt Delete file : file1.txt
ここまでの流れをみると、
一つひとつのCommandの処理はexecuteメソッドを実装するだけで単純なものです。
「executeメソッドを実装した」から「Commandパターンだ」と思っていてはいけません。
本に載っている大事な部分を引用します。
Commandパターンのポイントは、何を行うかの決定と、それの実行を分離することです。
このパターンを使う場合、「これを行え」と命令する代わりに、「これを行う方法を記録しろ」と命令し、その後「記録したことを行え」と命令します。
Commandパターンは命令と実行の分離と考えると良いと思います。
また、Commandパターンの応用編として、
Undo(やりなおし)の実装も紹介されています。
さきほどのプログラムの各Commandクラスにunexecuteメソッドを追加してみます。
require "fileutils" class Command attr_reader :description def initialize(description) @description = description end def execute end end class CreateFile < Command def initialize(path, contents) super("Create file : #{path}") @path = path @contents = contents end def execute f = File.open(@path, "w") f.write(@contents) f.close end def unexecute File.delete(@path) end end class DeleteFile < Command def initialize(path) super("Delete file : #{path}") @path = path end def execute if(File.exists?(@path)) @contents = File.read(@path) end File.delete(@path) end def unexecute f = File.open(@path, "w") f.write(@contents) f.close end end class CopyFile < Command def initialize(source, target) super("Copy file : #{source} to #{target}") @source = source @target = target end def execute if(File.exists?(@target)) @contents = File.read(@target) end FileUtils.copy(@source, @target) end def unexecute File.delete(@target) if(@contents) f = File.open(@target, "w") f.write(@contents) f.close end end end class CompositCommand < Command def initialize @commands = [] end def add_command(cmd) @commands << cmd end def execute @commands.each { |cmd| cmd.execute } end def unexecute @commands.reverse.each { |cmd| cmd.unexecute } end def description description = "" @commands.each { |cmd| description += cmd.description + "\n"} description end end cmds = CompositCommand.new cmds.add_command(CreateFile.new("file1.txt", "hello world10\n")) cmds.add_command(CopyFile.new("file1.txt", "file2.txt")) cmds.add_command(DeleteFile.new("file1.txt")) cmds.execute cmds.unexecute
最後にcmds.unexecuteメソッドを呼び出すことで、
コピー処理の一連の流れを取り消すことができます。
東京観光
週末は東京をぶらぶらしてました。
が、今日はあいにくの大雨でした。
なので、雨にぬれないようにと、とりあえず恵比寿ガーデンプレイスに行ってみました。
写真が暗いのはiPhoneでトイカメラのエフェクトをかけたから。
ここまで暗くはなかったです。
恵比寿に行ったものの、、
意外と何もすることがないので、、iPhoneで写真を撮ってごまかす。
iPhoneで写真を撮ってごまかす2。
東京駅に移動して新丸ビルでうろうろ。7階のカフェでお茶。
新丸ビルの7階んとこは、外に出れるので天気が良かったら楽しそう。
お酒も外で飲めるっぽいし。
東京生活ももうすぐ終わりです。
せっかくなので来週もiPhone片手にどっか行ってみようと思います。
Iterator
Rubyによるデザインパターン生活、何日目とかもういいや。
本日はIteratorパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Iteratorパターンは、
「集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する」とGoFは定義しています。
Javaでよく出てくるIteratorインタフェースがまさにそれです。
Iteratorインタフェースを利用することでListオブジェクトなど内部を意識せず、順番に要素へアクセスすることができます。
RubyにおけるIteratorパターンでは、外部イテレータと内部イテレータが存在します。
外部イテレータってのは、JavaのIteratorインタフェースのようなクラスを自分で作ってしまう方法で、
内部イテレータってのは、Rubyでよく見るeach{|element| #do something}のようなメソッドを意味します。
*1
で、まずは外部イテレータを試してみます。
配列オブジェクト用のイテレータ(ArrayIterator)クラス。
class ArrayIterator def initialize(array) @array = array @index = 0 end def has_next? @index < @array.length end def item @array[@index] end def next_item value = @array[@index] @index += 1 value end end
使い方はこんなかんじ。
array = ["red", "green", "blue"] it = ArrayIterator.new(array) while it.has_next? puts "item: #{it.next_item}" end
そんで実行結果。
item: red item: green item: blue
ちなみに名前はArrayIteratorだけど、配列オブジェクトにしか使えないわけではなくて、文字列オブジェクトの操作にも使えます。
it = ArrayIterator.new("test") while it.has_next? puts "item:#{it.next_item.chr}" end
文字列操作の場合は文字コードが返されるので、chrメソッドで文字データに変換しています。
実行結果は以下のとおり。
item:t item:e item:s item:t
続いて、内部イテレータの話です。
Arrayオブジェクトに定義されているeachメソッドのようなものを独自で実装すると以下のようになります。
def for_each_element(array) i = 0 while i < array.length yield(array[i]) i += 1 end end
引数で受け取ったArrayオブジェクトに対して、yieldメソッドでコードブロックを順に実行するかんじです。
呼び出し側のプログラムは以下のようになります。
a = [10, 20, 30] for_each_element(a){ |element| puts element }
実行結果です。
10 20 30
外部イテレータより、内部イテレータの方がRubyっぽいというか便利なように思います。
ただし、2つのArrayオブジェクトをマージするような場合は、
外部イテレータを使う方が楽なようです。
def merge(array1, array2) merged = [] it1 = ArrayIterator.new(array1) it2 = ArrayIterator.new(array2) while it1.has_next? && it2.has_next? if it1.item < it2.item merged << it1.next_item else merged << it2.next_item end end while it1.has_next? merged << it1.next_item end while it2.has_next? merged << it2.next_item end merged end a = [10, 20, 30] b = [11, 12, 21, 22] merge(a, b).each { |e| puts e }
マージの実行結果です。
10 11 12 20 21 22 30
Iteratorパターンのお話はこんなところなんだけど、
この章では、Enumerableモジュールの使い方を紹介しています。
Enumerableモジュールをインクルードすると、集約オブジェクトに対して便利なメソッドを取り込むことができます。
たとえば、all?とかany?とかinclude?とか。
Enumerableモジュールをインクルードするには、以下の点を考慮します。
class Account attr_accessor :name, :balance def initialize(name, balance) @name = name @balance = balance end def <=>(other) @balance <=> other.balance end end class Portfolio include Enumerable def initialize @accounts = [] end def each(&block) @accounts.each(&block) end def add_account(account) @accounts << account end end account1 = Account.new("account1", 1000) account2 = Account.new("account2", 5000) account3 = Account.new("account3", 3000) account4 = Account.new("account4", 4000) portfolio = Portfolio.new portfolio.add_account(account1) portfolio.add_account(account2) portfolio.add_account(account3) portfolio.add_account(account4) puts portfolio.any? { |account| account.balance > 2000 } puts portfolio.all? { |account| account.balance > 2000 }
実行すると、trueが表示されます。
true false
あと、この章の最後のところで、ObjectSpaceモジュールも紹介されています。
ObjectSpaceは、Rubyインタープリタの中に存在する完全なオブジェクト空間へのアクセスを提供します。
初めて知ったけど、これけっこう面白い。どこで使えばよいのかはわからないけど。
さっきのプログラムのつづきに以下のコードを追加すると、
ObjectSpace.each_object(Account){ |object| puts object.name }
こんなふうにメモリ上に存在するインスタンスを取得することができます。
account4 account3 account2 account1
以上。
*1:こういう話は言われてみたら、そうだなーって思うけど、言われなかったら意識しないままだと思う。
Composite
Rubyによるデザインパターン生活、、4日目。
#しばらく飲み会続きでお休みしていました。
、、継続します。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
本日はCompositeパターンを取り上げます。
僕の中では、Compositeというと「集約」というイメージがあります。オブジェクト思考とかで。
Rubyによるデザインパターン本では「部分から全体を組み立てる」と紹介しています。
そんじゃ見ていきます。
本の中では、ケーキを作るというプロセスをツリー構造に分解しています。
詳細化したタスクをCompositeパターンで処理しよう!ってかんじ。
- MakeCake(ケーキを作る)
- Make Batter
- AddDryIngredients
- AddLiquids
- Mix
- Package Cake(ケーキを箱詰めにする)
- 省略
- Make Batter
みたいなかんじで、ケーキを作るための作業を詳細化しています。
Compositeパターンでは3つの部品を使います。
- すべてのオブジェクトの基底となるクラス(Component)
- 葉(Leaf)となるクラス
- 小要素をもつ複合的なクラス(Composite)
今回登場するクラス。
- Task
- 基底となるクラス(Component)
- AddDryIngredientsTask
- 単体の作業を表すクラス(Leaf)
- AddLiquidsTask
- 単体の作業を表すクラス(Leaf)
- MixTask
- 単体の作業を表すクラス(Leaf)
- MakeBatterTask
- 複数の作業によって構成されるクラス(Composite)
それでは、コードを見てみます。
まずは、Taskクラスです。
class Task attr_accessor :name def initialize(name) @name = name; end def get_time_required 0.0 end end
属性nameとget_time_requiredメソッドを定義しています。
get_time_requiredメソッドは、作業に必要な時間を返します。
Taskクラスは抽象的なクラスのため、作業時間は0を返しています。
#結果としてサブクラスでオーバーライドすることになります。
つづいて、AddDryIngredientsTaskです。
class AddDryIngredientsTask < Task def initialize super('Add dry ingredients') end def get_time_required 1.0 end end
Taskクラスを継承しています。get_time_requiredメソッドをオーバーライドしています。
AddDryIngredientsTaskクラスはCompositeパターンにおけるLeafにあたるクラスです。
そのため小要素は持たず、自身の作業だけを遂行します。
次に紹介するMixTask、AddLiquidsTaskクラスも同様のLeafにあたるクラスです。
class MixTask < Task def initialize super('Mix that batter up!') end def get_time_required 3.0 end end
class AddLiquidsTask < Task def initialize super('Add Liquids task') end def get_time_required 5.0 end end
つづいて、AddDryIngredientsTask、MixTask、AddLiquidsTaskによって構成されるMakeBatterTaskクラスです。
class MakeBatterTask < Task def initialize super('Make batter') @sub_tasks = [] add_sub_task(AddDryIngredientsTask.new) add_sub_task(AddLiquidsTask.new) add_sub_task(MixTask.new) end def add_sub_task(task) @sub_tasks << task end def remove_sub_task(task) @sub_tasks.delete(task) end def get_time_required time = 0.0 @sub_tasks.each {|task| time += task.get_time_required} time end end
MakeBatterTaskクラスはCompositeパターンにおけるCompositeにあたるクラスです。
自身は小要素となるAddDryIngredientsTask、MixTask、AddLiquidsTaskオブジェクトを保持します。
作業時間を求めるget_time_requiredメソッドでは、各小要素に対して作業時間を問い合わせ、合計した結果を返します。
最後に呼び出しもととなるプログラムです。
task = MakeBatterTask.new
puts task.name
puts task.get_time_required
実行結果です。
Make batter 9.0
と、こんなかんじです。
あとは、
MakeBatterTaskクラスからCompositeにあたる部分をCompositeTaskクラスとして切り出すこともできます。
class CompositeTask < Task def initialize(name) super(name) @sub_tasks = [] end def add_sub_task(task) @sub_tasks << task task.parent_task = self end def remove_sub_task(task) @sub_tasks.delete(task) end def get_time_required time = 0.0 @sub_tasks.each {|task| time += task.get_time_required} time end end
するとMakeBatterTaskはシンプルになります。
class MakeBatterTask < CompositeTask def initialize super('Make batter') add_sub_task(AddDryIngredientsTask.new) add_sub_task(MixTask.new) add_sub_task(AddLiquidsTask.new) end end
また、CompositeTaskには配列を操作するような演算子メソッドを追加すると便利です。
<<メソッドや、[]メソッドなど。
class CompositeTask < Task def initialize(name) super(name) @sub_tasks = [] end def add_sub_task(task) @sub_tasks << task task.parent_task = self end def <<(task) add_sub_task(task) end def remove_sub_task(task) @sub_tasks.delete(task) end def [](index) @sub_tasks[index] end def []=(index, new_value) @sub_tasks[index] = new_value end def total_number_basic_tasks total = 0 @sub_tasks.each { |task| total += task.total_number_basic_tasks } total end def get_time_required time = 0.0 @sub_tasks.each {|task| time += task.get_time_required} time end end
以上、おしまい。
TextMateの使い方を学ぶ。Hash編、Block編
最近、Rubyで遊んでるんですが、開発環境にTextMateを使っています。
TextMateのBundle(スニペット?)は鬼のように強力なのですが、全然使いこなせていません。。
Googleで調べてもTextMateの日本語での情報はそれほど多くないので、
英語のドキュメント読んで勉強しようかなー、と思ってたら、Youtubeでわかりやすい動画を見つけました。
http://www.youtube.com/user/dneighbo
Derek氏ナイス。Good job!
そんなDerekさんの動画をまとめました。
Hash編
- Insert Hash Pointer( => の挿入)
- control + L
- Insert Hash Pair(Hashペアの挿入)
- : -> tab*1
- Create New Hash(Hashの作成)
- Hash -> tab
Block編
- Block Insert do(doブロックの挿入)
- do -> tab
- Block Insert {({ブロックの挿入)
- { -> tab
- Block Toggle { and do(do、{ブロックのトグル)
- control + {
個人的にはBlock Toggle { and doとかちょっとビビった。
他にも何本か動画がアップされているようなので、またのちほどまとめる。予定。
*1:->は続けて入力の意味です。
Observer
Rubyによるデザインパターン生活3日目。in 市ヶ谷。
今日はObserverパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Observerってのは「観測者」っていう意味が一番しっくりくるかな。
あるオブジェクトの状態変化を観測し、
変化が発生したことを知らせるのが目的です。
著者のラス・オルセンさんも言ってるけど、
Observerとなるクラスが観測者にあたるんだけど、
実際に観測者に変化を通知するのは、観測対象となるオブジェクト(Observable)なります。
#その辺でObserverパターンのネーミングは若干微妙な気もします。
そんな話はさておき、今回登場するクラスは以下のとおりです。
- Employee
- 従業員クラス。名前、役職、給料といった状態をもつ。状態が変化したら観測者(Observer)へ通知する。
- Payroll
- 給与明細クラス。従業員の給料の変化などを観測するObserverクラス。
- TaxMan
- 税金クラス。従業員の給料の変化などを観測するObserverクラス。
つづいてソースコードです。
まず、Employeeクラス。
class Employee attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) @name = name @title = title @salary = salary @observers = [] end def salary=(new_salary) @salary = new_salary notify_observers end def notify_observers @observers.each do |observer| observer.update(self) end end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end end
インスタンス変数@observersがポイント。
ここに観測者(Observer)となるオブジェクトを保持します。
@observersへの追加にはadd_observerメソッドを使います。
状態であるsalaryが変化した場合、notify_observersメソッドが呼び出されるので、
結果として観測者クラス(Observer)のupdateメソッドが呼び出されます。
つづいて、観測者(Observer)となるPayrollクラスです。
class Payroll def update(changed_employee) puts "#{changed_employee.name}のために小切手を切ります。" puts "給料は#{changed_employee.salary}です。" end end
変更通知用のupdateメソッドを持っています。
引数には観測対象であるEmployeeオブジェクトを受け取ります。
つづいてTaxManクラス。こちらもObserverクラスになります。
class TaxMan def update(changed_employee) puts "#{changed_employee.name}に新しい税金の請求書を送ります。" end end
Payrollクラスと同じくupdateメソッドを実装しています。
実行用のプログラムです。
fred = Employee.new("Fred", "Crane Operator", 30000.0) payroll = Payroll.new fred.add_observer(payroll) taxman = TaxMan.new fred.add_observer(taxman) fred.salary = 35000.0
そんで、実行結果です。
Fredのために小切手を切ります。 給料は35000.0です。 Fredに新しい税金の請求書を送ります。
以上が一般的なObserverパターンの流れです。
Payroll、TaxMan以外の観測者(Observer)クラスが追加された場合も、同様に処理できます。
ここまでのコードでまずいところは、
Employeeクラスの中に観測対象者(Observable)として状態や振る舞いが実装されてしまっているところ。
Employeeクラスが読みにくいものになっています。
なんで、コードを分離してあげます。
観測対象者(Observable)に該当するコードを抽出します。
今回はSubject(観測対象)という意味あいのクラスとして抽出します。
が、Subjectクラスをスーパークラスとして抽出してしまうと、
継承関係の制約(単一継承しかできない)を受けるためよろしくありません。
なんで、Rubyに用意されているモジュール機能を使います。
Rubyは複数のモジュールをインクルード可能です。
モジュールはJavaのインタフェースに近いような?気がします。
#実装つきのインタフェースってかんじ?
Subjectモジュール。
module Subject def initialize @observers = [] end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.update(self) end end end
Employeeクラス。Subjectモジュールをインクルードしています。
class Employee include Subject attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) super() @name = name; @title = title @salary = salary end def salary=(new_saraly) @salary = new_saraly notify_observers end end
以上がObserverパターンのRubyっぽいモジュールを使った方法になります。
ちなみにRubyのライブラリの中にもObservableモジュールが用意されています。
話が少し変わりますが、
RubyでObserverパターンを実現する場合、
コードブロックをObserverクラスに見立てて処理することもできます。
Subjectクラスをこんなかんじに変更します。
module Subject def initialize @observers = [] end def add_observer(&observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.call(self) end end end
add_observer(&observer)メソッドの引数がコードブロックの参照になっているのと、
notify_observersメソッドの中でObserverとなるコードブロックを実行しています。
呼び出しもとのプログラムはこんなかんじになります。
require "employee" fred = Employee.new("Fred", "Crane Operator", 30000.0) fred.add_observer do |changed_employee| puts "#{changed_employee.name}のために小切手を切ります。" puts "給料は#{changed_employee.salary}です。" end fred.salary = 35000.0
fred.add_observerの引数にコードブロックを渡しています。
これがObserverとして機能します。
実行結果はさきほどと同じ。
Fredのために小切手を切ります。 給料は35000.0です。
以上で、Observerパターンおしまい。
ちなみに、コードを読んで、ブログにまとめると大体2時間くらいかかります。
それが勉強になっていると信じてあと10コくらいのパターンの紹介を続けます。
あと、クラス図を簡単に書けるツール(Webで使える)あればいいなー。と。
おしまい。