ウィキペディアは言う
"ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張のために開いている必要がありますが、変更のために閉じている必要があります"
Word functionsが目に留まり、メソッドのオーバーロードを作成することは、Open/Closedの原則の例と見なすことができるのかと思いますか?
例を挙げましょう。サービス層にメソッドがあり、ほぼ1000か所で使用されているとします。このメソッドはuserIdを取得し、ユーザーが管理者かどうかを判断します。
bool IsAdmin(userId)
ここで、userIdではなく、usernameに基づいて、ユーザーが管理者であるかどうかを判断する必要がある場所について検討します。上記のメソッドのシグネチャを変更すると、コードが1000か所で壊れてしまいます(関数は変更しないでください)。したがって、ユーザー名を取得するためのオーバーロードを作成し、ユーザー名に基づいてuserIdと元のメソッドを見つけることができます。
public bool IsAdmin(string username)
{
int userId = UserManager.GetUser(username).Id;
return IsAdmin(userId);
}
このようにして、関数のオーバーロードを作成することで関数を拡張しました(関数は拡張に対して開かれている必要があります)。
オープン/クローズの原理の例ですか?
私はこうしてwikiの記述をこうして個人的に解釈します。
ただし、足元のショットは、IsAdmin(int)が存在する 'cUserInfo'クラスをコードが使用している場合、基本的にルールを破ってクラスを変更していることです。悲しいことにこのルールは、新しいクラスcUserWithNameInfo:public cUserInfoクラスを作成し、そこにIsAdmin(string)オーバーライドを配置した場合にのみ保持されます。私がコードベースを所有しているなら、私はそのルールには従いません。私はbollocksと言い、提案された変更を行います。
まず、あなたの例は残念ながら不自然です。それが不必要な二重取得を引き起こすので、あなたは現実の世界では決してそれをしません。または、さらに悪いことに、useridとusernameがいずれかの時点で同じタイプになる可能性があるためです。のではなく
public bool IsAdmin(int userid)
{
User user = UserManager.GetUser(userid);
return user.IsAdmin();
}
public bool IsAdmin(string username)
{
int userId = UserManager.GetUser(username).Id;
return IsAdmin(userId);
}
あなたは本当にそのクラスをextendすべきです。
public bool IsAdmin(int userid)
{
User user = UserManager.GetUserById(userid);
return user.IsAdmin();
}
public bool IsUsernameAdmin(string username)
{
User userId = UserManager.GetUserByName(username);
return user.IsAdmin();
}
さらにリファクタリングして、一貫性を保つために最初のメソッド名をIsUserIdAdminに変更することもできますが、必須ではありません。重要なのは、新しいメソッドを追加して、呼び出しコードが壊れないことを保証することで、クラスが拡張可能であることがわかったということです。または言い換えると、拡張機能を開く。
そして、これが開閉の原理を持つものです。コードの一部を拡張して、代わりに変更しなければならない(それに伴うリスクの増加を伴う)まで、コードが実際に知っているコードがどれだけ適切に適合しているかはわかりません。しかし、経験があれば、予測することを学びます。
ボブおじさんが言う (私の強調):
閉鎖は完全ではないので、戦略的でなければなりません。つまり、デザイナーは、自分のデザインを閉じるための変更の種類を選択する必要があります。これには経験から得られたある程度の科学が必要です。経験豊富な設計者は、さまざまな種類の変更の可能性を判断するのに十分なほどユーザーと業界を理解しています。次に、最も可能性の高い変更に対してオープン/クローズの原則が呼び出されることを確認します。
開閉の原則に準拠するモジュールには、2つの主要な属性があります。
- それらは「Open For Extension」です。つまり、モジュールの動作を拡張できます。アプリケーションの要件の変化に応じて、または新しいアプリケーションのニーズを満たすために、モジュールを新しいさまざまな方法で動作させることができます。
- それらは「変更のため閉鎖されています」。このようなモジュールのソースコードは違反です。ソースコードを変更することはできません。
メソッドをオーバーロードすることで、既存のモジュールの機能を拡張し、アプリケーションの新しいニーズに対応します
既存のメソッドに変更を加えていないため、2番目の規則に違反していません。
元の方法を変更できないようにすることに関して他の人が言わなければならないことを聞きたいです。はい、適切なアクセス修飾子を配置してカプセル化を適用できますが、他に何ができますか?
開閉原理は目標であり、理想的なケースであり、常に現実であるとは限りません。特に古いコードをリファクタリングする場合、最初のステップはOCPを可能にするために頻繁に大幅な変更を行うことです。原則の根本は、機能するコードがすでに機能しており、変更するとバグが発生する可能性があるということです。したがって、最良のシナリオは、既存のコードを変更することではなく、新しいコードを追加することだけです。
しかし、BigContrivedMethod(int1, int2, string1)
という関数があるとします。 BigContrivedMethod
は、thing1、thing2、thing3の3つの処理を実行します。この時点で、BCMの再利用は多すぎるため、おそらく困難です。 (可能な場合は)ContrivedFunction1(int)
、ContrivedFunction2(int)
、およびContrivedFunction3(string)
にリファクタリングすると、より簡単に組み合わせることができる、より小さく焦点を絞った3つのメソッドが提供されます。
そして、それがメソッド/関数に関するOCPの鍵です:合成。他の関数から呼び出すことにより、関数を「拡張」します。
OCPは他の5つの原則、SOLIDガイドラインの一部です。最初の1つは重要な単一の責任です。コードベース内のすべてが必要な特定のことだけを実行した場合、コードを変更する必要はありません。新しいコードを追加するか、古い方法を新しい方法で組み合わせるだけで済みます。実際のコードはめったにガイドラインに適合しないため、OCPを取得する前に、SRPを取得するためにコードを変更する必要があることがよくあります。
古いコードを変更するのではなく、新しいコードを記述してプログラムの動作を変更する場合は、オープン/クローズの原則に従っています。
すべての可能な変更に対してオープンなコードを書くことはかなり不可能です(そして、分析麻痺に入るのでそうしたくありません)変更ではなく、拡張によって現在取り組んでいるすべての異なる動作に対応するコードを記述します。
新しい動作を許可するために何かを単に変更するのではなく、変更が必要な何かに遭遇した場合、変更のポイントを理解し、動作を変更せずにプログラムを再構築して、新しいコードを記述してその動作を変更できるようにします。 。
それで、これがあなたのケースにどのように適用されるか:
クラスに新しい関数を追加する場合、クラスは開いたり閉じたりしていませんが、オーバーロードしている関数のクライアントは開いています。
クラスで機能する新しい関数を単に追加する場合は、クラスと関数のクライアントの両方が開いたり閉じたりします。