Tbpgr Blog

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

書籍 Refactoring to Patterns | Creation | Encapsulate Classes with Factory

パンくず

書籍 Patterns to Patterns
Creation
Encapsulate Classes with Factory

概要

Encapsulate Classes with Factoryのリファクタリングについて

使用する場面

一つのインターフェースを実装したクラスが複数あった場合に、
必要最低限のクラスのみ外部に公開したい。

対応方法

クラスの実装をFactoryで隠蔽する。

利点と欠点

利点

・目的を明らかにしたメソッドの利用でインスタンス生成がシンプルになる。
・公開が不要なクラスを隠蔽することで"conceptual weight"が減る
・"program to interface,not implementation"の真言に従う

欠点

・あらたなクラスを追加する度にCreationの変更が必要になる。
・Factoryのソースコードにアクセス出来なければこのリファクタリングを適用出来ない。

手順

1.いろんな種類のインスタンスを直接生成しているクライアントを見つける。
コンストラクタにpublic staticな"Extract Method"を適用する。
この新たなメソッドはCreation Methodです。
スーパークラスに"Move Method"を適用する。

2.全てのコンストラクタ利用箇所を探し、Creation Methodを呼ぶように修正する。

3.他のインスタンスにも1~2を繰り返す

4.コンストラクタをnon-pulbicにする。

5.あなたの望むカプセル化になるまで全てのクラスに1~4を繰り返す

サンプル

プログラム言語クラス(ProgrammingLanguage)があり、これを継承したJavaクラス(Java)とRubyクラス(Ruby)があります。
これを参照するクライアントに対しては、JavaRubyの実装は意識させないようにします。

リファクタリング
# encoding: Windows-31J

class ProgrammingLanguage
  attr_accessor:name
  def initialize(name);@name = name;end
  def coding();puts "コーディング";end
end

class Java < ProgrammingLanguage
  JAVA = "Java"
  def initialize;@name = JAVA;end
  def coding();puts "Javaでコーディング";end
end

class Ruby < ProgrammingLanguage
  RUBY = "Ruby"
  def initialize;@name = RUBY;end
  def coding();puts "Rubyでコーディング";end
end

class Client
  def use_language(language_name)
    language = nil
    if language_name == Java::JAVA
      language = Java.new
    elsif language_name == Ruby::RUBY
      language = Ruby.new
    end
    language.coding
  end
end

[Java::JAVA,Ruby::RUBY].each {|language_name|Client.new.use_language(language_name)}
リファクタリング

Factoryメソッドを導入後、さらにDart言語を追加しました。
既存クラスへの影響なく追加出来ました。

# encoding: Windows-31J

@JAVA = "Java"
@RUBY = "Ruby"

class ProgrammingLanguage
  attr_accessor:name
  def initialize(name);@name = name;end
  private:initialize
  def self.get_java_language()
    return Java.new(@JAVA)
  end
  def self.get_ruby_language()
    return Ruby.new(@RUBY)
  end
  
  def coding();puts "コーディング";end
end

class Java < ProgrammingLanguage
  def initialize(language);@name = @JAVA;end
  protected:initialize
  def coding();puts "Javaでコーディング";end
end

class Ruby < ProgrammingLanguage
  def initialize(language);@name = @RUBY;end
  protected:initialize
  def coding();puts "Rubyでコーディング";end
end

puts ProgrammingLanguage::get_java_language.coding
puts ProgrammingLanguage::get_ruby_language.coding