SOLIDのOCPは、抽象型を介して定義された同じインターフェースのセットがある場合に適用できるため、具象型を介してこれらのインターフェースのさまざまな実装を持つことができると理解しています。
たとえば、ビジネスロジックでIRepositoryを操作して、定義された一連の操作を実行します。
interface IRepository<T>{
void save(T item);
T Get(int Id);}
新しいタイプのリポジトリをサポートするようにアプリケーションを拡張することが可能です。たとえば、コードのビジネスロジックを変更することなく、SQLRepository
からOracleRepository
に変更できます。
ただし、Delete
機能を提供する必要がある場合-OCPに準拠するために役立つ設計がわからない場合。
削除機能を提供する別のインターフェースを使用することもできます。結局のところ、everythingではなく、削除機能が必要です。これは、インターフェース分離の原則でもうまく機能します。新しいインターフェイスは2つを組み合わせたものにすることができ、ユーザーはそのすべての機能が必要な場合に要求できます。言語に特性/ミックスインがある場合は、削除機能を分離して記述し、混合することができます。
しかし、ほとんどの場合それはばかげています。あなたは利益をもたらさない抽象化をするでしょう。あなたはまだ問題ではなく、おそらく問題になることはないであろう問題を解決しています。あなたはあなたのコードを推論することを困難にし、維持することを困難にします。ほとんどの場合、削除メソッドを追加するだけで、OCPは非難されます。
開閉原理とその類縁はガイドラインです。彼らは人々をより良いソリューションに導くのを助けるためにそこにいます。しかし、より良い解決策は、いくつかのルールを守ることではなく、目標です。
要件は、型が存在することをまったく知らないはずです。
彼らはタイプを見たことがなかったはずなので、あなたの顧客はあなたにタイプに何かをするように頼むことは決してありません。
それはあなたがあなたの新しいことをするために完全に異なるタイプを追加することができると言いました。古いものと新しいものの両方を行う新しいものを作成できます。新しいものを追加するために古いものを壊す必要はありません。
それが、オープンクローズの原則が述べようとしていることです。最も難しいのは、新しいものに良い名前を付けることです。
これを拡張する最も簡単な方法は、次のようなものにすることです。
_interface IRepository<T>
{
void save(T item);
T Get(int Id);
void delete(int id);
}
_
または:
_interface IRepository<T>
{
void save(T item);
T Get(int Id);
}
interface IRepository2<T>
: IRepository<T>
{
void delete(int id);
}
_
最初の例はおそらく最も直接的なアプローチです。すべてのプロバイダーがこの動作をサポートする必要があることを単に義務付けます。それは推定します:
delete()
を提供することは理にかなっています。追加専用のログなど、これがあまり意味のないプロバイダーもあるかもしれません。2番目の例は、はるかに謙虚です。ほとんどの場合、現在のコードベースをそのままにし、下位互換性を維持しながら、プロバイダーがより豊富なサービスを提供できるようにします。コードベースの複雑さが増すという犠牲が伴います。これは、次のいずれかの状況の場合です。
delete()
が意味をなさないプロバイダーがあり、2番目の例の命名はひどいです-私はあなたのシステムについて十分に知りません。合理的または可能な場合は、IRepository
と_IRepository2
_の名前を、それぞれの役割を反映する名前に変更することをお勧めします。
IRepository
-> IAppendRepository
IRepository2
_-> IRepository
これは、広範囲にわたる影響を与える可能性がありますが、マイナーなリファクタリングにすぎません。特に、これが多くのアプリケーションで参照されるライブラリにある場合。おそらく、名前の明瞭さを向上させながら、名前の衝突を防止する飛石リリースを実行する必要があります。
IRepository
-> IAppendRepository
IRepository2
_-> IRepositoryWithDelete
インフラストラクチャと他の誰もが信頼するコアコードの構築に全力を注ぐAPIおよびフレームワークの開発者は、後方互換性を壊さないように特に注意する必要があります。彼らが彼らのデザインを尊重することを確認することは彼らの仕事です。
新しい操作を追加すると、たとえそれらがロジックを壊さなくても、APIコンシューマーを壊す可能性があります。例、プラグイン、リフレクション、依存関係の注入、およびその他の多くのものは、新しく変更されたインターフェースがロードされたものと同じではなくなったことを検出し、セキュリティまたはロードの例外をスローします。
この特別な注意が必要な場合は、通常、新しいメソッドで新しいインターフェースを作成し、古いインターフェースを作成して新しいインターフェースを拡張します。ドキュメントでは、既存のインターフェースが新しいもので「改良」されたことをユーザーに警告できます。それは頭を下げるはずです、そしてそれは通常、DI、反射などのようなもので穏やかです。
ただし、インターフェイスのコンシューマーを含むコード全体の所有者である場合、元のインターフェイスにメソッドを追加してすべての依存コードを更新することは簡単です。それはより簡単で、「インターフェースの分離原理を維持する限り」「専門的でない」と考えるべきではありません。
これはOpen-Close Principle(OCP)についてではなく、SOLIDにおけるInterface Segregation Principle(ISP)についてのすべてです。
[〜#〜] isp [〜#〜]新しい関数は既存のコードに影響を与えないはずですこの関数は必要ありません。簡単に言うと、A
クラスのコードがあり、不要な場合は新しい関数を追加してコードを妨害しません。
Log
およびCustomer
クラスがあると仮定します。 Log
クラスはDelete
関数を必要としませんが、Customer
は必要です。この場合、Delete
関数をIRepository<T>
に追加すると、Log
クラスはこの関数を必要とせず、この冗長な関数を強制的に追加するため、ISPに違反します。
すべての関数を持つIRepository<T>
が必要な場合は、Log
クラスがこのインターフェイスまたは別のインターフェイスを実装する必要があることを再検討してください。
[〜#〜] ocp [〜#〜]は、コードを変更せずにコードを作成することを意味します(close)がopenです。新しい機能を追加します。
以下のSomeEnum
とSomeClass
を確認してください。
public enum SomeEnum
{
Type1,
Type2
}
public class SomeClass
{
public void SomeFunction(SomeEnum someEnum)
{
if(someEnum == SomeEnum.Type1)
{
// do something
}
else
{
// do another thing.
}
}
}
それ以外の場合はelseステートメントを使用します。この場合、SomeFunction
のelseステートメントはSomeEnum.Type2
を表します。そのようなコードを書く場合、Type3
をSomeEnum
に追加するとこの関数に影響するため、これはnot closeです。この問題を回避するには、if-elseステートメントを変更するか、switch-caseステートメントを使用します。新しいものを追加するのもopenです。
public class SomeClass
{
public void SomeFunction(SomeEnum someEnum)
{
switch(someEnum)
{
case SomeEnum.Type1:
// do something for Type1
break;
case SomeEnum.Type2:
// do something for Type2
break;
// case SomeEnum.Type3:
// open to adding type 3. Even don't add, there is no error or
// thing violates business rule.
// do something for Type3
// break;
}
}
}
結論、OCPに違反していない、それは実際にはISPであり、IRepository<T>
を実装するすべてのクラスがDelete
関数を持つ必要がある場合は、IRepository<T>
に実装するか、そうでない場合はDelete
関数を持つ新しいインターフェイスを作成し、IRepository<T>
を実装するそしてそれを使用してください。