パンくず
リファクタリング-プログラマーの体質改善テクニック
オブジェクト間での特性の移動
局所的拡張の導入
内容
リファクタリング名
局所的拡張の導入
適用ケース要約
利用中のサーバクラスにメソッドをいくつか追加する必要があるが、クラスを変更できない
適用内容要約
それらの追加されるメソッドを備えた新たなクラスを作る。この拡張クラスは元のクラスのサブクラスまたはラッパーである
適用詳細
事情により、ソースコードを編集することが出来ないクラスに対して機能を追加したい場合、
クライアント側にサーバサーバクラスのインスタンスを第一引数にとるメソッドを追加します。
このメソッドが増えてきた場合、サーバクラスを継承するか委譲した新たなクラスを作成して
機能を拡張します。
※あくまでJavaの書籍を基準にした内容なので、この前提で進めますがRubyの場合は
オープンクラスにより既存コードに影響を与えることなく処理を追加できます。
そのため、Javaのみで有効となるリファクタリング手法です。
サンプル
ある文章を「諸見里」語に変換します。
前提としてこのプログラムの作成者はStringクラスに諸見里語への翻訳機能を追加するのが妥当だと
思っています。
ここでの定義
諸見里語=「さ、す、せ、そ」が「しゃ、しゅ、しぇ、しょ」に変わる
しかし、JavaのStringクラスを変更することはできないため、自分のクラスに変換用のメソッドを追加します。
※諸見里については以下を参照
http://ja.wikipedia.org/wiki/%E8%AB%B8%E8%A6%8B%E9%87%8C%E5%A4%A7%E4%BB%8B
さらに、猫語を作成します。
ここでの定義
猫語=「な、ぬ、の」が「ニャ、ニュ、ニョ」に変わる
Stringクラスに追加したい機能が複数になったので、これらをまとめてクラスにします。
(委譲を利用する場合は、フィールドにStringのオブジェクトを持ったクラスを作成して新規メソッドを追加します。
さらに既存機能と同じ機能を全てコピーする必要があるので結構大変・・・)
サンプルコード
▼リファクタリング前
class Moromizato def talk(message) moromizato_change_word_list={"さ"=>"しゃ","す"=>"しゅ","せ"=>"しぇ","そ"=>"しょ"} moromizato_message=message moromizato_change_word_list.each{|before,after| moromizato_message=moromizato_message.gsub(before,after) } return moromizato_message end end class Cat def talk(message) cat_change_word_list={"な"=>"ニャ","ぬ"=>"ニュ","の"=>"ニョ"} cat_message=message cat_change_word_list.each{|before,after| cat_message=cat_message.gsub(before,after) } return cat_message end end moromizato=Moromizato.new message="せたがやのさかな屋さんのそばにしらない人がいた。すごい。" puts "原文:#{message}" puts "諸見里:#{moromizato.talk(message)}" cat=Cat.new puts "猫:#{cat.talk(message)}"
▼リファクタリング後
class ExtendedString < String def to_moromizato() moromizato_change_word_list={"さ"=>"しゃ","す"=>"しゅ","せ"=>"しぇ","そ"=>"しょ"} moromizato_message=self moromizato_change_word_list.each{|before,after| moromizato_message=moromizato_message.gsub(before,after) } return moromizato_message end def to_cat() cat_change_word_list={"な"=>"ニャ","ぬ"=>"ニュ","の"=>"ニョ"} cat_message=self cat_change_word_list.each{|before,after| cat_message=cat_message.gsub(before,after) } return cat_message end end class Moromizato def talk(message) extended_string=ExtendedString.new(message) return extended_string.to_moromizato end end class Cat def talk(message) extended_string=ExtendedString.new(message) return extended_string.to_cat end end moromizato=Moromizato.new message="せたがやのさかな屋さんのそばにしらない人がいた。すごい。" puts "原文:#{message}" puts "諸見里:#{moromizato.talk(message)}" cat=Cat.new puts "猫:#{cat.talk(message)}"
▼出力結果(共通)
原文:せたがやのさかな屋さんのそばにしらない人がいた。すごい。 諸見里:しぇたがやのしゃかな屋しゃんのしょばにしらない人がいた。しゅごい。 猫:せたがやニョさかニャ屋さんニョそばにしらニャい人がいた。すごい。
解説
諸見里語への変換機能と猫語への変換機能をクラスとして抽出したことで、
変換機能が再利用可能となりました。
おまけ
RubyのオープンメソッドでStringクラスを拡張した場合。
あたかもStringクラスに組み込まれている機能であるかのように
諸見里語変換機能と猫語変換機能が利用できます。
class ExtendedString < String def to_moromizato() moromizato_change_word_list={"さ"=>"しゃ","す"=>"しゅ","せ"=>"しぇ","そ"=>"しょ"} moromizato_message=self moromizato_change_word_list.each{|before,after| moromizato_message=moromizato_message.gsub(before,after) } return moromizato_message end def to_cat() cat_change_word_list={"な"=>"ニャ","ぬ"=>"ニュ","の"=>"ニョ"} cat_message=self cat_change_word_list.each{|before,after| cat_message=cat_message.gsub(before,after) } return cat_message end end class Moromizato def talk(message) extended_string=ExtendedString.new(message) return extended_string.to_moromizato end end class Cat def talk(message) extended_string=ExtendedString.new(message) return extended_string.to_cat end end moromizato=Moromizato.new message="せたがやのさかな屋さんのそばにしらない人がいた。すごい。" puts "原文:#{message}" puts "諸見里:#{moromizato.talk(message)}" cat=Cat.new puts "猫:#{cat.talk(message)}"