web-dev-qa-db-ja.com

テンプレートデザインパターンは継承のために悪い習慣ですか?

コードでテンプレートデザインパターンを使用して、さまざまなリソースのCRUDプロシージャを実装しています。

多くのリソースで同じ手順と、追加/上書きが必要な手順があります。

基本クラスとしてのResourceCommonProcedures、および基本クラスを継承する各Resourceプロシージャ。

public class ResourceCommonProcedures { 

    public Response create(Request request){
        Response response = new Response();
        int resType = this.getResourceType(request);

        ResourceCommonProcedures rp = getResourceController(resType);
        response = rp.create(request);

        return response;
    }

private ResourceCommonProcedures getResourceController(int resourceType){
            //..

            switch(resourceTypeName){


            case Resource1:
                return new R1Procedures();

            case Resource2:
                return new R2Procedures();

            case Resource3:
                return new R3Procedures();
            ............... 

}
}

Class R1Procedures extends ResourceCommonProcedures{


public Response create(Request request) {

        Response response = new Response();
            Resource1 resource = (Resource1) request.getContent();

            ...........
            // Converts DB structure to JPA 
            R1Converter r1Converter = new R1Converter();
            Resource1JPA resourceJpa = r1Converter.toJPA(resource);
            dao.persist();

            return response;

}
}

Class R2Procedures extends ResourceCommonProcedures{


public Response create(Request request) {

        Response response = new Response();
            Resource1 resource = (Resource2) request.getContent();

            // SOME RESOURCE 2 SPECIFIC CODE
            ...........
            // Converts DB structure to JPA 
            R2Converter r2Converter = new R2Converter();
            Resource2JPA resourceJpa = r2Converter.toJPA(resource);
            dao.persist();

            return response;


}

Joshua BlochがEffective Javaの第3版で書いたように、

テンプレートメソッドパターン[..]の魅力ははるかに低くなります。現代の代替手段は、同じ効果を得るために関数オブジェクトを受け入れる静的ファクトリーまたはコンストラクターを提供することです。

私が書いている10〜15のリソースプロシージャでのコードの再利用は重要だと思います。継承のためにテンプレートデザインパターンを回避する必要がありますか?

1

継承自体は悪くありません。使い方次第です。誰かが悪いと言ったからといって技術を回避することは、意思決定を行うための良い方法ではありません。

ジョシュ・ブロックがどこから来たのかはわかりますが、彼が提案する代替案はまったく同じものではありません。テンプレートパターンは、特定のデザインを強化することを目的としています。これは、基本クラスの剛性を利用してこの目的を達成します。一方、静的ファクトリーまたはコンストラクターは、依存性注入手法と互換性があるため、柔軟性とデカップリングに重点を置いています。

言い換えると、彼らには2つの(やや異なる)目的があります。

特定のソフトウェア開発手法を使用するかどうかを決定するときは、他の人の意見の要約ではなく、特定のプロジェクトのコンテキスト内での相対的なメリットを評価してください。必要に応じて、それらの意見を参考にしてください。ただし、常に最終的な仲裁人になります。

さらに読む
テンプレートメソッドパターン
Javaコンストラクターvs静的ファクトリーメソッド
関数オブジェクト

8
Robert Harvey

あなたのResourceCommonProceduresは、テンプレートメソッドパターンの例ではないようです(それは、おそらく簡単な意味を除いて、操作の「テンプレート」を実際には定義していないため)。基本クラスは、識別パラメーター(ResourceCommonProcedures)に基づいて具体的なresourceType派生インスタンスを生成するファクトリーとして機能します。おそらく、この種のロジックはResourceCommonProceduresではなく、他のクラスにある必要があります。応答を作成し、どのオブジェクトが要求を処理するかを決定することは、おそらく異なる責任です(現在の設計が異なる場合、そのような問題を引き起こします。それだけのために変更しないでください)。

しかし、テンプレートメソッドパターンについて質問したので、考慮すべき点がいくつかあります。まず、パターンの目的は、いくつかの制約を適用するだけでなく、拡張ポイント(またはプラグインポイント)も提供する、操作のスケルトン(またはテンプレート)を定義することです。フレームワークではこの種のことがわかります(たとえば、それらが派生した基本クラスを提供し、特定のメソッドをオーバーライドする場合)、パターンは他のコンテキストでも表示される可能性があります。通常、基本クラスは拡張機能を認識していません(デフォルトの動作を提供するものを除いて)。

次に、この種の拡張性/構成可能性を実現する方法はいくつかあります。大まかに言うと、継承ルートを使用できます(Go4ブックで説明されています)1)、またはコンポジションを使用します。ここでの選択は、基本的に、拡張コードに提供する必要のある/必要とするインターフェイスの種類に関するものです(ただし、他のものがデザインにも影響を与える可能性があります)。構成インターフェースは一般により柔軟であり、ラムダ(またはむしろ、機能インターフェース)はそれらを表現するための便利な方法を提供します。

従来、コンポジションベースのアプローチでは、実装する拡張に必要なインターフェースを定義し、このインターフェースを介して依存関係を受け入れるように基本クラスを設計していました。より複雑なシナリオでは、いくつかの異なる依存関係に対して、異なる役割で複数のインターフェースを使用できます。これはより柔軟です(異なるオブジェクトをさまざまな役割に合わせて組み合わせたり、同じオブジェクトを複数の役割に挿入したりできるため)、多少の複雑さが伴い、間違いなく少し面倒です。機能インターフェースを使用して、基本的に各拡張ポイントの役割インターフェースを個別に(各副操作について)定義できます。ラムダを注入したり、他のクラス/オブジェクトの静的メソッドやインスタンスメソッドへの参照を挿入したりできます。また、言語に付属している標準的なメソッドがたくさんあります。

第3に、基本クラスの拡張インターフェイスの定義方法に関係なく、拡張は分離された、かなり独立したコードの平和である場合も、統合された穴の一部である場合もあります(少なくとも原則として、当然、いくつかのことは簡単です。一方のアプローチで、次にもう一方のアプローチで行います)。

効果的なJavaの章で言及している章では、標準の機能的インターフェースの使用を好むと主張していますが、継承よりも構成を優先することはよく知られているアドバイスと同じ精神で(ちなみに、Go4本1)-一般的な設計ヒューリスティックとして。これは、継承を使用してはならないという意味ではなく、決定する前に長所と短所を比較検討する必要があります。


1
2

OOの強力なセールスポイントの1つはカプセル化されることになっています。実装の詳細は隠され、メソッドシグネチャなどの形式のAPIのみがより広い世界に公開されます。しかし、そのカプセル化を解除する方法はよく知られています。メソッドが特定の方法で他の-抽象-メソッドを呼び出すなど、特定のアクションを実行することを保証します。突然、実装をそのパブリックAPIの一部にしたので、それを解除しました。メソッドのカプセル化。

基本クラスにメソッドがあり、そのメソッドの実装を、これから存在するすべての子クラスに密結合すると、さらに問題が発生します。その実装に変更を加えると、子クラスが壊れる危険があります。その子クラスは、基本クラスを作成した後、サードパーティなどによって作成された可能性があります。それを制御できない場合があります。バグを見つけた場合、そうすることによって他のコードを破壊するリスクがあるため、バグを修正できない場合があります。

この問題の組み合わせ:カプセル化を解除し、メソッドの実装を子クラスのメソッドに密結合することには、脆弱な基本クラスの問題という名前があります。しかし、それはまた別の名前を持っています:テンプレートパターン。テンプレートパターンは、上で説明したとおりに実行し、意図的に実行します。そのため、シングルトンおよびサービスロケータパターンと同様に、テンプレートパターンはアンチパターンの領域にしっかりと組み込まれています。

したがって、継承自体のためにテンプレートパターンを回避するべきではありませんが、継承の可能な最悪の使用法を完全に具体化しているため、それを確実に回避する必要があります。

1
David Arno

私はここでほとんどのレスポンダーに同意せず、テンプレートデザインパターンは継承のために優れたソリューションではないと主張しなければなりません。

ウィキペディアから:

テンプレートメソッドパターンは、動作のアルゴリズムのプログラムスケルトンを定義する動作設計パターンであり、サブクラスにいくつかのステップを延期します。

ここでの鍵はbehavioraldeferringです。これは、テンプレートパターンで実行できるすべてのことが、戦略パターンで実行できる(または変換されたと言うこともできる)ことを意味します。そして、よく知られているように、関数型プログラミングでは戦略パターンは存在しません(明示的ではありません)。そのようにすると、より柔軟になり、懸念がより顕著に分離されます。

どうして?

テンプレートパターンを使用すると、本当に1つ(または多くてもいくつか)の関数を記述したいだけであれば、実行に必要な基本クラスから他のすべてのロジックを継承する必要があります。

したがって、基本的には、プロセスの一部、つまり1つの関数呼び出しを指定するだけです。テンプレートでは、それをクラスにする必要がありますが、1つの関数として保持する方が理にかなっています。

IMO、templateで実行できることは何でも、strategyで実行できます。 strategyはよりクリーンであるため、常にそのようにする必要があります。

申し訳ありませんが、コードを投稿していません。それ自体が明確であることを願っています。

0
andras