オブジェクト指向設計原則 SOLID 原則について学ぼう

オブジェクト指向設計原則 SOLID 原則について学ぼう

2022年2月25日

今日の投稿は情報処理技師の勉強中に投稿したら良さそうなテーマがあり持ってきました。

それがオブジェクト指向設計原則です。各原則の頭文字を取ってSOLIDとも呼ばれています。

この原則は多くのオブジェクト指向言語だけでなく、ウェブフレームワークのSpringでも核心原理として作用するため、プログラマーが知っておくと非常に役立ちます。

面接の場でもいいですね

1. 単一責任原則 (SRP, Single Responsibility Principle)

クラスを設計する際に、一つのクラスはただ一つの責任だけを持たなければならないという原則です。

責任という言葉が少し曖昧かもしれませんが、単純に説明すると、機能の変更や、修正が発生したときに影響を受ける範囲と考えると分かりやすいです。

一般的に責任という言葉は誰かが間違った行為をしたときにそれを正すために_「誰が責任を取るべきだ!」_ とよく使いますよね。 それと同じだと思えばいいです。

単一責任の原則は、その責任において一つの事物を担当するクラスは一つの責任を負うべきであるということです。

プログラマーなら当然クラスという言葉は聞いたことがありますよね? クラスとは一つの事物の同一の属性や行為の集合を指します。

これは特定のクラスが2つ以上の責任を持っていると問題になるからです。

例を挙げてみましょうか?

車両クラスに車両の販売情報と、車両の動作原理を入れたとき、車両価格を変更すると車両動作原理を参照するクラスにも必然的に影響を及ぼすことになります。

また、車両動作原理を使用するクラスがあるとき、車両の動作に無用な情報(販売情報)が付加的に含まれているため、不要な要素を引き受けることになりますよね?

これは誰が見ても良い設計とは言えません。

このような不祥事を防ぐためにSRPは一つのクラスは一つの責任だけを持つように規定しているのです。

2. オープン・クローズド原則 (OCP, Open Close Principle)

オープン・クローズド原則(以下、OCP)は、ソフトウェアのクラス、モジュール、メソッドなどの拡張には開かれていなければならず、修正には閉じていなければならないという原則です。

この原則に最も適しているために、ファイル(file)に特定の内容(content)を書く作業を例に挙げて説明します。

以下のコードは実際に動作するコードではなく、参考用です。

void fileOneWrite(String content){
  File file = new File("./one.txt");
  file.write(content);
}

Fileクラスのメンバ関数に引数で渡された文字列を書く関数があったと仮定したとき、この関数のように設計すると、one.txtファイルに引数contentに関する内容を書き込むようになります。 でももしtwo.txtファイルにも書きたいのであればどうでしょうか?

void fileOneWrite(String content){
  File file = new File("./one.txt");
  file.write(content);
}
void fileTwoWrite(String content){
  File file = new File("./two.txt");
  file.write(content);
}
void fileThreeWrite(String content){
  File file = new File("./three.txt");
  file.write(content);
}

上のようにメソッドを追加して新しい関数を定義したり、既存の関数を変更する必要があります。

しかしオープン・クローズド原則に従って設計を行った場合、拡張を考えて次のように設計できたかもしれません

void fileWrite(String filePath, String content){
  File file = new File(filePath);
  file.write(content);
}

このようにインターフェースや継承関係、例に出てきた引数を活用して拡張性を開いて変更の可能性を閉じること、これがOCPです。

3. リスコフ置換原則 (LSP, Liskov Substitution Principle)

プログラムのオブジェクトはプログラムの正確さを損なわずに、下位タイプのインスタンスに置き換えることができなければならないという原則です。

これは特に多態性と関係があります。

void sell(Item item){
  //販売するロジック
}

このような関数があるとき、Itemインターフェースやクラスを継承する下位タイプのインスタンスであれば、sell(apple)のように呼び出したときに問題なく変更されなければならないということです!

4. インターフェース分離の原則 (ISP, Interface Segregation Principle)

クライアントが利用しないメソッドに依存してはならないという原則です。

依存性は前回の投稿で依存性注入の部分についても学びましたが、その依存性と同じです。

言い換えればクライアントは自分が利用するメソッドにだけ依存しなければならないということです。

これを少し回して考えると、複数のクライアントが使用するインターフェースがあるとき、インターフェースのメソッドの中で、いくつかのクライアントが利用しないメソッドが存在する場合、そのメソッドを含むインターフェースを新しく作り、インターフェースを分離することを考慮しなければならないということです。

例えばフェンダーエレキギターサミクアコースティックギターは共にギターインターフェースにありますが、フェンダーエレキギターアンプ接続()メソッドのためにギターインターフェースにアンプ接続()メソッドが存在する場合、サミクギタークライアントはギターインターフェースを継承しているため、自分に関係のないアンプ接続()メソッドを実装しなければなりません。

このような非効率的な部分を防ぐために、ギターインターフェースをアコースティックギターインターフェース、エレキギターインターフェースに分けてそれぞれ継承させるのがより適切な方法と言えます。

5. 依存関係逆転の原則 (DIP, Dependency Inversion Principle)

ソフトウェアモジュールを分離する特定の形式を指す原則です。

この原則は次のような内容を含んでいます。

  1. 上位モジュールは下位モジュールに依存してはならない。上位モジュールと下位モジュールの両方が抽象化に依存しなければならない。
  2. 抽象化は詳細に依存してはならない。詳細が抽象化に依存しなければならない。

この原則は、上位と下位のオブジェクトが共通の抽象化に依存しなければならないというオブジェクト指向設計の大原則を提供します。

このように言うとよく理解できませんよね? 例を使ってもっと簡単に考えてみましょう。

image

私が機械式キーボードを使用しているので私は機械式キーボードを含むオブジェクト(依存するオブジェクト)と言えます。 しかし、私は常に機械式キーボードを使用しますか? そうではありません。状況に応じてノートパソコンキーボードを、メンブレンキーボードを利用することもあるでしょう。 したがって、これらのオブジェクトを一般化したキーボードというインターフェースを中間に作ってこれを依存するのがもっと良いでしょう。

下の図のようにですよ!

image

結びに

  • 今日はこのようにオブジェクト指向設計の5つの原則SOLIDについて学びました。
  • 理解が少し難解かもしれませんが、コーディングをしながら確かに必要な部分なので、無理に暗記することよりも、実際にコーディングをしながら身につけていくことをお勧めします。
  • それでは次の投稿でお会いしましょう!