SOLID原則って何ぞや?(単一責任の原則編)

想定読者

  • オブジェクト指向プログラミングに興味がある
  • 可読性が高く、変更に強いプログラムを作りたい
  • SOLID原則を理解して周りに「ドヤァ( ^)o(^ )」したい(自己満足でも可)

はじめに

SOLID原則は以下の5つの原則の頭文字を並べて出来たネーミングです。

単一責任の原則(single-responsibility principle)

There should never be more than one reason for a class to change.

変更するための理由が、一つのクラスに対して一つ以上あってはならない。

開放閉鎖の原則(open/closed principle)

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

ソフトウェアの実体(クラス、モジュール、関数など)は、拡張に対して開かれているべきであり、修正に対して閉じていなければならない

リスコフの置換原則(Liskov substitution principle)

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

ある基底クラスへのポインタないし参照を扱っている関数群は、その派生クラスのオブジェクトの詳細を知らなくても扱えるようにしなければならない

インターフェース分離の原則(interface segregation principle)

Many client-specific interfaces are better than one general-purpose interface.

汎用なインターフェースが一つあるよりも、各クライアントに特化したインターフェースがたくさんあった方がよい

依存性逆転の原則(dependency inversion principle)

High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces), [not] concretions.

上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも具象ではなく、抽象(インターフェースなど)に依存するべきである

ふむ、なんじゃこりゃって感じですよね。大丈夫です。今から具体例をTypeScriptのコードを用いて解説していきます!

解説は、「悪い例」→「何が悪いか解説」→「良い例」といった順序になります。

単一責任の原則

// 悪い例
class SmartPhone {
	takePhoto() { /* 省略 */ } // 撮影機能
	drawPicture() { /* 省略 */ } // 絵描き機能
}

class Photographer {
	constructor(private readonly SmartPhone smartPhone) {}
	takePhoto() {
		/* 省略 */
		smartPhone.takePhoto(); // 写真家がスマホを使って撮影
		/* 省略 */
	}
}

class Illustrator {
	constructor(private readonly SmartPhone smartPhone) {}
	drawPicture() {
		/* 省略 */
		smartPhone.drawPicture(); // 絵師がスマホを使って絵描き
		/* 省略 */
	}
} 

class OrdinaryPerson {
	constructor(private readonly SmartPhone smartPhone) {}
	takePhoto() {
		/* 省略 */
		smartPhone.takePhoto(); // 一般人がスマホを使って撮影
		/* 省略 */
	}
	drawPicture() {
		/* 省略 */
		smartPhone.drawPicture(); // 一般人がスマホを使って絵描き
		/* 省略 */
	}
}

全体的なコードの良し悪しは置いておいて今回は「単一責任の原則」の観点からのみ悪い点を探していきます。(他の原則についても同じように解説していきます)

まず、ここで登場する概念は「スマホ」、「写真家」、「イラストレーター」、「一般人」の4つです。

概念間の関係としては

  • 写真家がスマホを使って撮影
  • 絵師がスマホを使って絵描き
  • 一般人がスマホを使って撮影、絵描き

といったものが挙げられます。


茶番劇開始

ここで一般人がスマホの絵描き機能、撮影機能に対して「仕様が難しすぎるからもっと簡単にして初心者向けの機能にして欲しいという」依頼を出すとします。

すると、スマホ設計者は「絵描き機能」、「撮影機能」をより簡単なものに変更します。

一般人はその修正に満足して感謝のメッセージをスマホ設計者に送りました。

ですが、その翌日スマホ設計者のもとに2通のクレームが届きました。

一つは写真家からで、もう一つは絵師からのものでした。

クレームの内容はどちらとも「機能が簡単になり過ぎて不便になってしまっているので元に戻してほしい」といったものでした。

無事スマホ設計者は「一般人」と「写真家、絵師」の間で板挟みになって脳みそがパンクしてしまいました。

茶番劇終了


茶番劇を見てみると

変更するための理由:写真家、絵師、一般人

と3つあり、

変更するための理由が、一つのクラスに対して一つ以上あってはならない。

という単一責任の原則を守れていないことが分かります。

何が問題かというとSmartPhoneクラスのメソッドに複数のアクター(利用者)がいることが問題です。スマホクラスのそれぞれのメソッドには

takePhoto: 写真家、一般人

drawPicture: 絵師、一般人

といった複数のアクターが存在してしまっています。

この問題を解消することによって単一責任の原則が守られた「良いコード」へ進化を遂げることが出来ます。

// 良い例
class SmartPhoneForPhotographer { // 写真家用
	takePhoto() { /* 省略 */ } 
}

class SmartPhoneForIllustrator { // 絵師用
	drawPicurte() { /* 省略 */ }
}

class SmartPhoneForOrdinaryPerson { // 一般人用
	takePhoto() { /* 省略 */ }
	drawPicture() { /* 省略 */ }
}

class Photographer {
	constructor(private readonly SmartPhoneForPhotographer smartPhone) {}
	takePhoto() {
		/* 省略 */
		smartPhone.takePhoto(); // 写真家が写真家用スマホを使って撮影
		/* 省略 */
	}
}

class Illustrator {
	constructor(private readonly SmartPhoneForIllustrator smartPhone) {}
	drawPicture() {
		/* 省略 */
		smartPhone.drawPicture(); // 絵師が絵師用スマホを使って絵描き
		/* 省略 */
	}
} 

class OrdinaryPerson {
	constructor(private readonly SmartPhoneForOrdinaryPerson smartPhone) {}
	takePhoto() {
		/* 省略 */
		smartPhone.takePhoto(); // 一般人が一般人用スマホを使って撮影
		/* 省略 */
	}
	drawPicture() {
		/* 省略 */
		smartPhone.drawPicture(); // 一般人が一般人用スマホを使って絵描き
		/* 省略 */
	}
}

ここでもう一度茶番劇の内容について振り返ってみると

まず一般人が「撮影機能」、「絵描き機能」を簡単にしてほしいという依頼を出す。

これを受けて機能変更が適用されるのはSmartPhoneForOrdinaryPersonというクラスのみであり、他の「SmartPhoneForPhotographer」、「SmartPhoneForIllustrator」は影響を受けません。

つまり、

  • 一般人用スマホの「撮影機能」、「絵描き機能」を変更するための理由は「一般人の要望」
  • 写真家用スマホの「撮影機能」を変更するための理由は「写真家の要望」
  • 絵師用スマホの「絵描き機能」を変更するための理由は「絵師の要望」

といった概念間の関係に変更されました。

これらの変更によって、

変更するための理由が、一つのクラスに対して一つ以上あってはならない。

という単一責任の原則が守られていることが分かります。

これで「単一責任の原則」についてはマスターしたといっても過言ではありません。

是非「私はsingle-responsibility principleを完全に理解した」とドヤってください( ^)o(^ )

次回はSOLIDの「O」の部分である「開放閉鎖の原則」について解説します!

良かったら覗いてみてください

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です