技術とか戦略とか

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

単一責任の原則とは

オブジェクト指向の設計ではいくつかの原則があります。
その中でも有名な原則の一つが「単一責任の原則」です。
 
この原則は、その名の通り「一つのクラスに持たせる責任は一つにする」という原則です。
この原則が何のために用いられるのかと言うと、複数の理由で1つのクラスを修正するのを防ぐためです。
ここで言う「理由」を「案件」、「クラス」を「ソース」と捉えると分かりやすいと思います。
複数の案件で1つのソースを修正する必要が出てしまうと、並行開発やバージョン管理等が煩雑になってしまい、品質に悪影響が及びます。
 
「一つのクラスに持たせる責任は一つにする」と捉えると具体的なイメージが浮かびにくいと思うのですが、「複数の案件で1つのソースを修正しないような粒度で設計する」と捉えると分かりやすいと思います。
 
-----------------
 
以下では、javaソースコードで例を出します。
商品の名前と値段を登録し、それを表示するという例です。
 
まずは悪い例からです。
 
【サンプルコード1】
・CommodityMain.java
public class CommodityMain {

    public static void main(String[ ] args) {

        RegistShowCommodity registShowCommodity = new RegistShowCommodity();
        String commodityName = "PC";

        // 商品の登録
        registShowCommodity.setCommodityPrice(commodityName, 100000);

        // 商品の表示
        registShowCommodity.showCommodityPrice(commodityName);

    }

}
 
・RegistShowCommodity.java
import java.util.HashMap;
import java.util.Map;

public class RegistShowCommodity {

    private Map<String, Integer> commodityMap
        = new HashMap<String, Integer>();

    public void setCommodityPrice (String commodityName,int price) {
        commodityMap.put(commodityName,price);
    }

    public void showCommodityPrice (String commodityName) {
        System.out.println(commodityName + ":" +
            commodityMap.get(commodityName) + " yen");
    }

}
 
【実行結果1】
PC:100000 yen
 
-----------------
 
ここで、「商品の税込み価格を計算可能にする」という案件と、「表示を綺麗にする」という案件が発生したとします。
今の設計だと、どちらの案件も RegistShowCommodityクラスの修正となってしまいます。
 
そこで、商品の登録を行う RegistCommodityクラス、商品の価格計算を行う CalcCommodityクラス、価格表示を行う ShowCommodityクラスの3クラスに分割します。
 
【サンプルコード2】
・CommodityMain.java
public class CommodityMain {

    public static void main(String[ ] args) {

        RegistCommodity registCommodity = new RegistCommodity();
        CalcCommodity calcCommodity = new CalcCommodity();
        ShowCommodity showCommodity = new ShowCommodity();
        String commodityName = "PC";

        // 商品の登録
        registCommodity.setCommodityPrice(commodityName, 100000);

        // 商品の表示
        showCommodity.showCommodityPrice(commodityName,
            calcCommodity.calcPrice(
                registCommodity.getCommodityPrice(commodityName)));

    }

}
 
・RegistCommodity.java
import java.util.HashMap;
import java.util.Map;

public class RegistCommodity {

    private Map<String, Integer> commodityMap
        = new HashMap<String, Integer>();

    public void setCommodityPrice (String commodityName,int price) {
        commodityMap.put(commodityName,price);
    }

    public int getCommodityPrice (String commodityName) {
        return commodityMap.get(commodityName);
    }

}
 
・CalcCommodity.java
public class CalcCommodity {

    public int calcPrice (int price) {
        return price;
    }

}
 
・ShowCommodity.java
public class ShowCommodity {

    public void showCommodityPrice (String commodityName,int price) {
        System.out.println(commodityName + ":" +
            price + " yen");
    }

}
 
【実行結果2】
PC:100000 yen
 
-----------------
 
3クラスに分割した結果、「商品の税込み価格を計算可能にする」という案件は CalcCommodityクラスの修正、「表示を綺麗にする」という案件は ShowCommodityクラスの修正で対応できるようになり、別々の案件で1つのクラスを修正する状態を防ぐことができました。
 
【サンプルコード3】
・CommodityMain.java
public class CommodityMain {

    public static void main(String[ ] args) {

        RegistCommodity registCommodity = new RegistCommodity();
        CalcCommodity calcCommodity = new CalcCommodity();
        ShowCommodity showCommodity = new ShowCommodity();
        String commodityName = "PC";

        // 商品の登録
        registCommodity.setCommodityPrice(commodityName, 100000);

        // 商品の表示
        showCommodity.showCommodityPrice(commodityName,
            calcCommodity.calcPriceWithTax(
                registCommodity.getCommodityPrice(commodityName)));

    }
}
 
・RegistCommodity.java
import java.util.HashMap;
import java.util.Map;

public class RegistCommodity {

    private Map<String, Integer> commodityMap
        = new HashMap<String, Integer>();

    public void setCommodityPrice (String commodityName,int price) {
        commodityMap.put(commodityName,price);
    }

    public int getCommodityPrice (String commodityName) {
        return commodityMap.get(commodityName);
    }

}
 
・CalcCommodity.java
public class CalcCommodity {

    private double taxRate = 1.1;

    public int calcPrice (int price) {
        return price;
    }

    // 「商品の税込み価格を計算可能にする」案件
    public int calcPriceWithTax (int price) {
        return (int) (price * taxRate);
    }

}
 
・ShowCommodity.java
public class ShowCommodity {

    // 「表示を綺麗にする」案件
    public void showCommodityPrice (String commodityName,int price) {
        System.out.println("■CommodityName :" + commodityName);
        System.out.println("■CommodityPrice:" + price + " yen");
    }

}
 
【実行結果3】
■CommodityName :PC
■CommodityPrice:110000 yen