A Day In The Life

とあるプログラマの備忘録

メソッドの引数はオペランドのみにする原則(メソッドの引数がクラス設計に影響する)

前回に続きメイヤー著「オブジェクト指向入門 第2版 方法論・実践」で面白い原則があったのでまとめてみました(コード例はJavaで書きました)。

前提

メソッドの引数は2種類ある

オペランドとオプションの見分け方

  • 引数にデフォルト値を設定しておけば呼び出し側で特に指定しなくてよい引数はオプションである
  • クラスが進化する過程においてオペランド引数は変わらないがオプションは増えたり減ったりする

メソッド呼び出し時に引数にデフォルト値があると便利だと感じた場合やある呼び出しでは引数の指定が必要ないと感じた場合その引数はオプションである可能性が高いです。

メソッドの引数はオペランドのみにする

コピー機を表す以下のクラスがあった場合。printメソッドの引数printingSize,color,numberOfCopiesはオプション引数である。このような設計だとprintメソッドを呼び出すたびに毎回オプションを指定する必要がある。
Printクラス変更前のクラス図

Printer printer = new Printer();
// A4用紙カラーで1枚印刷
printer.print("A4", true, 1);
// A5用紙カラーで1枚印刷
printer.print("A5", true, 1);

オプション引数をクラスの属性(インスタンス変数のこと)に変更するとprintメソッド呼び出しのたびにオプションを設定する必要がなくなりオプションの設定が必要最小限に抑えられる。
この時、属性に必ず初期値を設定してやること。初期値を設定するだけでたいていの場合面倒な属性のセットが不要になりprintメソッドを呼び出すだけでよくなる。初期値には一番よく使用される値を設定すること。
Printクラス変更後のクラス図

public class Priter {
  private String printingSize = "A4";
  private boolean color = false;
  private int numberOfCopies = 1;

  /* getter,setter省略 */

  public void print() {
    // 省略
  }
}
・・・
・・・・・・
Printer printer = new Printer();
// A4用紙白黒で1枚印刷
printer.print();
// プリンタの設定を変えたいときだけオプションを変更する
printer.setPrintingSize("A5");
// A5用紙白黒で1枚印刷
printer.print();

オプションをクラス属性にすることで仕様変更の範囲を最小限に抑えることができる

Printerクラスに両面印刷機能が追加になった場合、メソッドにオプション引数を指定していたバージョンのprintメソッドは引数を追加する必要があるためその影響範囲が既存のクラスにも及ぶ(仕様変更によりコンパイルエラーが発生する)。
仕様変更による影響範囲が大きい
オプションをクラスの属性にしていたバージョンのprintメソッドは引数が変わらないので既存クラスに影響を及ぼさない。解放閉鎖原則(OCP)に従っている。
仕様変更による影響範囲が小さい

例外

  • メソッドの呼び出し手順が毎回同じで決まりきっている場合
    public class Priter {
      private String printingSize = "A4";
      private boolean color = false;
      private int numberOfCopies = 1;
    
      /* getter,setter省略 */
    
      public void printWithNumberOfCopies(int numberOfCopies) {
        setNumberOfCopies(numberOfCopies);
        print();
      }
    
      public void print() {
        // 省略
      }
    }
    ・・・
    ・・・・・・
    Printer printer = new Printer();
    // A4用紙白黒で5枚印刷
    printer.printWithNumberOfCopies(5);
    


  • コンストラクタの引数にオプションを指定するのはあり
    Printクラスをさらに改善したクラス図
    Printer printer = new Printer("A5", true, 15);
    // A5用紙カラーで15枚印刷
    printer.print();
    

まとめ

メソッドの引数がクラスの仕様に関係するというのは意外に思うかもしれませんが、実際はかなり密接につながっています。クラス設計がなんかしっくりこないと感じるときは一度メソッドの引数を見直してみてはいかがでしょうか。