読者です 読者をやめる 読者になる 読者になる

南極の図書館

ペンギンが寝ていた…。

「リファクタリング」の完全読破。その7、7章前半

7章は2回に分けて。今回は前半部分について。

第7章 オブジェクト間での特性の移動

オブジェクトの設計において根幹をなすのは責任をどこに配置するか。
しかし、ファウラーでも「責任を初めから正しいところに配置することができません。」と言う。
後からでも、リファクタリングを使って適切に配置していけば良い、ということだ。
それに、システムは変化していくということを忘れてはいけない。

P146 ある週では正しく適正であった設計判断も、次の週にはそうでなくなります。それが問題なのではなく、それについてなにもしないことが問題なのです。

このような状況を受け入れなければならない。
受け入れた上で、下記のような方法で解消していく。

メソッドの移動(P142)

メソッドが、定義しているクラスよりも他のオブジェクトを参照することが多い場合に検討する。
移動することでクラスが単純になり、責任の集合をすっきりした実装に収めることができる。
ただし、サブクラスまたはスーパークラスで利用しているメソッドであり、ポリモーフィズムをまるごと表現できないならば移動してはいけない。
メソッドの移動を行うメソッドへの参照が多い場合は、すべて書き換えるのは困難なので委譲メソッドを残す。
(コード例はフィールドの移動と合わせて後述。)

フィールドの移動(P146)

フィールドが、現在または将来に渡って、定義しているクラスよりも他のクラスから使われることが多いときに行う。
はじめに「フィールドのカプセル化(P206)」、「自己カプセル化フィールド(P171)」を行う。つまりprivateにしてアクセサを作る。
その後、フィールドの移動と参照しているメソッドの修正を行う。


上記二つの例を合わせて示す。
まずは、リファクタリング前のコード。

class Account{
  private AccountType _type    = new AccountType();
  private int _daysOverdrawn = _type.daysOverdrawn;
  //メソッドの移動前
  double overdraftCharge(){
    if(_type.isPremium()){
      double result = 10;
      if(_daysOverdrawn > 7) result += (_daysOverdrawn - 7) * 0.85;
      return result;
    }
    else return _daysOverdrawn * 1.75;
  }
  //フィールドの移動前
  private double _interestRate;
  double interestForAmount_days(double amount, int days){
    return _interestRate * amount * days / 365;
  }
}
class AccountType{
  public int daysOverdrawn;
  public AccountType() {
    daysOverdrawn = 8; //本来はもっと複雑な式
  }
  boolean isPremium(){
    return true; //本来はもっと複雑な式
  }
}


上記のコードをリファクタリングすると、下記のようになる。
なお、このコード例はあくまで手法を示すものであり、リファクタリングを適用する状況の判断は自分自身で行う。
(実際に、ここに書いたコードだけを見ると、私はメリットがあるとは思えない。)

class Account{
  private AccountType _type    = new AccountType();
  private int _daysOverdrawn;
  //メソッドの移動後に委譲にする場合。そうでなければ呼び出し元をすべて修正する。
  double overdraftCharge(){
    return _type.overdraftCharge(_daysOverdrawn);
  }
  //フィールドの移動後、アクセサに変更する。
  double interestForAmount_days(double amount, int days){
    return _type.getInterestRate() * amount * days / 365;
    //利用するメソッドが多い場合は_type.getInterestRate()を返すgetInterestRate()を作るのも良い。
  }
}
class AccountType{
  public int daysOverdrawn;
  public AccountType() {
    daysOverdrawn = 8;
  }
  boolean isPremium(){
    return true;
  }
  //メソッドの移動後
  double overdraftCharge(int daysOverdrawn){
    if(isPremium()){
      double result = 10;
      if(daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
      return result;
    }
    else return daysOverdrawn * 1.75;
  }
  //フィールドの移動後、アクセサも定義する。
  private double _interestRate;
  double getInterestRate() {
    return _interestRate;
  }
  void setInterestRate(double arg){
    _interestRate = arg;
  }
}
クラスの抽出(P149)

クラスは、きっちり抽象化されたものであり、少数の明確な責任を担うべきである。
しかし、実際にはクラスは成長していくので、大きすぎて理解出来なくなったクラスは切り離す必要がある。
切り離し、元のクラスから新クラスにリンクを貼り(必要があるまで双方向にはしない)、低レベルなメソッドから移動していく。

(切り離したNewClassに対して、元のOriginalClassからリンクを貼る)
class OriginalClass...
  private NewClass _newClass = new NewClass();

クラスの抽出後に考えるのはアクセス制御。
publicは問題が多く、複製してから外部に渡すことも混乱の元になる。
ここでは元クラスを利用することを強制するか、新クラスを不変にするか、新クラスの不変インタフェースを用意する方法が薦められている。
また、最後に「クラスの抽出は、並列プログラムの並列実行可能性を向上するための一般的な技法」とも書かれている。

クラスのインライン化(P154)

クラスの抽出の逆。特に注意は無し。


7章は次回に続く。