Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

書籍 リファクタリング−プログラマーの体質改善 | オブジェクト間での特性の移動 | 局所的拡張の導入

内容

リファクタリング

局所的拡張の導入

適用ケース要約

利用中のサーバクラスにメソッドをいくつか追加する必要があるが、クラスを変更できない

適用内容要約

それらの追加されるメソッドを備えた新たなクラスを作る。この拡張クラスは元のクラスのサブクラスまたはラッパーである

適用詳細

要は外部メソッドの追加が複数になった場合の対応法。

事情により、ソースコードを編集することが出来ないクラスに対して機能を追加したい場合、
クライアント側にサーバサーバクラスのインスタンスを第一引数にとるメソッドを追加します。
このメソッドが増えてきた場合、サーバクラスを継承するか委譲した新たなクラスを作成して
機能を拡張します。

※あくまで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)}"