オープン/クローズの原則では、ソフトウェア エンティティ (クラス、モジュールなど) は拡張に対してオープンである必要がありますが、変更に対してはクローズされている必要があります。これは何を意味し、なぜそれが優れたオブジェクト指向設計の重要な原則なのですか?
15 に答える
これは、新しいコードを新しいクラス/モジュールに入れる必要があることを意味します。既存のコードは、バグ修正のためにのみ変更する必要があります。新しいクラスは、継承によって既存のコードを再利用できます。
オープン/クローズの原則は、新しい機能を導入する際のリスクを軽減することを目的としています。既存のコードを変更しないため、壊れないことが保証されます。メンテナンスコストを削減し、製品の安定性を高めます。
具体的には、OOP における設計の「聖杯」であり、(個々の設計を通じて、またはアーキテクチャへの参加を通じて) エンティティを十分に拡張可能にし、コードを書き直すことなく (場合によっては再コンパイルすることなく) 将来の予期しない変更をサポートします。 **)。
これを行う方法には、ポリモーフィズム/継承、合成、制御の反転 (別名 DIP)、アスペクト指向プログラミング、戦略、ビジター、テンプレート メソッドなどのパターン、および OOAD の他の多くの原則、パターン、および手法が含まれます。
** 6 つの「パッケージ原則」、REP、CCP、CRP、ADP、SDP、SAP を参照
DaveK より具体的に言えば、通常、追加の機能を追加したり、クラスの機能を変更したりする場合は、元のクラスを変更する代わりにサブクラスを作成することを意味します。このように、親クラスを使用する人は、後で変更することを心配する必要はありません。基本的に、それはすべて後方互換性に関するものです。
オブジェクト指向設計のもう 1 つの非常に重要な原則は、メソッド インターフェイスによる疎結合です。行いたい変更が既存のインターフェイスに影響を与えない場合は、変更しても問題ありません。たとえば、アルゴリズムをより効率的にするためです。オブジェクト指向の原則も常識によって和らげる必要があります:)
ソフトウェア エンティティは、拡張に対してはオープンである必要がありますが、変更に対してはクローズされている必要があります
つまり、クラスまたはモジュールは、そのまま使用でき、拡張でき、変更する必要がない方法で作成する必要があります。
Javascript の悪い例
var juiceTypes = ['Mango','Apple','Lemon'];
function juiceMaker(type){
if(juiceTypes.indexOf(type)!=-1)
console.log('Here is your juice, Have a nice day');
else
console.log('sorry, Error happned');
}
exports.makeJuice = juiceMaker;
別の Juice タイプを追加する場合は、モジュール自体を編集する必要があります。これにより、 OCP が壊れます。
Javascript の良い例
var juiceTypes = [];
function juiceMaker(type){
if(juiceTypes.indexOf(type)!=-1)
console.log('Here is your juice, Have a nice day');
else
console.log('sorry, Error happned');
}
function addType(typeName){
if(juiceTypes.indexOf(typeName)==-1)
juiceTypes.push(typeName);
}
function removeType(typeName){
let index = juiceTypes.indexOf(typeName)
if(index!==-1)
juiceTypes.splice(index,1);
}
exports.makeJuice = juiceMaker;
exports.addType = addType;
exports.removeType = removeType;
これで、同じモジュールを編集せずに、モジュールの外部から新しいジュース タイプを追加できるようになりました。
これは、脆弱な基本クラスの問題に対する答えです。基本クラスへの一見無害な変更が、以前の動作に依存していた継承者に意図しない結果をもたらす可能性があるということです。したがって、派生クラスが基本クラスによって定義された契約に従うように、依存したくないものをカプセル化するように注意する必要があります。そして、継承者が存在するようになると、基本クラスで何を変更するかに細心の注意を払う必要があります。
さまざまな概念を理解しやすくするために、質問を 3 つの部分に分解してみましょう。
オープンクローズ原則の理由
以下のコードの例を考えてみましょう。異なる車両は異なる方法で整備されています。したがって、 a にサービスを提供する戦略は a にサービスを提供する戦略とは異なるため、Bike
とには異なるクラスがあります。このクラスは、サービスのためにさまざまな種類の車両を受け入れます。Car
Bike
Car
Garage
剛性の問題
コードを観察し、新しい機能の導入に関して、クラスがどのようGarage
に硬直しているかを確認します。
class Bike {
public void service() {
System.out.println("Bike servicing strategy performed.");
}
}
class Car {
public void service() {
System.out.println("Car servicing strategy performed.");
}
}
class Garage {
public void serviceBike(Bike bike) {
bike.service();
}
public void serviceCar(Car car) {
car.service();
}
}
お気付きかもしれませんが、Truck
またはのような新しい車両Bus
が整備されるときはいつでも、 や のGarage
ようないくつかの新しいメソッドを定義するために を変更する必要がありserviceTruck()
ますserviceBus()
。つまり、Garage
クラスはBike
、Car
、Bus
、などの可能なすべての乗り物を知っている必要がありTruck
ます。したがって、変更に対してオープンであることにより、オープン/クローズの原則に違反します。また、新しい機能を拡張するには、クラスを変更する必要があるため、拡張用に開いていません。
オープン・クローズ原則の意味
抽象化
上記のコードの剛性の問題を解決するために、開閉原理を使用できます。つまり、Garage
クラスが認識しているすべての車両のサービスの実装の詳細を取り除いて、クラスを馬鹿にする必要があります。言い換えれば、 や などの具体的な型ごとに、サービス戦略の実装の詳細を抽象化する必要がBike
ありCar
ます。
さまざまなタイプの車両のサービス戦略の実装の詳細を抽象化するために、interface
呼び出された を使用し、その中にVehicle
抽象メソッドservice()
を含めます。
ポリモーフィズム
同時に、 とだけでなく、などの多くの形式のビークルGarage
を受け入れるクラスも必要です。そのために、オープン/クローズの原則ではポリモーフィズム(多くの形式) を使用します。Bus
Truck
Bike
Car
Garage
クラスが の多くの形式を受け入れるようにするには、Vehicle
そのメソッドのシグネチャを などの実際の実装ではなくインターフェイスを受け入れるように変更します。service(Vehicle vehicle) { }
また、1 つのメソッドだけで多くの形式を受け入れるため、クラスから複数のメソッドを削除します。Vehicle
Bike
Car
interface Vehicle {
void service();
}
class Bike implements Vehicle {
@Override
public void service() {
System.out.println("Bike servicing strategy performed.");
}
}
class Car implements Vehicle {
@Override
public void service() {
System.out.println("Car servicing strategy performed.");
}
}
class Garage {
public void service(Vehicle vehicle) {
vehicle.service();
}
}
開閉原則の重要性
修正のため休業
上記のコードでわかるように、Garage
クラスはさまざまなタイプの車両のサービス戦略の実装の詳細を認識せず、任意のタイプの new を受け入れることができるため、変更に対して閉じられていますVehicle
。インターフェイスから新しい車両を拡張し、Vehicle
に送信するだけGarage
です。それでおしまい!Garage
クラスのコードを変更する必要はありません。
変更のために閉じられている別のエンティティは、Vehicle
インターフェイスです。ソフトウェアの機能を拡張するためにインターフェイスを変更する必要はありません。
延長可能
クラスは、変更する必要なく、Garage
新しいタイプの をサポートするというコンテキストで拡張できるようになりました。Vehicle
新しいVehicle
車両を導入するために、インターフェイスから拡張し、Vehicle
その特定の車両にサービスを提供するための戦略を備えた新しい実装を提供できるため、インターフェイスは拡張可能です。
戦略設計パターン
戦略という言葉を複数回使用したことに気づきましたか? これは、ストラテジー デザイン パターンの例でもあるためです。Vehicle
それを拡張することで、さまざまなタイプの s にサービスを提供するためのさまざまな戦略を実装できます。たとえば、 のサービスにTruck
は、 のサービスの戦略とは異なる戦略がありBus
ます。したがって、これらの戦略をさまざまな派生クラス内に実装します。
戦略パターンにより、時間の経過とともに要件が変化しても、ソフトウェアは柔軟になります。クライアントが戦略を変更するたびに、新しいクラスを派生させて既存のコンポーネントに提供するだけで、他のものを変更する必要はありません! 開閉原理は、このパターンを実装する上で重要な役割を果たします。
それでおしまい!それが役立つことを願っています。
OCP に準拠するための追加の経験則は、派生クラスによって提供される機能に関して基本クラスを抽象化することです。または、スコット・マイヤーズが「リーフ以外のクラスを抽象化する」と言っているように。
これは、基本クラスに実装されていないメソッドがあり、これらのメソッドをそれ自体がサブクラスを持たないクラスにのみ実装することを意味します。次に、基本クラスのクライアントは、基本クラスの特定の実装に依存できません。
この原則は、既存の安定したテスト済みの機能を変更することなく、新しい機能を簡単に追加できることを意味し、時間とお金の両方を節約します。
多くの場合、インターフェイスを使用するなどのポリモヒズムは、これを実現するための優れたツールです。
「Open/Closed」は、OO プログラミングでは明らかに有用ですが、開発のあらゆる面で使用する健全な方法であることを強調したいと思います。たとえば、私自身の経験では、プレーンな C で作業するときに「Open/Closed」を可能な限り使用すると、非常に鎮痛剤になります。
/ロバート
私は最近、この原則が何を意味するかについての追加のアイデアを与えられました: Open-Closed Principle は、コードを書く方法と、回復力のある方法でコードを書くことの最終結果を同時に説明しているということです。
私は、Open/Closed が密接に関連する 2 つの部分に分かれていると考えるのが好きです。
- 変更可能なコードは、その動作を変更して入力を正しく処理するか、最小限の変更で新しい使用シナリオを提供できます。
- 変更がクローズされているコードは、新しい使用シナリオを処理するために人間の介入があったとしても、あまり必要ありません。必要性は単に存在しません。
したがって、Open/Closed 動作を示す (または、必要に応じて、Open/Closed 原則を満たす) コードは、当初の目的以外の使用シナリオに応じて、最小限の変更またはまったく変更を必要としません。
実装に関する限り?一般的に言われている「オープン/クローズとは、コードが多態的であることを意味する!」という解釈です。せいぜい不完全なステートメントである必要があります。コードのポリモーフィズムは、この種の動作を実現するための 1 つのツールです。継承、実装...実際、すべてのオブジェクト指向設計原則は、この原則が暗示する方法で回復力のあるコードを記述するために必要です。
これは、OO ソフトウェアを構築する必要がありますが、本質的に変更してはならないことを意味します。これは、基本クラスからの信頼性の高い予測可能なパフォーマンスを保証するため、優れています。