「継承を超える構成」は「ドライ原則」に違反していますか?


36

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

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;
}

だから私の質問は、「継承に対する組成」が「ドライ原則」に違反しているということです。


13
場合によっては、継承も構成も必要ありません。つまり、いくつかのオブジェクトを持つ1つのクラスが仕事をすることもあります。
エリックエイド

23
すべてのページをログインページにしたいのはなぜですか?たぶんログインページがあるだけです。
コチェッセ氏18

29
しかし、継承を使用すると、あちこちに複製ができますextends LoginPage。チェックメイト!
el.pescado

2
最後のスニペットに多くの問題がある場合、ゲッターとセッターを使いすぎている可能性があります。それらはカプセル化に違反する傾向があります。
ブライアンマックラッチン

4
したがって、ページを装飾できるようにしLoginPage、デコレーターを作成します。これ以上の複製は不要で、簡単page = new LoginPage(new EditInfoPage())です。完了です。または、open-closed-principleを使用して、任意のページに動的に追加できる認証モジュールを作成します。主に新しい抽象概念を見つけることを含む、コードの重複に対処する方法はたくさんあります。LoginPageおそらく悪い名前です。あなたが確認したいのは、そのページを閲覧している間にユーザーが認証され、LoginPageそうでない場合は適切なエラーメッセージにリダイレクトされるか表示されることです。
ポリノーム

回答:


46

繰り返しますか

public LoginPage loginPage;

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

int x;

コードベース全体の1つのオブジェクトにのみ存在できるようになりました。ブレー。

DRYは心に留めておくべき良いことですが、続けてください。ほかに

... extends LoginPage

あなたの代替で複製されているので、DRYについてアナルであってもこれは意味がありません。

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

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

ただし、同じコードを別の場所に配置する場合、その場所は異なる責任を負い、独立して変更する必要がある可能性が高いため、DRYの実施を緩和し、この同一の動作に異なるアイデンティティを持たせるのが賢明です。魔法の数字を禁止するのと同じ考え方です。

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

あなたが本当に文句を言っていると思うのは、定型コードと呼ばれます。はい、継承ではなく構成を使用するには定型コードが必要です。無料で公開されるものはありません。公開するコードを作成する必要があります。そのボイラープレートには、状態の柔軟性、公開されたインターフェイスを狭める能力、抽象化のレベル、適切なオールインダイレクションに適した別の名前を付ける機能があり、あなたは構成されているものを外部からではなく使用しています内部なので、通常のインターフェイスに直面しています。

しかし、はい、それは多くの余分なキーボード入力です。コードを読むときに継承スタックをバウンドさせるというヨーヨーの問題を防ぐことができれば、それだけの価値があります。

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

public class MyLoginPageWasNull extends NullPointerException{}

int x;コードベースでこれ以上のゼロ回以上存在することはできません
K.アラン・ベイツ

@ K.AlanBates すみません
candied_orange

...私はあなたがポイントクラスでレトルトすることを知っていました笑 誰もPointクラスを気にしません。コードベースにアクセスint a; int b; int x;して、「あなた」を削除するようにプッシュする場合は
K.

@ K.AlanBatesうわー。そのような振る舞いが良いコーダーになると思うのは悲しいです。最高のコーダーは、他の人についてのほとんどのものを見つけることができるものではありません。それが残りをより良くするものです。
candied_orange

意味のない名前を使用することは初心者ではなく、そのコードの開発者を資産ではなく負債にする可能性があります。
K.

127

DRY原則に関する一般的な誤解は、コードの行を繰り返さないことに何らかの関係があるということです。DRYの原則は、「すべての知識が、システム内で単一の明確な、権威ある表現を持たなければならない」です。コードではなく知識に関するものです。

LoginPageログイン用にページを描画するEditInfoPage方法を知っています。これを行う方法を知っていれば、それは違反になります。LoginPageビア構成を含めることは、DRY原則に決して違反しません

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


27
「単一責任の原則」はさらに誤用されています。「繰り返さないでください」は簡単に2番目に来るかもしれません:
gnasher729

28
「DRY」はキャッチフレーズである「Do n't Repeat Yourself」の便利な頭字語ですがが、原則の名前では、原則の実際の名前は「Once And Only Once」であり、これはキャッチーな名前です説明するためにいくつかの段落を必要とする原則のために。重要なことは、段落のカップルを理解されていない三文字D、R、およびY.記憶
イェルクWミッターク

4
@ gnasher729ご存知のように、SRPはおそらくもっと多く誤用されているということに、私は実際に同意します。公平であるとはいえ、多くの場合、それらは一緒に誤用されることが多いと思います。私の理論では、プログラマは一般に頭字語に「簡単な名前」を付けることを信頼すべきではありません。
wasatz

17
私見これは良い答えです。ただし、公平を期すために、私の経験では、最も頻繁に発生するDRY違反は、コピーペーストプログラミングとコードの複製が原因です。誰かが「コードをコピーして貼り付けるときはいつでも、複製された部分を共通の関数に抽出できない場合はよく考えてください」と言ったことがあります。
Doc Brown

9
コピーペーストの乱用は確かにDRYに違反する原動力ですが、単にコピーペーストを禁止することは、DRYをフォローするための不十分なガイダンスです。これらのメソッドが異なる知識を表す場合、同一のコードを持つ2つのメソッドを使用しても何も後悔はありません。彼らは2つの異なる責任を果たします。今日、たまたま同じコードを持っています。独立して自由に変更できる必要があります。はい、コードではなく知識です。よく置きます。私は他の答えの1つを書きましたが、私はこれに屈します。+1。
candied_orange

12

簡単な答え:はい、そうです-ある程度の、許容できる程度まで。

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

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

DRY原則の核心は、同じ論理ルールがエンコードされているコード内の2つの場所を避けることです-これらの論理ルールが変更されると、非DRYコードでは、それらの場所の1つを簡単に調整し、他の場所を忘れてしまうため、エラーが発生します。

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

あなたの例への注意:あなたHomePageとあなたのEditInfoPage見た目はわかりませんが、ログイン機能があり、HomePage(またはEditInfoPageがの LoginPage場合、継承はここで正しいツールかもしれません。構成がより明確な方法でより優れたツールとなる議論の余地のない例は、おそらく物事を明確にするでしょう。

仮定すると、どちらHomePageEditInfoPageありLoginPage、そしてあなたが書いたように、それは一つの一部だけを必要とすることはかなり可能性があるよりも1は、後者を再利用したいですLoginPage、ではないすべてのものを。その場合、示された方法で構成を使用するよりも良い方法があります

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

  • そのコンポーネントを再利用しHomePageEditInfoPage内部で現在使用さいるのと同じ方法でLoginPage

そうすれば、通常、継承を介した構成が正しいアプローチである理由と時期がより明確になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.