ホームに戻る
出典 :
開発者が知っておくべきSOLIDの原則 | POSTD SOLID原則まとめ - Qiita SOLID原則について簡単に書く - Qiita
関連 :
構造化プログラミング [C++]継承 (Inheritance) [C++]仮想関数 (Virtual Function) ソフトウェア開発におけるアンチパターン
目次 :

SOLIDの原則とは

Robert.C.Martinによって提唱された、オブジェクト指向プログラミングにおけるガイドライン。
開発者にとって読みやすく、メンテナンスが容易なプログラムを作成するための指針である。

S : SRP - 単一責務の原則

クラス(またはモジュール、ソフトウェアコンポーネント)が担う責務(機能)は一つにする という原則。
クラスが複数の責務を担っている場合、一つの責務に対する変更が、他の責務に影響してしまう可能性がある。
このため、クラスが複数の責務を担っている場合は、 責務ごとにクラスを分割し、一段階抽象的な一つの責務を担うクラスがこれらを所有する
⇒ 責務の階層化
モジュール強度を「機能的強度」とする設計を意味する。

O : OCP - 開放閉鎖の原則

ソフトウェアのエンティティ(クラス・モジュール・関数)は拡張に対して開き、修正に対して閉じていなければならない という原則。
// 「動物」クラス class Animal { public: Animal(string name) : m_Name(name) {} // 鳴く void Sound(); private: string m_Name; } // 鳴く void Animal::Sound() { switch (m_Name) { case "dog": //< 犬 cout << "わんわん"; break; case "cat": //< 猫 cout << "みゃー"; break; default: break; } }
上記のコードはOCPに従っていない例である。
新しい「動物」を追加(拡張)するごとに、Animal クラス(の Sound() 関数)を修正しなければならず、(関連が無いはずの)既存のコードへの影響が避けられない。
case 節を列挙することにより関数、クラスが肥大化し、整備性が低下することとなる。
これをOCPに従うよう書き直すと以下のようになる。
// 「動物」クラス class Animal { public: // 鳴く(純粋仮想関数) virtual void Sound() = 0; } // 「犬」クラス class Dog : public Animal { public: // 鳴く(オーバーライド) void Sound() { cout << "わんわん"; } } // 「猫」クラス class Cat : public Animal { public: // 鳴く(オーバーライド) void Sound() { cout << "にゃー"; } } // 「蛇」クラス class Snake : public Animal { public: // 鳴く(オーバーライド) void Sound() { cout << "シャー"; } }
ここでは新しい動物(蛇)を追加する場合でも Animal クラスを変更する必要は無く、影響範囲は新たに定義する Snake クラスで閉じられる。
即ち Animal クラスは「拡張」に対して開き、「変更」に対して閉じている。
OCPが守られることで、既存の構成要素が他の構成要素の変更から全く影響を受けなくなる。

L : LSP - リスコフの置換原則

サブクラスは、そのスーパークラスで代用可能でなければならない という原則。 その逆も同様である
int hogehoge(Sample ob)
Sample 型の引数を取る hogehoge() 関数において、Sample 型から派生したクラスのインスタンスを引数として渡しても、その文自体はエラーとはならない。
むしろ、サブクラスのインスタンスを渡したとしても正常に動作することを期待することが多い。
しかし、この hogehoge() が Sample 型のみに依存しており、サブクラスのインスタンスを渡した場合に不正な動作となるのであれば、有効な引数とそうでない引数に留意する必要が生じる。

I : ISP - インタフェース分離の原則

自分たちが使用しないインタフェースに依存することを強いられるべきではない という原則。
下記のコードはISPに従っていない例である。
// クラス「図形」 class Shape { public: virtual void drawCircle() = 0; //< 円を描画 virtual void drawSquare() = 0; //< 正方形を描画 virtual void drawRectangle() = 0; //< 矩形を描画 } // クラス「円」 class Circle : public Shape { public: void drawCircle(); //< 円を描画 void drawSquare(); //< 正方形を描画 void drawRectangle(); //< 矩形を描画 } // クラス「正方形」 class Squqre : public Shape { public: void drawCircle(); //< 円を描画 void drawSquare(); //< 正方形を描画 void drawRectangle(); //< 矩形を描画 } // クラス「矩形」 class Rectangle : public Shape { public: void drawCircle(); //< 円を描画 void drawSquare(); //< 正方形を描画 void drawRectangle(); //< 矩形を描画 }
Circle クラスは「円」を司っているため、drawSquare() および drawRectangle() は本来必要が無い。
(これらの関数に動作を規定することは不可能、または無意味である。一般的には単にエラー処理を行うのみとなる。)
しかし、Shape クラスが抽象クラスであることから、必要の無いこれらを実装する必要が生じる。
これをISPに従うよう書き直すと以下のようになる。
// 「円を描画」インタフェースクラス class DrawCircleIf { public: virtual void drawCircle() = 0; //< 円を描画 } // 「正方形を描画」インタフェースクラス class DrawSquareIf { public: virtual void drawSquare() = 0; //< 正方形を描画 } // 「矩形を描画」インタフェースクラス class DrawRectangleIf { public: virtual void drawRectangle() = 0; //< 矩形を描画 } // クラス「円」 class Circle : public DrawCircleIf { public: void drawCircle(); //< 円を描画 } // クラス「正方形」 class Squqre : public DrawSquareIf { public: void drawSquare(); //< 正方形を描画 } // クラス「矩形」 class Rectangle : public DrawRectangleIf { public: void drawRectangle(); //< 矩形を描画 }
または
// クラス「図形」 class Shape { public: virtual void draw() = 0; //< 描画 } // クラス「円」 class Circle : public Shape { public: void draw(); //< 描画 } // クラス「正方形」 class Squqre : public Shape { public: void draw(); //< 描画 } // クラス「矩形」 class Rectangle : public Shape { public: void draw(); //< 描画 }
上記のコードではいずれも、必要の無いインタフェースは実装していない。

D : DIP - 依存性逆転の原則

高水準モジュールは低水準モジュールに依存してはならない という原則。
原文は、
依存性は、具体化でなく抽象化でなければならない。
class Hoge { public: void fuga() { Obj ob; ob.mogo(); } }
上記のコードはDIPに従っていない。
Hoge クラスは Obj クラス(低水準モジュール・詳細・具体化)に依存しており、Obj クラスの情報が無いと処理を記述することができない。
また、Obj クラスに変更があった場合、Hoge にも変更が及ぶ。(これはOCPにも違反している。)
これを書き直すと以下のようになる。
// インタフェースのみを定義した抽象クラス class AbstractIf { public: virtual void mogo() = 0; } class Hoge { public: void fuga(AbstractIf* ob) { ob->mogo(); } }
Hoge は具体的な特定のクラスではなく、抽象化に依存している。このため、実際に渡されるオブジェクトの型によらず、柔軟な対応が可能となる。