継承を使用してコードを再利用する場合、再利用の利点を飲み込むのが難しいと思いませんか?


8

私は約8年間コーディングを行ってきましたが、継承は柔軟性が高すぎるため、作成したコードと完全に混同されることがあります。最も簡単な例は次のとおりです。

abstract class AClass {
    protected void method1() {
        if(check()) {
            do1();
        } else {
            do2();
        }
    }
    protected abstract void do1();
    protected abstract void do2();
}

クラスの目的は、do1()とdo2()を実装してさらにロジックを実行できるようにすることですが、method1()をオーバーロードすることを決定すると、状況はすぐに複雑になります。

私は戦略パターンでのみコードを見つけます。コードは継承を通じてよく再利用されます。ほとんどの場合、基本クラスの設計者はそのサブクラスを非常によく知っており、その継承は完全にオプションです。

IoHandlerの4つのクラスによって継承されるクラスがあり、サーバーサイド、クライアントサイド、エッジサーバー、オリジンサーバーのサブクラスであり、私を狂わせ始めています。私はいつもコードのリファクタリングをしていました。うまくいくと思うアイデアを思いついたのですが、うまくいかなかったのです。人間の脳は一度に7つの情報しか保持できないと言われています。


1
特定のメソッドのオーバーロードを防ぐことができることを知っていますか?final[Javaを使用している場合] としてマークします。これにより、実質的に非仮想になり、過負荷になることはありません。この状況での使用に最適[テンプレートパターン]
ファレル

@Farrell実際、私はそれを「オーバーロードできるように集中的に保護されている」とマークしています。私がこれを行うとき、私はMFCからのいくつかのアイデアを使用していると思います。
tactoth、2011年

2
メソッドのオーバーロードは継承とは関係ありません。いくつかのコメント、そして受け入れられた回答でさえ、それを逐語的に繰り返すのは本当に奇妙です。確かにあなたはオーバーライドについて話しているのですか?
アーロンノート、

