ルール9(getterを使用しない)を考える

ttomioka.hatenablog.com

先日行ったセミナーでオブジェクト指向エクササイズのルール9(getterを使用しない)が曖昧だったと感じたので、課題を仮定して考えてみました。OOエクササイズの一つの考え方としてにご覧下さい。

課題について

商品価格、数量、割引率があって総合価格(割引後価格)を計算する。
ただこれだけでは不足なので、次のルールも加える。

  • 注文は1商品のみしかできない。ただし複数量は注文できる。
  • 注文には必ず割引がある。
  • 割引額は通常価格 x 割引率。小数点以下は四捨五入。
    通常価格は商品価格 x 数量。通常価格-割引額が割引後価格。

エクササイズ

第0段階 最初の思いつき

一番初めではないがここは通りそうな道。

f:id:tmk463:20160602115044p:plain

ちなみに注文の計算するコード。
注文に必要な値を全てgetして計算している。

public int 割引後価格を計算する() {
    int 商品価格 = product.getPrice().getValue();
    int 数量 = quantity.getValue();
    BigDecimal 割引率 = discountRate.getValue();

    int 通常価格 = 商品価格 * 数量;
    int 割引 = BigDecimal.valueOf(通常価格).multiply(割引率)
                                   .setScale(0 ,RoundingMode.HALF_UP).intValue();
    int 割引後価格 = 通常価格 - 割引;
    return 割引後価格;
}

注文に計算ロジックがまとまっているが、集中しすぎている。
今後肥大化しすぎて手に負えなくなる可能性が高い。

第1段階 不足している概念を追加1

このままでは概念が不足しているので「通常価格」を導入する。
通常価格は商品価格x数量なので、商品に計算を依頼できるようにする。

f:id:tmk463:20160602115920p:plain

これで商品と商品価格のgetterを撲滅。
ただし通常価格にgetterが増えた。

第2段階 不足している概念を追加2

続いて「通常価格」と同様に「割引後価格」を導入する。
割引後価格は割引率がからむので、割引率に計算を依頼できるようにする。

f:id:tmk463:20160602120638p:plain

これで割引率のgetterを撲滅できた。

この段階の注文の計算コード。

public DiscountedPrice 割引後価格を計算する() {
    UsualPrice 通常価格 = 商品.通常価格を計算する(数量);
    DiscountedPrice 割引後価格 = 割引率.割引後価格を計算する(通常価格);
    return 割引後価格;
}

getせずに割引後価格を求められている。
また通常価格と割引後価格があるということを、コードで表現できた。

ここで終わってもいい気がするが、
まだ数量と通常価格にgetterは残っているので続行。

第3段階 最後のgetterを削除

数量と通常価格のgetterを削除したいが、ここからが難しい。
最終的に何らかの計算はしないといけないので自クラスで保持している以外の値が必要になる。
迷った結果、掛算をするメソッドを導入した。

f:id:tmk463:20160602121420p:plain

これで数量、通常価格のgetterを撲滅できたが、
ルール3(すべてのプリミティブ型文字型をラップすること)違反になった。

ただ、内部状態をさらすというカプセル化違反からは脱却できた。
何かと掛け算して使われるということも把握できる。(少なくとも自由奔放には使われない)

この他に検討した内容については次の通り。

  • intを返さずに通常価格オブジェクトを返すようにする。
    • 数量になんでもint型を渡すと通常価格を計算できるという解釈が
      生まれてしまう気がするので避けた。
  • 商品価格自体を渡してしまう。
    • 結局は数量の中でgetしてしまうので問題が解決しない。
  • getValue()ということではなくintValue()のように型を制限した形にする。
    • 内部状態をさらしていない解釈もできるのでよいのかもしれない。
      ただ用途があまり制限されないので今回は避けた。

とりあえず、getterを使わないを達成できたので終了。

振り返り

  • 第2段階まではスッとできた。
    足りないクラスやメソッドを導入することで、コードに知識が広がるしgetterの削除につながる。
  • 第3段階ではどの道に行ってもプリミティブ型を受け取ったり返したりする案しかうかばなかった。
    Valueオブジェクトとしてルール3違反はどうしようもないかもしれない。
    仕方が無いというのが現時点での結論。もっとエレガントな方法があるのだろうか。