技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

カプセル化によるルールの強制

オブジェクト指向を適用すると、ソースコードの重複した記述を排除でき、生産性や品質を向上することができます。
それとは別に、他の開発者にルールを強制できるメリットもあります。
カプセル化を例にして説明するのがわかりやすいので、今回はカプセル化を例に挙げてルールを強制するメリットを書いていきます。
 
----
 
例えば、自分は商品の金額を計算するPriceCalcクラスを作成していて、他の開発者はPriceCalcクラスを利用するUserクラスを作成しているとします。
自分としては「このロジックで金額計算して欲しい」というのがあるのですが、他の開発者にそのロジックの実装をさせるようにしてしまうと、意図が上手く伝わらなかった時に誤ったロジックで計算されてしまいます。
この状況をソースコードで表すと下記のような形になり、PriceCalcクラスは計算に必要な変数をまとめるだけ、PriceCalc側でその変数を参照して独自に業務ロジックを実装するような形となります。
 
public class PriceCalc {

  public ArrayList<Integer> costs = new ArrayList<Integer>();
  public int taxRate = 0;
  :
  : 色々なメンバ変数定義
  :

}
 
public class User {

  PriceCalc priceCalc = new PriceCalc();
  :
  : priceCalcオブジェクトのメンバ変数を使った複雑な業務ロジック
  :
  
}
 
----
 
それでは困るので、業務ロジックもPriceCalcクラス側に実装することにします。
変数とメソッドをひとまとまりにして一つのクラスとして提供することで、Userクラス側ではメソッドを呼び出すだけであるべき業務ロジックに基づいた計算を行うことができ、Userクラス側で業務ロジックを意識する必要が無くなります。
 
しかし、メンバ変数がpublicだと、Userクラス側でその変数を参照して独自に業務ロジックを実装することもできてしまいます。
自分としてはそれでは困るので、「変数を参照して独自に業務ロジックを実装しないでください」と伝えるのですが、それが上手く伝わらないと独自に業務ロジックを実装されてしまう可能性があります。
自分一人でプログラムを作っている場合は全ての変数をpublicにした方が便利だったりもしますが、複数人で開発している場合は意思疎通がうまく行かなかった場合に意図通りではないコーディングをされてしまうリスクがあります。
 
public class PriceCalc {

  public ArrayList<Integer> costs = new ArrayList<Integer>();
  public int taxRate;
  :
  : 色々なメンバ変数定義
  :
  
  public void registNewCost(int cost) {
    this.costs.add(cost);
  }
  :
  : メンバ変数の設定用メソッド
  :
  
  public int getPrice(int index) {
    :
    : 複雑な業務ロジック
    :
  }

}
 
public class User {

  PriceCalc priceCalc = new PriceCalc();
  :
  : PriceCalcクラスのメソッドを使った設定
  :
  int priceOk = priceCalc.getPrice(index);
  
  // しかし用意したメソッドを使用せずに独自で計算できてしまう
  int priceNg = priceCalc.costs.get(index) * priceCalc.taxRate;
  
}
 
----
 
それを防ぐためには、変数のアクセスレベルをprivate等にして、変数に自由にアクセスできないようにするのが有効です。
こうすることで、変数を参照して独自に業務ロジックを実装しようとされた場合に、コンパイルエラーとして機械的に失敗させることができます。
これが、カプセル化によるルールの強制であり、意思疎通がうまく行かなかった場合に意図通りではないコーディングをされるリスクを減らすことができます。
 
public class PriceCalc {

  private ArrayList<Integer> costs = new ArrayList<Integer>();
  private int taxRate;
  :
  : 色々なメンバ変数定義
  :
  
  public void registNewCost(int cost) {
    this.costs.add(cost);
  }
  :
  : メンバ変数の設定用メソッド
  :
  
  public int getPrice(int index) {
    :
    : 複雑な業務ロジック
    :
  }

}
 
public class User {

  PriceCalc priceCalc = new PriceCalc();
  :
  : PriceCalcクラスのメソッドを使った設定
  :
  int priceOk = priceCalc.getPrice(index);
  
  // 下記はコンパイルエラー
  // int priceNg = priceCalc.costs.get(index) * priceCalc.taxRate;
  
}