オープンクローズ原理
:オブジェクト指向設計では、「実行時の型識別を使用して(RTTI「グローバル変数(グローバル変数)の使用を避けるために」など多くの人気のアイデア、「すべてのメンバ変数はプライベートに設定する必要があります(プライベート)」を持っていますファイル名を指定して実行時型識別は、例えば、dynamic_castをは)大変危険です「と。だから、これらのアイデアの源は何ですか?なぜ彼らは、この定義されたんですか?これらのアイデアは常に正しいですか?オープンクローズ原理(オープンクローズ原理):この記事では、これらのアイデアの基礎を説明します。
イヴァー・ヤコブソンは、かつて「すべてのシステムがある限り、システムがそれを心に留めておく必要があるだろう上記のバージョンを開発することであるとして、そのライフサイクルが異なる。」と述べました。
すべてのシステムは、そのライフサイクルの間に変化します。最初のバージョンよりも長く続くことが予想システムを開発する際にこれを念頭に置く必要があります。
だから、最終的にどのように我々は、ソフトウェアのライフサイクルがそう長く続くように、これらの変化の顔に安定したデザインを構築することができますか?
早ければ1988年のように、バートランド・メイヤーが提案に関する指針を与え、彼は非常に有名な現代的なオープンクローズドの原則を作成しました。「拡張されたオープンが、修正のため閉鎖に対処するためのソフトウェアエンティティ(クラス、モジュール、関数など)。」:彼の正確な言葉を適用します。
ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張のために開いている必要がありますが、変更のため閉鎖。
プログラムは変更が受けに変更を持っているより多くの依存カスケードモジュールになる必要がある場合は、その後、プログラムは、我々は「悪いデザイン(悪いデザイン)」と呼ぶものの特性が表示されますです。アプリケーションは、それに応じて、脆弱な剛性、予測不可能になり、再利用することができません。これらの問題が生じる解決しようとするオープンクローズ原理(オープンクローズ原理)は、あなたがモジュールの設計を変更することはありませんことを強調しています。要件が変更されたとき、あなたは新しいコードを追加することで、この行動モジュールを拡張することができ、よりむしろ既に缶作業を存在することを、コードを変更します。
オープンクローズ原理(オープンクローズ原理)説明
オープンクローズ原則とモジュールの対応は、2つの主要な機能を備えています。
1.彼らは「拡張のためのオープン(オープン用の拡張)」。
これは、モジュールの動作を拡張することができます。アプリケーションの要件が変更されると、我々は新たな需要を満たすために、過去の行動からモジュールのショーは、新規または異なることができます。
2.彼らは、「変更(クローズについては変形例)のため閉鎖します」。
モジュールのソースコードに違反することはできない、と誰もがソースコードを変更することが許されていません。
一般的に、従来の方法は、拡張モジュールのモジュールの動作を変更することであるので、二つの特徴の上に表示されるが、競合しています。モジュールは、一般的に固定された挙動を有すると考えられる変更することができません。それでは、どのようにこれらの2つの対向する性質がそれを共存させるのですか?
抽象化が鍵となります。
抽象化が鍵となります。
オブジェクト指向設計手法を使用するときは、抽象の固定セットを作成することができますし、表現するために無制限のコミュニティを行うことができます。要約は、本明細書に無限に多くの円を表すことができる派生サブクラスによって振る舞うことができる、抽象基底クラスを指します。改ざん抽象クラスのモジュールが可能であり、それは固定された抽象に依存しているため、このモジュールは、修正のために閉じることができます。次に、モジュールの動作は、抽象派生クラスを作成することによって拡張することができます。
例:クライアント/サーバーの参照
図1に示したシンプルなデザインは、オープンクローズドの原則に準拠していません。
(図1:閉じたクライアント)
クライアントとサーバーのクラスは、特定のカテゴリ(具象クラス)は、我々は、メンバ関数のサーバーが仮想であることを保証することはできません。ここでは、クライアントクラスは、Serverクラスを使用しています。我々は異なるクライアントオブジェクトServerオブジェクトを使用する場合は、新しいクライアントサーバークラスとオブジェクトを使用するクラスを変更する必要があります。
図2は、閉じた開放の原則に沿って、対応する設計。
(図2:オープン・クライアント)
この例では、AbstractServerクラスは抽象クラスであり、純粋仮想メンバ関数を含んでいます。Clientクラスは、抽象的に依存しているが、クライアントサーバーのオブジェクトクラスのインスタンスは、クラスを派生使用します。我々は、異なるターゲット・クライアント・サーバ・クラスを使用する必要がある場合、クラスは、Clientクラスの新しいサブクラスが変わらないAbstractServerから得ることができます。
例:抽象的な形状
次の例を考えてみましょう。私たちは、アプリケーションが標準のGUI画面上の円(サークル)と四角(スクエア)を描画する必要があります。丸と四角は、特定の順序で描画されなければなりません。丸と四角は同じリスト内に作成され、適切な順序を維持するために、プログラムは順番にリストをトラバースし、すべてのラウンドと四角形を描画することができなければなりません。
C言語では、技術の使用は、オープンクローズドの原則を満たすことができません。私たちは、次のコードに示すの方法によってこの問題を解決することができます。
enum ShapeType {circle, square};
struct Shape
{
ShapeType itsType;
};
struct Square
{
ShapeType itsType;
double itsSide;
Point itsTopLeft;
};
struct Circle
{
ShapeType itsType;
double itsRadius;
Point itsCenter;
};
void DrawSquare(struct Square*);
void DrawCircle(struct Circle*);
typedef struct Shape *ShapePointer;
void DrawAllShapes(ShapePointer list[], int n)
{
int i;
for (i=0; i<n; i++)
{
struct Shape* s = list[i];
switch (s->itsType)
{
case square:
DrawSquare((struct Square*)s);
break;
case circle:
DrawCircle((struct Circle*)s);
break;
}
}
}
ここでは、データ構造のセットの定義を参照してください、これらの構造は、その最初の要素を除いて同じですが、他は異なっています。最初の要素のタイプコードにより識別される。この構造は、円(サークル)又は正方形(スクエア)で表されます。次いでDrawAllShapes構造トラバーサル関数ポインタ配列、および関数呼び出し(またはDrawCircle DrawSquare)と一致するタイプ・コードを確認してください。
新しい形の種に閉じたままに保証されていないので、ここでは、関数DrawAllShapesは閉じを開くの原則に準拠していません。我々はそれがグラフィックリストをサポートし、三角形が含まれていることができるように定義されている、(トライアングル)この機能を拡張したい場合は、我々はこの機能を変更する必要があります。実際には、我々は数字の新しいタイプを描画する必要があるとき、私たちは、この機能を変更する必要があります。
もちろん、このプログラムはほんの一例です。練習DrawAllShapesでswitch文の機能は、様々な相互作用のアプリケーション内で継続されますコールに続き、各機能が少し異なっています。このようなアプリケーションの増加は、新形状検索のための必要性はすべて同様のswitch文を置く意味(または場合/他のチェーン)が存在し、新しいシェイプ機能を追加します。また、すべてのswitch文(または場合/他のチェーン)を作るために同様の機能DrawAllShapesを持っているような良い構造もほとんどありません。より多くの可能性が高い、とif文を一緒に結合する論理演算子の数、または積み重ねられたスイッチケース文の句です。だから、見つけると理解し、これらの問題をすべての位置で、その後、新しいグラフィック定義を追加することは簡単なことではありません。
次のコードに示すオープンクローズドの原則に沿ったソリューションCicle /スクエアの問題。
public abstract class Shape
{
public abstract void Draw();
}
public class Circle : Shape
{
public override void Draw()
{
// draw circle on GUI
}
}
public class Square : Shape
{
public override void Draw()
{
// draw square on GUI
}
}
public class Client
{
public void DrawAllShapes(List<Shape> shapes)
{
foreach (var shape in shapes)
{
shape.Draw();
}
}
}
この例では、我々がShape抽象クラスを作成し、抽象クラスは純粋仮想関数drawが含まれています。そして、それが由来とスクエアサークルShapeクラスれます。
私たちは行動DrawAllShapesは、新しいグラフィックタイプを描画するために機能拡張したい場合は、私たちがしなければならないことをここで注意は、Shapeクラスから派生したサブクラスを追加することです。DrawAllShapesは、あなたが変更する必要はありません機能します。したがってDrawAllShapesは、閉じた開放の原則に沿って、その動作を変更し、そのことによって拡張することができます。
より現実的なケースでは、Shapeクラスはメソッドの数が含まれていてもよいです。しかし、新しいグラフィックスアプリケーションを追加行う唯一の必要性は、これらの機能を実装するために派生クラスを作成することですので、まだ非常に簡単です。同時に、我々はもはや必要性は、アプリケーション内の位置を変更するために必要なすべてを見つけることができます。
変更なので、オープンクローズ原則に沿って、プログラムは、既存のコードを新しいコードを追加するのではなく、修正することにより、カスケード接続の変更の種類は、それが存在しないと、以前に記載されているものです。
戦略的な閉鎖(戦略的な閉鎖)
プログラムを理解するには、閉じられた、100%完全ではありません。私たちはサークルを決めた場合たとえば、上記の例をシェイプ想像し、最初にすべての広場、そして何が起こるかのDrawAllShapes関数の前に描画されるべき?DrawAllShapes機能は閉じておくために、このような変更は可能ではありません。一般的には、モジュールの設計は、これはクローズド打破する変化の多種多様が常にあるどれだけ近いかに関係なく話します。
したがって、それは完全に閉じするのは非現実的である、戦略に注意を払う必要があります。つまり、プログラマは、変更内容の閉スクリーニングを設計する必要があります。これは、いくつかの経験に基づく予測が必要です。経験豊富なデザイナーは、ユーザーと業界は様々な変化の可能性を決定するために場所を理解してよいでしょう。その後、閉じられた原則は変更する可能性が最も高い開いたままにするかを決定することができます。
要約表示が取得するために閉じ
どのように我々はDrawAllShapes機能は、それが閉じて保つの種類のロジックを変更描くのですか?閉鎖は抽象的に基づいていることを忘れないでください。したがって、ソート閉鎖のDrawAllShapesを作るために、我々は、抽象化のいくつかのレベルをソートする必要があります。上記の例では、グラフィック他のカテゴリの画像の前に描画される必要のある種の種類に関して特別な場合です。
配列決定戦略は、任意の2つのオブジェクトが最初に注目されるべきである見つけることができる所与あります。したがって、我々はそれがパラメータとして他の形状を受け入れ、bool型の結果を返すことができ、先行する形という名前のメソッドを定義することができます。結果が真である場合には、コールが先にパラメータとしてオブジェクトの形状のShapeオブジェクトを受信する必要があることを示しています。
私たちは、このような比較機能を実現するために、オーバーロードオペレータの技術を使用することができます。このように、私たちは、ソート順を描画することができる、その後の後、2つのShapeオブジェクトの相対的な順序を比較することで得られます。
次のショー実装が簡単であるコード。
public abstract class Shape
{
public abstract void Draw();
public bool Precedes(Shape another)
{
if (another is Circle)
return true;
else
return false;
}
}
public class Circle : Shape
{
public override void Draw()
{
// draw circle on GUI
}
}
public class Square : Shape
{
public override void Draw()
{
// draw square on GUI
}
}
public class ShapeComparer : IComparer<Shape>
{
public int Compare(Shape x, Shape y)
{
return x.Precedes(y) ? 1 : 0;
}
}
public class Client
{
public void DrawAllShapes(List<Shape> shapes)
{
SortedSet<Shape> orderedList =
new SortedSet<Shape>(shapes, new ShapeComparer());
foreach (var shape in orderedList)
{
shape.Draw();
}
}
}
このオブジェクトは、オブジェクトのソート形状に達した、と適切な順序でソートすることができます。しかし、我々はまだ抽象化の適切なものを持っていません。この本発明の場合、個人が順序を指定する先行Shapeオブジェクトのメソッドをオーバーライドしなければなりません。どのようにこの作品はだろうか?それが描画することができます前に、私たちは前にあるサークルスクエアを確保するためのコードを書くには何が必要?
public bool Precedes(Shape another)
{
if (another is Circle)
return true;
else
return false;
}
図から分かるように、この機能は、オープンクローズドの原則を満たしていません。それが派生した新しい形のサブクラスに閉じておくことができません。新しいShapeの派生クラスが作成されるたびに、このメソッドは常に変更されます。
使用「データ駆動部(データ駆動型)、」クローズを達成する方法
テーブル駆動(表駆動)方式は、各派生クラスを変更する強制することなく、派生Shapeクラスの閉鎖を達成することができます。
以下は、可能な設計を示しています。
private Dictionary<Type, int> _typeOrderTable = new Dictionary<Type, int>();
private void Initialize()
{
_typeOrderTable.Add(typeof(Circle), 2);
_typeOrderTable.Add(typeof(Square), 1);
}
public bool Precedes(Shape another)
{
return _typeOrderTable[this.GetType()] > _typeOrderTable[another.GetType()];
}
この方法を使用して、我々は最初に照合ドローように修飾されたポリシーを注文する新しいクラスまたはサブ形修飾(例えば、一般にDrawAllShapes機能スケジューリング問題のシール、および各派生Shapeクラスを維持することに成功しました広場)や他のは閉じたままです。
ここでは、まだ形の様々な注文する閉鎖テーブル(表)自体を維持することができません。しかし、我々は別のモジュールで、このテーブル定義を使用することができ、モジュールは、テーブルへの変更は、もはや他のモジュールに影響を与えませんのでこと、他のテーブルから分離されています。
さらなる拡大が閉じられています
これは物語の終わりではありません。
私たちは、Shape階層や形状ごとの異なるタイプの照合のための機能を閉じるDrawAllShapesを制御することができます。それにもかかわらず、形状が非閉パターン照合の種類を決定することなくクラスを派生しました。私たちがより高いレベルの構造に基づいてソート形状に願って見えるかもしれません。この問題の完全な研究は、この記事の範囲を超えていますが、興味のある読者は、実装する方法を検討することができます。このようなクラスがOrderedShape OrderedObjectに抽象クラスを保持し、形状やOrderedObjectクラスから独自の継承を実装することができますように。
概要
オープンクローズ原理(オープンクローズ原理)について、多くの缶話すがあります。多くの点では、この原則は、コアオブジェクト指向設計です。再利用性と保守性を:常にたとえば、オブジェクト指向技術から最大の利益を得るために継続する原則に従ってください。同時に、原理は達することができるオブジェクト指向プログラミング言語の使用が続いていません。むしろ、これらのプログラムの一部を変更する傾向がある抽象化する技術のアプリケーションの詳細を集中するために、プログラマが必要です。
参考資料