web-dev-qa-db-ja.com

「相続をめぐる構成」は「乾燥の原則」に違反していますか?

たとえば、他のクラスが拡張するクラスがあるとします。

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

そしていくつかのサブクラス:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

実際、サブクラスにはオーバーライドするメソッドがありません。また、HomePageに一般的な方法でアクセスしません。つまり、次のようなことはしません。

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

ログインページを再利用したいだけです。しかし https://stackoverflow.com/a/53354 によると、LoginPageインターフェイスは必要ないため、ここでは継承を使用しないため、ここではコンポジションを優先する必要があります。

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

しかし、問題はここに来る、新しいバージョンでは、コード:

public LoginPage loginPage;

新しいクラスが追加されると複製されます。そして、LoginPageがセッターとゲッターを必要とする場合、より多くのコードをコピーする必要があります。

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

だから私の質問は、「相続をめぐる構成」が「乾式の原則」に違反しているかということです。

36
ocomfd

ええとあなたは繰り返すことを心配しています

public LoginPage loginPage;

2か所でDRYに違反していますか?その論理によって

int x;

現在、コードベース全体の1つのオブジェクトにのみ存在できます。ブレー。

DRYは覚えておくことは良いことですが、実行してください。しかも

... extends LoginPage

DRYについての肛門であっても、これは意味をなさないでしょう。

有効なDRY懸念事項は、複数の場所で定義されている複数の場所で必要な同一の動作に焦点を当てる傾向があり、この動作を変更する必要性が最終的に複数の場所での変更を必要とすることになります。1つの場所で決定を行い、それらを1か所で変更するだけでよく、1つのオブジェクトだけがLoginPageへの参照を保持できるということではありません。

DRYは盲目的にフォローされるべきではありません。コピーと貼り付けが適切なメソッドやクラス名を考えるよりも簡単であるために複製している場合は、おそらく間違っています。

しかし、同じ場所に別の場所に同じコードを配置したい場合は、その場所によって責任が異なり、個別に変更する必要がある可能性があるため、DRYの適用を緩和することをお勧めします。そして、この同一の振る舞いに異なるアイデンティティを持たせます。マジックナンバーを禁止するのと同じ考え方です。

DRYは、コードがどのように見えるかだけではありません。それは、心のない繰り返しでアイデアの詳細を広めないことであり、メンテナーが心のない繰り返しを使用して物事を修正することを強います。それはあなたが自分に無知な繰り返しが物事が本当に悪い方向に向かっているあなたの慣習であると自分に伝えようとするときです。

あなたが本当に不満を述べようとしているのは定型コードと呼ばれるものです。はい、継承ではなく構成を使用するには定型コードが必要です。無料で公開されるものはありません。公開するコードを記述する必要があります。そのボイラープレートには、状態の柔軟性、公開されたインターフェースを狭める機能、抽象化のレベルに適したさまざまな名前を付ける機能、優れたol間接参照、そして外部からではなく外部から構成されているものを使用しています内部なので、通常のインターフェースに直面しています。

しかし、はい、それは多くの余分なキーボード入力です。私が防ぐことができる限り ヨーヨーの問題 継承スタックを上下に跳ね返すコードは、それだけの価値があります。

今では、継承の使用を拒否しているわけではありません。私のお気に入りの用途の1つは、例外に新しい名前を付けることです。

public class MyLoginPageWasNull extends NullPointerException{}
46
candied_orange

DRYの原則に関する一般的な誤解は、コードの行を繰り返さないことに何らかの関係があるということです。 The DRYの原則は、知識は、システム内で単一の明確で信頼できる表現を持つ必要があります " 。これは知識ではなく、コードに関するものです。

LoginPageは、ログイン用のページを描画する方法を知っています。EditInfoPageがこれを行う方法を知っていると、違反になります。合成を介してLoginPageを含めることは、何らかの方法でDRY原則の違反です。notではありません。

DRY原理は、おそらくソフトウェアエンジニアリングで最も誤用されている原理であり、コードを複製しないための原理としてではなく、抽象的なドメイン知識を複製しないための原理として常に考えられるべきです。実際には、多くの場合、DRY=を正しく適用すると、あなたがコードを複製することになります。これは必ずしも悪いことではありません。 。

127
wasatz

短い答え:はい、それはあります-少し、許容できる程度まで。

一見すると、継承によってコード行が節約される場合があります。これは、「再利用するクラスにはすべてのパブリックメソッドと属性が1:1の方法で含まれる」という効果があるためです。したがって、コンポーネントに10個のメソッドのリストがある場合、継承されたクラスのコードでそれらを繰り返す必要はありません。構成シナリオで、これらの10個のメソッドのうち9個を再利用コンポーネントを通じて公開する必要がある場合、9個の委任呼び出しを書き留め、残りの1個を解放する必要があります。これを回避する方法はありません。

なぜこれが許容できるのですか?構成シナリオで複製されるメソッドを見てください。これらのメソッドはexclusivelyコンポーネントのインターフェースへの呼び出しを委任しているため、実際のロジックは含まれていません。

DRY原則の中心は、同じ論理ルールがエンコードされているコード内の2つの場所を避けることです-非論理コードでこれらの論理ルールが変更されるとこれらの場所の1つを適応させ、他の場所を忘れるのは簡単であり、エラーを引き起こします。

しかし、デリゲート呼び出しにはロジックが含まれていないため、これらは通常そのような変更の対象ではなく、「継承よりも構成を優先する」ときに実際の問題を引き起こしません。また、コンポーネントのインターフェイスが変更された場合でも、コンポーネントを使用するすべてのクラスで正式な変更が発生する可能性がありますが、コンパイルされた言語では、呼び出し元の1つを変更し忘れたときにコンパイラーから通知されます。

あなたの例へのメモ:HomePageEditInfoPageがどのように見えるかはわかりませんが、それらにログイン機能があり、HomePage(またはEditInfoPageis aLoginPageの場合、継承はここで正しいツール。議論の余地のない例では、構成がより明白な方法でより優れたツールになるため、おそらく物事がより明確になります。

HomePageEditInfoPageLoginPageではないと想定し、あなたが書いたように後者を再利用したいのですが、すべてではなくLoginPageの一部のみが必要である可能性が高いです。それが事実である場合、示されている方法でコンポジションを使用するよりも良いアプローチは

  • LoginPageの再利用可能な部分を独自のコンポーネントに抽出する

  • HomePage内で現在使用されているのと同じ方法で、EditInfoPageおよびLoginPageでそのコンポーネントを再利用します。

そうすることで、継承よりも構成が適切なアプローチである理由と時期が明らかになります。

12
Doc Brown