はじめに
単一責任の原則というのをご存知でしょうか?
SOLID原則のうちの一つに含まれる原則で、CleanArchitecture にて、モジュールの関係性を「モジュールはたったひとつの変更を望む人に対して責任を負うべきである」と説明している原則です。
よくクラスAとかクラスBとか依存だとか関係だとか抽象的な言葉で説明されているため、私が全く飲み込めなかったため、具体的な例に落とし込んでみました。
変更とは?責任とは?
変更
そのままの意味。
クラスや、インターフェース、関数など、ソースコードに関わるすべてのものに対する変更
責任
こちらもそのままの意味
そのモジュールの役割=責任
例えばTeacher とStudentというクラスを考えます。
Teacher クラスはteach メソッドの引数にもらったStudent に対して、subject をセットすることを責任として持っています。
Student.setTeachSubject を呼ぶことで、Student がなんの科目を教えられたのかを保存します。
class Teacher {
teach(student: Student, subject: string) {
student.setTeachedSubject(subject)
console.log(`${student.name} に${subject}を教えました`)
}
}
class Student {
private const teachedSubject: string
setTeachedSubject(subject: string) {
this.teachedSubject = subject
}
}
変更を望む人とは?
Teacher クラスの例えをそのまま使うと、Teacher クラスに依存している別のロジックが、変更を望む人たちと言う事になります。
例えば Teacher クラスを使う、TeachingService というクラスを作成したとします。
class TeachingService {
teachingMath() {
const student = new Student()
const teacher = new Teacher()
const subject = '数学'
teacher.teach(student, subject)
}
}
TeachingService は、teachingMath というメソッドを持っており、名前の通り数学を教える役割を持っています。
そのロジックの中でTeacher クラスに依存しています。
もしteachingMath メソッドで、subjectを複数保存したい。などの希望が出てきたとき
、初めてTeachingService クラスはTeacher クラスの変更を望む人になります。
今この瞬間、Teacherクラスに依存しているクラスは単一で、Teacher クラスを変更を望む人も単一な状態です。
つまり、Teacherクラスはたった一つの変更を望むクラスTeacherServiceに対して責任を負う。という意味で単一責任の原則を適用された状態になっています。
変更を望む人が複数いたらどんな不都合がある?
さて、単一責任の原則。という名前が付いているのは、変更を望む人が複数いると不都合が発生するからです。
では、変更を望む人が二人いるときのことを考えてみます。
TeachingService と同様に、Teacher クラスに依存しているClubService が新しく出来上がったとします。
class ClubService {
teachingSoccer() {
const student = new Student()
const teacher = new Teacher()
const sports = 'サッカー'
teacher.teach(student, sports)
}
}
teachingSoccer メソッドは、生徒に対してサッカーを教える責任を持っています。
TeachingServiceと同様、teachingSoccer でteach メソッドに変更を加えたくなったとき、ClubService はTeacher クラスの変更を望む人になります。
これにより、Teacher は単一責任の原則に違反した状態になりました。
また、ClubService もTeachingService も、お互いのことは考慮せずにteach メソッドを使用しています。
元々、Teacher クラスはTeachingService に対して責任を負っていたため、TeachingService の要求に従ってteach メソッドのロジックを変更しましょう。
TeachingService はsubject が数学の時以外はエラーを返すようにしたとします
(そんな変更要求はなさそうですが。。。)
class Teacher {
teach(student: Student, subject: string) {
if(subject !== '数学') {
throw new Error()
}
student.setTeachedSubject(subject)
console.log(`${student.name} に${subject}を教えました`)
}
}
そうすると、ClubService は常にエラーを返却されてしまうことになります。
全く異なる文脈で使用されていることを知らないまま、TeachingService がteach メソッドを変更したことで、ClubService が期待する動作を行えなくしてしまいました。
これが単一責任の原則に違反したときの不都合です。
ClubServiceはTeacher クラスを使用せず、ClubService に対して責任を負う別のクラスを参照すべきなのです。
まとめ
この記事では単一責任の原則に付いて、簡単なロジックを使って説明しました。
異なる文脈で使用されるロジックは、簡単に不具合を起こすことができるため、単一の文脈で使用されるようにクラス間の関係性を整理することが重要になります。