7

次の不自然なコードがあるとします。

abstract class Root
{
  public abstract void PrintHierarchy();
}

class Level1 : Root
{
  override public void PrintHierarchy()
  {
    Console.WriteLine("Level1 is a child of Root");
  }
}

class Level2 : Level1
{
  override public void PrintHierarchy()
  {
    Console.WriteLine("Level2 is a child of Level1");
    base.PrintHierarchy();
  }
}

クラスだけを見ていると、それがそれ自体で何かを実行し、オーバーライドしている基本メソッドを呼び出すため、オープン/クローズの原則に従っているLevel2ことがすぐにわかります。Level2.PrintHierarchy

ただし、Level1クラスだけを見ると、呼び出されないため、OCPに違反しているように見えますbase.PrintHierarchy。実際、C#では、コンパイラは「抽象ベースメンバーを呼び出せません」というエラーでクラスを禁止しています。

Level1OCPに従っているように見せかける唯一の方法はRoot.PrintHierarchy、空の仮想メソッドに変更することですが、そうすると、実装する派生クラスを強制するためにコンパイラーに依存できなくなりますPrintHierarchy

ここでコードを維持しているときに私が抱えている本当の問題は、をoverride呼び出さない多数のメソッドが表示されることですbase.Whatever()base.Whateverが抽象である場合は問題ありませんが、そうでない場合は、Whateverメソッドが具体的なオーバーライド可能なメソッドではなく、インターフェイスにプルされる候補になる可能性があります。または、クラスまたはメソッドを他の方法でリファクタリングする必要がありますが、ちなみに、それは明らかにデザインが悪いことを示しています。

抽象的であることを覚えRoot.PrintHierarchyたり、中にコメントを入れたりする以外に、のように形成されたクラスがOCPに違反しLevel1.PrintHierarchyているかどうかをすばやく特定する他のオプションはありますか?Level1


コメントにはたくさんの良い議論があり、いくつかの良い答えもあります。私はここで何を尋ねるべきかを正確に理解するのに苦労していました。@Jon Hannaが指摘しているように、仮想メソッドが単に「実装する必要がある」と示す場合もあれば、「拡張する必要がある」という意味の場合もあります。ベースバージョンを呼び出せない場合は、私のデザインを壊してください!」ただし、C#は、抽象またはインターフェイスが明らかに「実装する必要がある」状況であることを除いて、どちらを意味するかを示す方法を提供していません。(コードコントラクトに何かがない限り、ここでは少し範囲外だと思います)。

ただし、言語に実装必要なデコレータと拡張が必要な​​デコレータがある場合、無効にできない場合は、単体テストで大きな問題が発生する可能性があります。そのような言語はありますか?これは契約による設計のように聞こえるので、たとえばエッフェルにあったとしても驚かないでしょう。

最終結果はおそらく@Jordãoが言うように、そしてそれは完全に文脈的です。しかし、私はまだ答えを受け入れる前に、しばらく議論を開いたままにするつもりです。

4

3 に答える 3

5

Rootは以下を定義します: ルート オブジェクトには PrintHierarchy メソッドがあります。PrintHierarchy メソッドについては、それ以上何も定義していません。

Level1PrintHierarchy メソッドがあります。PrintHierarchy メソッドを停止しないため、オープン/クローズの原則に違反することはありません。

ここで、さらに要点を説明します。「PrintHierarchy」の名前を「Foo」に変更します。Level2 は、オープン/クローズの原則に従っていますか、それとも違反していますか?

答えは、「Foo」のセマンティクスが何であるかがわからないため、手がかりがないということです。したがって、メソッド本体の残りの部分の後、残りの部分の前、途中で base.Foo を呼び出す必要があるか、またはまったく呼び出さないかはわかりません。

1.ToString()「System.ObjectSystem.ValueType1」または「1System.ValueTypeSystem.Object」を返して、オープン/クローズ時にこのふりを維持する必要がありますか?それとも、「1」を返す前に未使用の変数に呼び出しを割り当てる必要がありますbase.ToString()か?

明らかに、これらのどれもありません。可能な限り意味のある文字列を返す必要があります。その基本型は可能な限り意味のある文字列を返しますが、それを拡張してもその基本型への呼び出しの恩恵を受けません。

オープン/クローズの原則とは、Foo() を呼び出すときに何らかの Fooing が発生することを期待し、Level1 で呼び出すと Level1 に適切な Fooing を期待し、Level2 で呼び出すと Level2 に適切な Fooing を期待することを意味します。Level2 Fooing に Level1 Fooing が含まれるかどうかは、Fooing が何であるかによって異なります。

baseクラスを拡張するのに役立つツールであり、要件ではありません。

于 2010-10-07T17:11:23.163 に答える
3

システム (またはクラス) が OCP に従っているかどうかは、静的に調べただけでは判断できず、コンテキスト情報がありません。

設計にどのような変更が加えられる可能性があるかを知っている場合にのみ、それらの特定の種類の変更について OCP に従っているかどうかを判断できます。

それを助けることができる言語構造はありません。

適切なヒューリスティックは、Robert Martin の不安定性と抽象性 (pdf)のような何らかのメトリクス、またはシステムの変化に対する準備をより適切に行うための結束の欠如のメトリクスを使用し、OCP およびその他すべてに従う可能性を高めることです。 OOD の重要な原則。

于 2010-10-08T14:07:08.650 に答える
1

OCPは、すべて前提条件と事後条件に関するものです。「前提条件を弱いものにのみ置き換え、事後条件を強いものに置き換えることができる場合」。オーバーライドされたメソッドでbaseを呼び出すことは、それに違反するとは思いません。

于 2011-12-03T23:42:05.653 に答える