私は開閉の原則の意図を理解しています。変更せずに拡張するように指示することで、変更中にすでに機能しているものを壊すリスクを減らすことを目的としています。
しかし、私はこの原則が実際にどのように適用されるかを理解するのに苦労しました。私の理解では、それを適用するには2つの方法があります。 Beoforeおよび可能な変更後:
以前:できるだけ抽象化し、「未来を予測する」ようにプログラムします。たとえば、Motorcycle
sが将来システムに追加された場合、メソッドdrive(Car car)
を変更する必要があるため、おそらくOCPに違反しています。しかし、メソッドdrive(MotorVehicle vehicle)
は、将来変更される可能性が低いため、OCPに準拠しています。
ただし、将来を予測し、システムにどのような変更が加えられるかを事前に知ることは非常に困難です。
変更後:変更が必要な場合、現在のコードを変更するのではなく、クラスを拡張します。
プラクティス#1を理解するのは難しくありません。ただし、応募方法がわからないのはその2です。
例(YouTubeのビデオから取得しました):CreditCard
オブジェクトを受け入れるクラスにメソッドがあるとします:makePayment(CraditCard card)
。 1日Voucher
sがシステムに追加されます。このメソッドはそれらをサポートしていないため、変更する必要があります。
最初にメソッドを実装するときに、未来を予測してより抽象的な用語でプログラミングすることに失敗しました(例:makePayment(Payment pay)
)。このため、既存のコードを変更する必要があります。
実践#2では、変更ではなく拡張することで機能を追加する必要があります。 それはどういう意味ですか?既存のコードを単に変更するのではなく、既存のクラスをサブクラス化する必要がありますか?コードの書き換えを避けるためだけに、その周りに何らかのラッパーを作成する必要がありますか?
または原則は、「機能を正しく変更/追加する方法」に言及するのではなく、「最初に変更を加える必要がないようにする方法(つまり、抽象化へのプログラム)に言及しますか?
設計原則は常に相互にバランスを取る必要があります。あなたは未来を予測することはできません、そしてほとんどのプログラマーは彼らがしようとすると恐ろしくそれをします。そのため、 のルール があり、これは主に複製に関するものですが、他の設計原則のリファクタリングにも適用されます。
インターフェイスの実装が1つしかない場合、拡張が行われる場所が明確でない限り、OCPについてあまり気にする必要はありません。実際、この状況で過剰設計しようとすると、多くの場合、失われる明快さを失います。一度拡張すると、リファクタリングしてOCPフレンドリーにしますifこれが最も簡単で明確な方法です。これを3番目の実装に拡張する場合は、多少の労力が必要な場合でも、OCPを考慮して必ずリファクタリングしてください。
実際には、実装が2つしかない場合、3つ目を追加するときのリファクタリングは通常それほど難しくありません。維持するのが面倒になるのは、そのポイントを超えて成長させるときです。
私は開閉の原則の意図を理解しています。変更せずに拡張するように指示することで、変更中にすでに機能しているものを壊すリスクを減らすことを目的としています。
また、既存のオブジェクトの動作を変更しないことにより、そのメソッドに依存するすべてのオブジェクトを壊さないことも重要です。いったんオブジェクトが動作の変更を通知すると、他のオブジェクトがその動作を期待しているものを正確に知らなくても、オブジェクトの既知の予期される動作を変更するため、危険です。
どういう意味ですか?単に既存のコードを変更するのではなく、既存のクラスをサブクラス化する必要がありますか?
うん。
「クレジットカードのみを受け入れる」は、そのパブリックインターフェイスを介して、そのクラスの動作の一部として定義されます。プログラマーは、このオブジェクトのメソッドはクレジットカードのみを取ることを世界に宣言しました。彼女は、特に明確なメソッド名を使用せずに、それを行いました。システムの残りの部分はこれに依存しています。
それは当時は理にかなっていたかもしれませんが、今すぐ変更する必要がある場合は、クレジットカード以外のものを受け入れる新しいクラスを作成する必要があります。
新しい動作=新しいクラス
余談ですが-将来を予測する良い方法は、メソッドに指定した名前について考えることです。 makePaymentのような非常に一般的なサウンドメソッド名を、メソッド内で正確にどのような支払いができるかについて、特定のルールを持つメソッドに指定しましたか?それはコードのにおいです。特定のルールがある場合、これらはメソッド名から明確にする必要があります-makePaymentはmakeCreditCardPaymentにする必要があります。オブジェクトを初めて作成するときにこれを行うと、他のプログラマーから感謝されます。
あなたは未来を見つめすぎていると思います。オープン/クローズに準拠する柔軟な方法で現在の問題を解決します。
drive(Car car)
メソッドを実装する必要があるとしましょう。言語に応じて、いくつかのオプションがあります。
オーバーロードをサポートする言語(C++)の場合は、drive(const Car& car)
を使用します
後である時点でdrive(const Motorcycle& motorcycle)
が必要になる可能性がありますが、drive(const Car& car)
には影響しません。問題ない!
オーバーロードをサポートしていない言語(Objective C)の場合、-driveCar:(Car *)car
メソッドに型名を含めます。
後のある時点で-driveMotorcycle:(Motorcycle *)motorcycle
が必要になるかもしれませんが、これも干渉しません。
これにより、drive(Car car)
を変更できないようにすることができますが、他の種類の車両にも拡張できます。この最小限の将来計画により、今日の仕事を終わらせることができますが、将来自分を妨げることはありません。
あなたが必要とする最も基本的なタイプを想像しようとすると、無限の退行につながる可能性があります。セグエ、自転車、ジャンボジェットを運転したい場合はどうなりますか?人々が移動し、移動に使用するすべてのデバイスを説明できる単一の汎用抽象型をどのように構築しますか?