【73】単一責任原則
「変更する理由が同じものは集める、変更する理由が違うものは分ける」。良いデザインの基本原則を 1 つあげるとすればこれでしょう。
この原則は「単一責任原則(Single Responsibility Principle:SRP)」と呼ばれています。これはつまり、1 つのサブシステムやモジュール、クラス、関数などに、変更する理由が 2 つ以上あるようではいけない、ということです。1 つ典型的な例をあげましょう。ビジネスルール、レポート、データベースに関わるメソッドを持つクラスの例です。
public class Employee {
public Money calculatePay() ...
public String reportHours() ...
public void save() ...
}
3 つのメソッドが 1 つのクラスに集められていることを特に問題と思わず、むしろ望ましいと思うプログラマもいるでしょう。結局のところ、クラスというのは、閉じ変数を操作するメソッドの集まりなのだから、と考えるわけです。しかし、上のクラスが問題なのは、3 つのメソッドがまったく違った理由によって変更される可能性があるということです。calculatePay
メソッドには、給与計算に関わるビジネスルールが変わる度に、変更を加える必要があります。reportHours
メソッドには、レポートのフォーマットが変わる度に、変更を加える必要があります。save
メソッドには、DBA がデータベーススキーマを変更する度に、変更を加える必要があります。3 つも変更理由がある Employee
クラスは、非常に不安定な存在と言えます。3 つの理由のうちいずれかがあれば変更されてしまうからです。さらに重要なのは、Employee
に依存するクラスがすべて、Employee
の変更に影響されるということです。
良いシステムデザインとは、システムのコンポーネントがそれぞれ独立してデプロイできるようになっているデザインのことです。独立してデプロイできるというのは、あるコンポーネントに変更を加えたからといって、別のコンポーネントの再デプロイは不要であるという意味です。しかし Employee
が、別のコンポーネントの数多くのクラスによって利用されるのだとしたら、Employee
に変更を加える度に、他のコンポーネントの再デプロイが必要になります。それだとコンポーネントデザイン(バズワードがお好きなら、SOA と呼んでもいいでしょう)の大きな利点が失われてしまいます。では、以下のようにクラスを分割したとしたらどうでしょうか。
public class Employee {
public Money calculatePay() ...
}
public class EmployReporter {
public String reportHours(Employee e) ...
}
public class EmployeeRepository {
public void save(Employee e) ...
}
こうすれば、3 つのクラスはそれぞれ別のコンポーネントに配置されることになります。レポート関連のクラスと、データベース関連のクラスと、ビジネスルール関連のクラスが、すべて別のコンポーネントに配置されるということです。
察しのいい人なら、上のコードでもやはり依存関係は生じていると気づいたはずです。Employee
クラスに、他の 2 つのクラスが依存しているのです。したがって、Employee
が変更されれば、他の 2 つのクラスの再コンパイル、再デプロイが必要になる可能性が高いと言えます。Employee
だけを独立して修正してデプロイすることは困難なのです。それでも、他の 2 つのクラスに関しては、独立して修正し、デプロイすることが可能です。この 2 つのクラスのいずれかに変更を加えても、他のクラスの再コンパイル、再デプロイが必要になるというわけではないからです。さらに言えば、実は Employee
も「依存関係逆転の原則(Dependency Inversion Principle:DIP)」を厳格に守れば、独立してデプロイすることが可能です。ただその話は別の本に譲りましょう。
SRP を守り、違う理由で変更し得るコードを別の要素に分けることは、各コンポーネントを独立してデプロイできるような設計をする上で非常に重要な条件なのです。