クラスとは
複雑な責務をクラス単位に分離することで、その関係性をシンプルに表現できる概念です。
巨大な仕様をプログラムにする際に、Main関数の中にすべての処理を詰め込むのは現実的に考えて不可能なので、我々はモジュールやクラスを使って責務を分離することで、複雑な仕様に対処します。
クラスとモジュールは、責務の範囲が違うだけなので、これからお話しする内容はどちらに対しても適用できます。
クラスのメリット・デメリット
メリット
- 修正箇所、および修正に対する影響範囲の特定が容易になる
- 仕様と実装の紐づきがわかりやすく可読性が上がる
デメリット
- メリットが活かせない場合がある
クラスのメリットを活かすためのポイント
- 責務を分離できていること
- クラス名が責務を表現していること
以下はポイントに違反した、従業員と、顧客情報を扱うクラスの例です。
class EmployeeService
{
public CustomerRepository _customerRepository;
public EmployeeService(CustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public void AddEmployee(Employee employee)
{
_customerRepository.AddEmployee(emplyee)
}
public void AddCustomer(Customer customer)
{
_customerRepository.Add(customer)
}
}
1. 責務を分離できていない
従業員情報を扱うEmployeeServiceクラスに、消費者を追加する関数が入っています。
言い換えると、EmployeeServiceクラスは従業員追加の責任と、顧客追加の責任の2つを持っています。
問題点
- 消費者に関する修正が、従業員の処理に影響を及ぼす可能性がある。(その逆も然り)
- 従業員と消費者の関係に、特別な仕様があると勘違いが発生する。
- 修正が発生した際、修正対象のクラスを探す手間が発生する。
- 従業員に関する実装の中に、Customerという単語が出てくることが混乱を招く。
2. クラス名が責務を表現していない
EmployeeServiceというクラス名で消費者追加の責務を持っている。
CustomerRepositoryというクラス名で従業員追加の責務を持っている。
問題点
- 修正が発生した際、修正対象のクラスを探す手間が発生する。
- 従業員に関する実装の中に、Customerという単語が出てくることが混乱を招く。
以下に責務ごとにクラスを分離した例を示します。
class EmployeeService
{
public EmployeeRepository _employeeRepository;
public void AddEmployee(Employee employee)
{
_employeeRepository.Add(emplyee)
}
}
class CustomerService
{
public CustomerRepository _customerRepository;
public void AddCustomer(Customer customer)
{
_customerRepository.Add(customer)
}
}
分離した責務
- EmployeeServiceは従業員という概念に対する責務
- EmployeeRepositoryは従業員の情報を保持する責務
- CustomerServiceは消費者という概念に対する責務
- CustomerRepositoryは消費者の情報を保持する責務
こんな感じに4つの責務に分離してあげると先ほど挙げた問題は全て解消します。
クラスのまとめ
プログラミング界隈では、責務が正しく分離されていることを「凝集度」という指標で呼んだりします。
クラス設計には、仕様の理解と、システム設計の理解、言語や技術の習熟度が関係してきます。
経験から言えるのは、最初からすべて把握できる人はいないので、責務の分離にも限界があります。
開発中にそれらの理解が進み、もっと良い分離の仕方に気づきます。
気づいたとき、その都度リファクタリングしていくことが大事です。