@Aaronaughtええ、あなたの修正は本当に役に立ちます:(
tactoth

回答:


3

このようなパターンを繰り返し続けると、それらはもうそれほどトリッキーに見えなくなります。

また、継承する前に、基本クラスについて知っておく必要があります。また、一般的なコードが再利用される限り、継承はオプションではありません。抽象ファクトリー/コンポジットのような他のパターンを取る-その継承は実際に目的を果たします。

経験則:一連のクラスをまとめるためだけに継承を使用しないでください。意味のある場所ならどこでも使用してください。不自然な例では、method1をオーバーロードしている子クラスのユーザーは、そうするために重大な理由を持っている必要があります。(OTOH、私は特定の機能だけをオーバーロードするのを防ぐ方法も欲しかった-これを避けるために)。そして、その子クラスを使用する前に、この癖についても知っておく必要があります。しかし、純粋な戦略パターンはそれを実行しません-実行する場合、それを非常にうまく文書化します。

あなたが実際のコード例を挙げれば、ここの人々があなたのためにそれをレビューするかもしれません。


答えをありがとう、継承は主にロジックを共有するために使用されると私はまだ思います、コードを共有できるときは共有する必要があるという原則を常に適用してきました、そして共有しないことに決めたとき、それはより明確になります。
tactoth '26年

コード例が大きすぎるため、30以上のクラスを整理する必要があります。
tactoth '26年

18

はい、それは非常にトリッキーになる可能性があります...そしてあなたは良い仲間です。

効果的なJavaの Joshua Blochは、継承の問題のいくつかをうまく捉えており、継承ではなく構成優先することを推奨しています。

継承はコードの再利用を実現する強力な方法ですが、それが常にジョブに最適なツールであるとは限りません。不適切に使用すると、ソフトウェアが壊れやすくなります。パッケージ内で継承を使用しても安全です。サブクラスとスーパークラスの実装は同じプログラマーの制御下にあります。拡張用に特別に設計および文書化されたクラスを拡張するときに継承を使用しても安全です(項目15)。ただし、パッケージの境界を越えて通常の具象クラスから継承するのは危険です。この本では、「継承」という言葉を使用して、実装の継承を意味しています(あるクラスが別のクラスを拡張する場合)。この項目で説明する問題は、インターフェースの継承には適用されません(クラスがインターフェースを実装する場合、またはあるインターフェースが別のインターフェースを拡張する場合)。

メソッドの呼び出しとは異なり、継承はカプセル化を解除します。言い換えれば、サブクラスは、適切な機能のためにスーパークラスの実装の詳細に依存します。スーパークラスの実装はリリースごとに変更される可能性があり、変更された場合、コードが変更されていなくてもサブクラスが壊れる可能性があります。結果として、スーパークラスの作成者が拡張する目的で特別にサブクラスを設計および文書化していない限り、サブクラスはそのスーパークラスと連携して進化する必要があります。


+1。また、このアイデアは、Block and Effective Javaだけではなく、ずっと以前から広く普及していることに注意してください。こちらをご覧ください
Josh Kelley

1

あなたの質問は曖昧で一般的すぎると思います。あなたが説明することは、継承の有無にかかわらず、複雑な問題のように聞こえます。だから私見はあなたがそれに苦労していることは完全に正常です。

もちろん、継承が仕事に最適なツールではない場合もあります(「継承よりも構成を優先する」という必須の参照;-)。しかし、注意して使用すると、私はそれが便利だと思います。あなたの説明から、継承があなたのケースに適切なツールであるかどうかを判断することは困難です。


これは漠然とした質問であり、設計の決定を容易にするためにいくつかのアイデアを集めることを目的としています。私は構成と継承を交互に切り替えてきました。
tactoth '26年


1

構成の継承は、変化のベクトルを狭めます。

ソリューションが実装されており、コードベースの300か所でAClassを使用しているとします。今度は、method1()呼び出しの45を変更して、do2()の代わりにdo3()メソッドが呼び出されるようにする必要があります。

IAnInterfaceにdo3()を実装し(インターフェースのすべての実装がdo3()メソッドを簡単に処理できることを願っています)、method1()呼び出しでdo1()またはdo3()を呼び出すBClassを作成し、45の参照をAClassと注入された依存関係の両方。

do4()、do5()、...にこれと同じ変更を実装するイメージ

または、次のようにすることができます...

interface IFactoryCriteria {
    protected boolean check();
}

public final class DoerFactory() {
    protected final IAnInterfaceThatDoes createDoer( final IFactoryCriteria criteria ) {
        return criteria.check() ? new Doer1() : new Doer2();
    }
}

interface IAnInterfaceThatDoes {
    abstract void do();
}

public final class AClass implements IFactoryCriteria {
    private DoerFactory factory;
    public AClass(DoerFactory factory)
    {
        this.factory = factory;
    }

    protected void method1() {
        this.factory.createDoer( this ).do();
    }

    protected boolean check() {
        return true;//or false
    }
}

次に、Doer1またはDoer2を返す2番目のファクトリを実装し、ファクトリインジェクションを45か所変更するだけです。AClassは変化せず、IAnInterfaceも変化せず、do4()、do5()などのより多くの変更を簡単に処理できます。


1
+1私はエドによって提案されたものよりも問題のこの実装が好きです。これにより、問題がより多くのSRPになるだけでなく、AClassの依存関係が制限されると思います。私の主な質問は、IAnInterfaceThatDoesをファクトリクラスではなくAClassに渡すだけの長所と短所だと思います。たとえば、適切なIAnInterfaceThatDoesを決定するロジックがファクトリの範囲外にある場合はどうなりますか?
dreza

IAnInterfaceThatDoesの公開を最小限に抑えるために、ファクトリを依存関係にしました。呼び出し元のコードがそのインターフェイスへのアクセスを必要とする場合、それを確実に移動できます。インターフェースの作成をファクトリーの外に移動したい状況を知りません。それを変更する必要がある場合は、createDoerメソッドを公開してDoerFactoryIインターフェースを実装することも別の方法だと思います。次に、DIフレームワークなどを使用してファクトリを実装できます。
ヒースリリー

0

なぜしない:

interface IAnInterface {
    abstract void do1();
    abstract void do2();
}

public final class AClass {
    private IAnInterface Dependent;
    public AClass(IAnInterface dependent)
    {
        this.Dependent = dependent;
    }

    protected void method1() {
        if(check()) {
            this.Dependent.do1();
        } else {
            this.Dependent.do2();
        }
    }
}

(ファイナルはC#の「封印」に相当すると思います。構文が正しくない場合は誰かが私を修正してください)。

依存性注入/ IoCは、実際に人々が変更できるようにしたいメソッドのみを継承できることを意味します。これで問題が解決すると思います。さらに、メインクラスが実際にdo1とdo2を実行するようには聞こえませんが、条件に基づいて呼び出しをサブクラスに委譲します:懸念の分離を壊しています!

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