[2013-12-05]
オブジェクト指向設計における原則のひとつ。
ソフトウェアの構成要素は、拡張に対して開いていて、修正に対して閉じていなければならないという原則。
(この定義を理解しようとする必要はない。意味が分からなくて当然だ)
ある種類の変更がかかったとき、
モジュールやクラス単位について、
1行たりともコードを変更せずに、新しい動きをさせることができなければならないという原則。
( 1行たりともコードを変更しない、という部分が「修正に対して閉じている」にあたる )
( 新しい動きをさせることができる、という部分が「拡張に対して開いている」にあたる )
OCP原則は「変更の種類 × モジュールやクラス単位」ごとに満たされているかどうかが決まるものである。
class Shape
draw:-> console.log "draw #{@shapeName}!"
class Circle extends Shape
constructor:-> @shapeName = "circle";
class Square extends Shape
constructor:-> @shapeName = "square";
class ShapeDrawer
draw:(shapeList)->
for shape in shapeList
shape.draw()
main =->
drawer = new ShapeDrawer()
drawer.draw [new Circle(), new Square()]
main()
このコードは、新たに
class Triangle extends Shape
constructor:-> @shapeName = "triangle";
を追加して、mainを
main =->
drawer = new ShapeDrawer()
drawer.draw [new Circle(), new Square(), new Triangle()]
と変更しても ShapeDrawer には1行たりともコード変更をしなくてよい。 そして、ShapeDrawer は "draw triangle!" という文字を出力するという新しい動きをさせることが出来るようになっている。
このとき、「Shapeを継承したクラスが増えるという変更種類」に対して「ShapeDrawerクラス」はOCP原則を満たしていると言える。
原則の定義に「~なければならない」と書いてあるが、 上のコードを見てわかるようにOCP原則を、全ての変更種類、全てのモジュールやクラスについて満たすことは不可能である。
OCP原則はどこに対してそれを満たすかを選択するための概念であって、できるだけ多くにその原則を適用すること目的としたものではない。
例えば、上のコード例だと図形がいろいろ追加されるのであれば、 「Shapeを継承したクラスが増えるという変更種類」に対してOCP原則を満たしていることはメリットになる。
しかし、「Shapeを継承したクラスが増えるという変更種類」がほとんど無く、 代わりにデコレーションの種類(例えば、"《circle》"だとか"{{circle}}"みたいな出力をしたい)がいろいろ追加されるのであれば、 「Shapeを継承したクラスが増えるという変更種類」に対してOCP原則を満たしていることは、全くメリットにならない。 それどころか、無駄に設計の複雑性を上げてしまい他の変更を行う妨げになってしまうという意味で、デメリットになる。
OCP原則を適用する箇所は、設計者が適切に決めなければならない。
この原則が本当に言いたいことは、
「頻繁に修正がかかる変更種類」「それを抽象的に扱ったほうが、単純になるクラス」に対して
OCP原則を満たすように設計するべきである、ということである。
オープン・クローズドの原則(OCP)はオブジェクト指向設計の核心である。 この原則に適切に従うことで、オブジェクト指向技術から得られる利益(柔軟性、再利用性、保守性)を享受できる。
オブジェクト指向のプログラミング言語を使えば自動的にOCPに準拠できるわけではない。 また、アプリケーションのあらゆる部分で抽象を無闇に使えばいいわけでもない。(無駄なコストを支払う必要が発生する) 最も頻繁に変更されるプログラム部分にだけに的を絞って、抽象を適用するように努めるべきである。
つまり、早まった「抽象」をしないことも、「抽象」を使うのと同等に重要。