クラスに責務を与える - 良いクラスに関する考察 -

# 概要
前回の記事では[オブジェクト指向プログラミングの3大要素](https://qiita.com/masaki_nakada_ari/items/39953452cfb12a58c4a3)を紹介しました。
オブジェクト指向プログラミングにおいてクラスはオブジェクトを作成するための設計図とよく言われます。
良いクラスは良い設計図ということになりますが、良いクラスはどんなクラスでしょうか？
本記事では例題からクラス図とサンプルコードを作成して、より実践的に良いクラスに関する考察をしていきます。

今回は「クラスに責務を与える」について考えてみます。
クラスはデータ（変数）と操作（メソッド）を定義することができます。
データのみを定義したクラス（データクラス）や操作のみを定義したクラス（管理クラス）よりも
データ（変数）を使用した操作（メソッド）が定義されているクラスのほうが良いクラスになる傾向があります。
ここでは責務を以下のように定義します。

::: note info
**責務 ＝ データ（変数）を使用した操作（メソッド）が定義されているクラス**
:::

# 例題
注文書に商品単位で注文明細を記載しています。
注文書に全ての注文明細の合計金額、注文明細に商品の合計金額を記載しています。
注文書を管理するためにクラスを抽出して、クラスに適切なデータと操作を追加してください。

# クラスに責務を与えない
まずはクラスに責務を与えないサンプルとしてデータと操作を別のクラスで定義してみましょう。
以下のクラスを定義します。
・Order（注文書）
・OrderDetail（注文明細）
・Item（商品）
・OrderManager（注文書管理）

## クラス図
クラス図で確認してみましょう。

``` plantuml
@startuml
class Order
{
    + OrderNumber : int
    + OrderDate : DateTime
    + OrderDetailList : List<OrderDetail>
}

class OrderDetail
{
    + OrderNumber : int
    + DetailNumber : int
    + OrderItem :Item
    + Quantity : int
}

class Item
{
    + ItemNumber : int
    + ItemName : string
    + Price : int
}

class OrderManager
{
    + SumPrice(Order order) : int
    + SumDetailPrice(OrderDetail detail) : int
}

Order --> "0..*" OrderDetail
OrderDetail --> "1" Item
OrderManager .right.> Order
OrderManager ..> OrderDetail
@enduml
```

Order、OrderDetail、Itemにデータが定義されていて、OrderManagerに操作が定義されています。
OrderManagerからOrder、OrderDetailに破線矢印が伸びていています。

## サンプルコード（C#）
サンプルコードは以下のようになります。

``` diff_c_sharp
public class Order
{
    public Order(int orderNumber)
    {
        OrderNumber = orderNumber;
        OrderDate = DateTime.Today;
        OrderDetailList = new List<OrderDetail>();
    }

    public int OrderNumber { get; set; }
    public int OrderDate { get; set; }
    public List<OrderDetail> OrderDetailList { get; private set; }
}

public class OrderDetail
{
    public OrderDetail(int orderNumber, int detailNumber, Item orderItem, int quantity)
    {
        OrderNumber = orderNumber;
        DetailNumber = detailNumber;
        OrderItem = orderItem;
        Quantity = quantity;
    }

    public int OrderNumber { get; set; }
    public int DetailNumber { get; set; }
    public Item OrderItem { get; set; }
    public int Quantity { get; ;set; }
}

public class Item
{
    public Item(int itemNumber, string itemName, int price)
    {
        ItemNumber = itemNumber;
        ItemName = itemName;
        Price = price;
    }

    public int ItemNumber { get; set; }
    public string ItemName { get; set; }
    public int Price { get; set; }
}

public class OrderManager()
{
    public int SumPrice(Order order)
    {        
        var sum = 0;

        foreach (var detail in order.OrderDetailList)
        {
            sum += SumDetailPrice(detail);
        }

        return sum;
    }

    public int SumDetailPrice(OrderDetail detail)
    {
        return detail.Quantity * detail.OrderItem.Price;
    }
}

class MainClass
{
    static void Main(string[] args)
    {
        // サンプルコードのため、オブジェクトの生成をコード上で記載しています。
        var item501 = new Item(501, "Item 501", 100);
        var item551 = new Item(551, "Item 551", 2000);
        var orderNumber = 12345;
        var orderManager = new OrderManager();
        var order = new Order(orderNumber);

        order.OrderDetailList.Add(new OrderDetail(orderNumber, 1, Item501, 7));
        order.OrderDetailList.Add(new OrderDetail(orderNumber, 2, Item551, 3));

        console.WriteLine(orderManager.SumPrice(order)); // 6700
    }
}
```

特に問題がないように思えますが、OrderManagerに操作が集中しています。
OrderManagerがOrderとOrderDetailの2つのクラスを管理しています。

::: note warn
データクラスに対して管理クラスを必ず定義しなければいけないのは、クラスの数が増えるほど煩わしさが増えます。
:::

# クラスに責務を与える
クラスに責務を与えるために、以下の修正をします。
・OrderにSumPriceを移動する。
・OrderDetailにSumDetailPriceを移動する。
・OrderManagerを削除する。

## クラス図
修正したクラス図は以下のようになります。

``` plantuml
@startuml
class Order
{
    + OrderNumber : int
    + OrderDate : DateTime
    + OrderDetailList : List<OrderDetail>
    <color:red>+ SumPrice() : int</color>
}

class OrderDetail
{
    + OrderNumber : int
    + DetailNumber : int
    + OrderItem :Item
    + Quantity : int
    <color:red>+ SumDetailPrice() : int</color>
}

class Item
{
    + ItemNumber : int
    + ItemName : string
    + Price : int
}

Order --> "0..*" OrderDetail
OrderDetail --> "1" Item
@enduml
```
OrderManagerは削除されましたが、クラスの関連や多重度に修正はありません。

::: note info
OrderにSumPrice、OrderDetailにSumDetailPriceを定義しているのがポイントです。
:::

## サンプルコード（C#）
修正したサンプルコードは以下のようになります。

``` diff_c_sharp
public class Order
{
    public Order(int orderNumber)
    {
        OrderNumber = orderNumber;
        OrderDate = DateTime.Today;
        OrderDetailList = new List<OrderDetail>();
    }

    public int OrderNumber { get; set; }
    public int OrderDate { get; set; }
    public List<OrderDetail> OrderDetailList { get; private set; }

+   public int SumPrice()
+   {
+       var sum = 0;
+
+       foreach (var detail in OrderDetailList)
+       {
+           sum += detail.SumDetailPrice();
+       }
+
+       return sum;
+   }
}

public class OrderDetail
{
    public OrderDetail(int orderNumber, int detailNumber, Item orderItem, int quantity)
    {
        OrderNumber = orderNumber;
        DetailNumber = detailNumber;
        OrderItem = orderItem;
        Quantity = quantity;
    }

    public int OrderNumber { get; set; }
    public int DetailNumber { get; set; }
    public Item OrderItem { get; set; }
    public int Quantity { get; ;set; }

+   public int SumDetailPrice()
+   {
+       return Quantity * OrderItem.Price;
+   }
}

public class Item
{
    public Item(int itemNumber, string itemName, int price)
    {
        ItemNumber = itemNumber;
        ItemName = itemName;
        Price = price;
    }

    public int ItemNumber { get; set; }
    public string ItemName { get; set; }
    public int Price { get; set; }
}

- public class OrderManager()
- {
-     public int SumPrice(Order order)
-     {        
-         var sum = 0;
-
-         foreach (var detail in order.OrderDetailList)
-         {
-             sum += SumDetailPrice(detail);
-         }
-
-         return sum;
-     }
-
-     public int SumDetailPrice(OrderDetail detail)
-     {
-         return detail.Quantity * detail.OrderItem.Price;
-     }
- }

class MainClass
{
    static void Main(string[] args)
    {
        // サンプルコードのため、オブジェクトの生成をコード上で記載しています。
        var item501 = new Item(501, "Item 501", 100);
        var item551 = new Item(551, "Item 551", 2000);
        var orderNumber = 12345;
-       var orderManager = new OrderManager();
        var order = new Order(orderNumber);

        order.OrderDetailList.Add(new OrderDetail(orderNumber, 1, Item501, 7));
        order.OrderDetailList.Add(new OrderDetail(orderNumber, 2, Item551, 3));

-       console.WriteLine(orderManager.SumPrice(order)); // 6700
+       console.WriteLine(order.SumPrice()); // 6700
    }
}
```

Order#SumPriceでOrderDetail#SumDetailPriceを使用しています。
クラスを使用している部分に関してはそれほど大きな修正がありませんが、引数なしでメソッドが呼び出せるようになって少しすっきりしました。

::: note info
引数が多くなるほどメソッドは使いにくくなるので、引数なしでメソッドが呼び出せるのは大きな意味があります。
:::

## まとめ
今回は「クラスに責務を与える」について考えてみました。
クラスに責務を与えることでオブジェクト指向らしいクラスになります。
クラスの凝集度も高くなり、使いやすいクラスになります。

::: note info
「クラスにどんな責務があるか」を意識してクラス設計をすると良いクラスになります。
:::

最後まで読んでいただきありがとうございます。