オブジェクト指向の設計って難しいよね-その1

オブジェクト指向プログラミングの手法を学んでいざ一から設計してアプリケーションを作ったとしてもうまく作れないってのが直面する問題だと思う
そんな人向けにこうすればいいよという程度の内容を雑に書いていく
※あくまでこのブログを書いている人がいいよと思う方法なので他にいい方法あったらコメント等よろしくです!

コツ

アプリケーションの全体を設計して細かくしていくというのが良い
とは言っても細かくするってどうするんだというのが最初のつまづきポイントになるかと思う
細かくする上でうまくいきがちなのはアプリケーションが取り扱うデータとそのデータを制御する処理を分けるとよい
概念を説明する言葉だけではわかりにくいと思うので、ネットショップのシステムを実装する想定をして下記仕様を実装することで説明する
1. ネットショップシステムは商品を持つ 1. 商品は商品名と値段を持つ 1. 商品の税込価格を計算し取得できるようにする

第一段階

public class Product {
    /** 商品名 */
    String name;
    /** 価格 */
    int price;

    public Product(String name, int price) [
         this.name = name;
         this.price = price;
    }

    /** 商品名を取得 */
    public String getName() {
        return this.name;
    }

    /** 値段を取得 */
    public int getPrice() {
        return this.price;
    }

    /** 税込価格を取得 */
    public int getTaxInPrice() {
        return (int) (this.price * 1.1); 
    }
}

消費税の計算しているが税率などは外出しすべきという意見は例なので一旦置いておいてもらいたい 一見良さげだが悪いところがある
消費税は付加価値税なのでpriceに含めるべき?確かにそうだが今回は置いておいてほしい
さて、何があるか?消費税を廃止すべきだって?正解!!!

冗談ではなく、商品のデータを扱うクラスに税込価格を計算する機能はあまりよろしくない
税込金額も商品を取り扱う上で必要なので問題ないのではと思うかもしれない
では何故良くないのか?
例えば要件としてタイムセールを行いたいという要望があったとしよう
その際に使用を追加する際に上記のようなクラス設計の場合取れる選択肢が以下の2つになる

  1. 商品クラスをインスタンス化した側でgetPrice()してタイムセールの計算を行い消費税の計算を行う
  2. 商品クラスにタイムセールの計算を行うメソッドを追加する

1は料金の計算箇所が別々の場所複数になるので論外として、2はいいんじゃないかと思うかもしれない
だがしかし、タイムセールの計算をする機能の実装で商品のデータを管理しているクラスを変更するのは違和感を感じないだろうか
まぁ第二段階として一旦実装してみよう

第二段階

public class Product {
    /** 商品名 */
    private String name;
    /** 価格 */
    private int price;

    public Product(String name, int price) [
         this.name = name;
         this.price = price;
    }

    /** 商品名を取得 */
    public String getName() {
        return this.name;
    }

    /** 値段を取得 */
    public int getPrice() {
        return this.price;
    }

    /** 税込価格を取得 */
    public int getTaxInPrice() {
        return (int) (this.price * 1.1); 
    }

    /** タイムセール価格を取得 */
    public int getTimeSellPrice() {
        double sellPrice = 1;
        DateTime now = DateTime(TimeZone.getTimeZone("Asia/Tokyo"));
        if (now.getHourOfDay() > 18) {
            sellPrice = 0.8;
        }
        return (int)(this.price * sellPrice); 
    }

    /** タイムセール税込み価格を取得 */
    public int getTimeSellTaxInPrice() {
        return (int) (this.getTimeSellPrice() * 1.1); 
    }
}

他の影響を少なく実装することを想定するとだいたい上記のような実装になるのではなかろうか
上記のような実装は実際に動くだろうけど見てるだけでゲンナリしてくるはず

タイムセールの実装ご苦労さま^^じゃぁつぎはサマーセールとウィンターセール実装してネ^^と要件追加があったらどうなるかわかるだろう
Productクラスが神クラス化する

ではどうすればよかったのかを考える

第三段階

最初にコツであげたとおりデータとデータを制御する処理を分けてクラス化すると良い
また消費税の計算は個別のクラスにするかクラスにせずとも処理のすべてが終わったあとに計算するようにするなどをすれば良い
税込価格を商品別に計算する必要があるため静的関数を利用することを考える
やはり消費税はクソであるさっさとなくなるべきである

public class Product {
    /** 商品名 */
    private String name;
    /** 価格 */
    private int price;

    public Product(String name, int price) [
         this.name = name;
         this.price = price;
    }

    /** 商品名を取得 */
    public String getName() {
        return this.name;
    }

    /** 値段を取得 */
    public int getPrice() {
        return this.price;
    }
}

public class PriceProduct{
    private Product product;

    public ProductPrice(Product product) [
         this.product = product;
    }

    /** タイムセール価格を取得 */
    public int getTimeSellPrice() {
        double sellPrice = 1;
        DateTime now = DateTime(TimeZone.getTimeZone("Asia/Tokyo"));
        if (now.getHourOfDay() > 18) {
            sellPrice = 0.8;
        }
        return (int)(this.product.price * sellPrice); 
    }
    /** サマーセル価格を取得 */
         :
    /** ウィンターセル価格を取得 */
         :
}

public class Common {
    static int taxInPrice(int pp) {
        return (int) (pp * 1.10);
    }
}

サマセ/ウィンセはタイムセールと似た実装なので割愛
個別の消費税計算がなくなり、だいぶスッキリしたんじゃなかろうか
このようにデータと、データを扱った処理を分けることができるときれいに実装できる

タイムセールという存在が途中で出てきたからこういう実装ができたが、最初の段階でProduct/CommonのtaxInPriceが実装するのはなかなか難しいと思う
できれば最初に実装できるようになりたい
無理ならタイムセールの存在が出てきた段階で仕様を変えたい(影響が多いから修正を少なくしてねと言われるからだいたい無理だが)

詳しく知りたい人は単一責任の原則で調べるといいです

上記のようにデータ、データを扱った処理のように1つのクラスに1つのロジックを組むことを単一責任の原則という(はず、自分なりの単一責任の原則をわかりやすくアウトプットした形です。まちがってたらすんまそん。)ので気になる人は単一責任の原則で調べるといいです。

掲載しているコードはコンパイルとか全くしていないのでコピペしても動かない可能性がありますあしからず