Open-Closed Principle
拡張に対して開いていること、修正に対して閉じていること
ソフトウェアの拡張性についての原則です。この原則には様々な解釈があります。
SOLID原則と呼ばれる、主要5原則のひとつ。
- Single Responsibility Principle 単一責任原則
- Open Closed Principle 開放閉鎖原則
- Liskov Substitution Principle リスコフ置換原則
- Interface Segregation Principle インターフェース分離原則
- Dependency Inversion Principle 依存性逆転原則
どんなときに使える?
拡張性を考えるとき
あとでこのクラス拡張するから、改修しやすくしとかないと…
というような場合ですが、この原則、正直使いどころが難しいです。
この原則の意味するところは?
開いている/閉じているとは?
意味するところは、
- 開いている コードに機能を拡張できること
- 閉じている 既存コードの修正をすることなく、コードの追加のみで拡張できること
一見、あたりまえ。コードを修正すれば機能追加できるし、他のところを直さなければよいし。
拡張とは何か?
プログラミングにおける「拡張」とは、プログラムを修正し、なにかの動作を加えること。
拡張のとき、修正箇所が少ないほうが、動作がおかしくなりにくい。(直していないところはそのまま動くわけだから)
この直す範囲を区切るのに、モジュールという考えがあります。ソースファイル、クラス、メソッド…とにかく意味的に機能を分割したものが、モジュールです。
モジュールA、モジュールB、があったとします。拡張するときに、モジュールAだけ直すので済むなら、モジュールBは前のまま動くわけです。
開放閉鎖原則が登場するとき
それならモジュールAとBを、うまく分けておくだけで、いいじゃない?と思います。
ただBの呼び方によって、こういう場合があります。
- とある場合によって、呼ぶモジュールBの種類を変えたい。
- しかもその条件が外から与えられる。
この場合、Bを呼ぶのはAだから、呼ぶものが変わればAも修正が必要。だけどAがやることは本質的に変わらないので変えたくない。
ポリモーフィズムの例
呼ばれるほうは変わるが、呼ぶほうは意識しないですむ仕掛け。ポリモーフィズムといいます。
例えば以下のような場合。
// 図形構造体(四角形や円)
struct Square
{
ShapeType itsType;
double itsSide;
Point itsTopLeft;
};
// 全ての図形を描画する
void DrawAllShapes(Shape* list[], int n)
{
int i;
for (i=0; i<n; i++)
{
struct Shape* s = list[i];
// 図形のタイプで分岐
switch (s->itsType)
{
case square:
// 四角形なら
DrawSquare((struct Square*)s);
break;
case circle:
// 円なら
DrawCircle((struct Circle*)s);
break;
}
}
}
DrawAllShapes は、Shapeの配列を受け、各々の描画を行います。
Shapeの型の情報を調べてはそれにふさわしい関数を呼び出します。(DrawCircle または DrawSquare)
いま、四角形と円がありますが、ここに三角形を追加するとします。するとDrawAllShapesに、三角形の図形描画を追加する必要あります。
続いて以下の例です。
// 図形クラス
class Shape
{
public:
virtual void Draw() const = 0;
};
// 矩形クラス
class Square : public Shape
{
public:
virtual void Draw() const;
};
// 円クラス
class Circle : public Shape
{
public:
virtual void Draw() const;
};
// 全ての図形を描画する
void DrawAllShapes(vector<Shape*>& list)
{
for (vector<Shape*>::iterator i=list.begin(),i!=list.end(); ++i)
(*i)->Draw();
}
こうすると、三角形を追加したときに、必要なことはShape クラスに対し三角形クラスを追加するだけです。
DrawAllShapes 関数そのものは変更する必要はないわけです。
この原則ははたして使えるのか?
…と、開放閉鎖原則では、たいていこのようなポリモーフィズムがメリットとしてあげられます。
これ、「なるほど!」と、「は?」と、意見が分かれるのでないでしょうか。
確かにDrawAllShapesを直さなくて済むのはわかる。でも、
直す前のほうが、やっていること分かりやすいし直しやすそう…
それに、将来に三角形を追加するといったって、まず絶対に四角か円しかないんだけど…という場合もあるのです。
そういう場合に、無理して直す必要あるのか?
そもそも将来の予測はかなり困難です。分かりもしない将来のために仕掛けつくるより、シンプルなほうがよい場合も多いです。YAGNI原則ですね。
まとめ
開放閉鎖原則は、オブジェクト指向を学んだ人には、ちょっと甘美な響きに聞こえます。
「コードを追加するだけで拡張できるんだぜ!オブジェクト指向の真骨頂だ!」
よいときももちろんあります。ただ「余計なコードを埋め込むための免罪符」になってしまうときもあります。
ほんとうにその拡張は必要なのか、よく考えて、適切な対応を決めましょう。
- Single Responsibility Principle 単一責任原則
- Open Closed Principle 開放閉鎖原則
- Liskov Substitution Principle リスコフ置換原則
- Interface Segregation Principle インターフェース分離原則
- Dependency Inversion Principle 依存性逆転原則
コメント