web-dev-qa-db-ja.com

さまざまなデータを必要とするオブジェクトのファクトリ/戦略パターン

EmailTemplatesを構築するためのファクトリーおよび/または戦略パターンの設計で、いくつかの問題に直面しています。 IMOこれは適切なデザインパターンのようですが、私が下に行くパスはあまり拡張可能ではなく、将来スパゲッティのようなコードになりがちになると思います。問題はこれです:EmailServiceは(ハイドレートされた)EmailTemplateを受け取ってメールを送信します。 EmailTemplateには、戦略(受領、払い戻し、無効など)に応じて異なるオブジェクト/データから派生した一連のキー/値ペアがあります。

これが私が現在取り組んでいるものです:

_public class EmailTemplateFactory {

private static Map<TemplateType, BiFunction<EmailTemplate, EmailTemplateContext, EmailTemplate>> templates = new HashMap<>();

static {
    templates.put(TemplateType.RECEIPT, EmailTemplateFactory::buildReceipt);
    templates.put(TemplateType.REFUND, EmailTemplateFactory::buildRefund);
    templates.put(TemplateType.VOID, EmailTemplateFactory::buildVoid);
}

public static EmailTemplate getTemplate(final EmailTemplateContext emailTemplateContext) {
    if (emailTemplateContext == null) {
        throw new IllegalArgumentException("Must provide context to build email template!");
    }
    return templates.get(emailTemplateContext.getTemplateType())
            .apply(getBaseTemplate(TemplateFormat.HANDLEBARS, emailTemplateContext), emailTemplateContext);
}

private static EmailTemplate buildReceipt(final EmailTemplate baseTemplate, final EmailTemplateContext emailTemplateContext) {
    //build the receipt template
    return new EmailTemplate();
}

private static EmailTemplate buildVoid(final EmailTemplate baseTemplate, final EmailTemplateContext emailTemplateContext) {
    // build the void template
    return new EmailTemplate();
}

private static EmailTemplate buildRefund(final EmailTemplate baseTemplate, final EmailTemplateContext emailTemplateContext) {
    // build the refund template 
    return new EmailTemplate();
}

private static EmailTemplate getBaseTemplate(final TemplateFormat templateFormat, final EmailTemplateContext emailTemplateContext) {
  // setting up common data between templates 
}

public enum TemplateType {
    RECEIPT, REFUND, VOID;
}
}
_

これらの異なる戦略がテンプレートを構築するために使用する複数の異なるデータを含むEmailTemplateContextと呼ばれるクラスを作成しました。ただし、これは面倒な感じがします。1つまたは2つ(5つまたは6つのうち)だけが任意のテンプレートを作成するために必要な、いくぶん無関係なフィールドがたくさんあるからです。ここでファクトリに渡されるコンテキストオブジェクトは正しいアプローチですか?

buildReceiptbuildRefund、およびbuildVoidメソッド内のコードのほとんどは、単にemailTemplate.addVariable(key, value)であり、それらのかなりの量が戦略間で共有されます( getBaseTemplateメソッドを作成した理由.

私を正しい方向に向けてくれる助けがあれば幸いです。ありがとう!

3
ungabunga

基本クラスEmailTemplateStrategy、純粋な仮想メソッドbuildTemplate、およびBuildReceiptTemplateStrategyBuildVoidTemplateStrategyBuildRefundTemplateStrategyなどの派生クラスを使用して、戦略パターンに「クラシック」アプローチを使用しないのはなぜですか。

これは技術的には現在のものと大差ありませんが、次の利点があります。

  • EmailTemplateContext型の保護されたメンバーは、基本クラスの一部にすることができます。コンストラクターを介して渡すか、基本クラスコンストラクター内に直接作成するだけです。したがって、BuildFooTemplateStrategyの呼び出し元/ユーザーは、各呼び出しで同じ引数を渡す必要はありません。

  • EmailTemplateStrategyは、一部の再利用可能なユーティリティメソッドにとって自然な場所です。これらはEmailTemplateContextに完全なAPIを提供できるため、このAPIを介してのみ、派生はコンテキストオブジェクトに直接アクセスしません。これは、EmailTemplateContextを構造化しすぎたと思われる場合に備えて、リファクタリングする機会を与えます。

  • 異なる戦略がより複雑になると、コードはより適切に編成されます:1つの巨大な神クラスEmailTemplateFactoryの代わりに、特定のテンプレートのコードを正確に含む複数のクラスを取得します。各クラスには、追加のプライベートヘルパーメソッドまたは追加のメンバー変数がある可能性があります。

(ここでOCPについて何かを追加し、拡張性を容易にしたいと思いましたが、ファクトリークラス内に新しいテンプレート構築関数を含めることはおそらく必須ではないので、現在のアプローチでもこれを可能にすると思います。 。)

もちろん、マップMap<TemplateType,EmailTemplateStrategy>を提供するためのファクトリクラスが必要ですが、このファクトリは、このマッピングの構築のためのコードのみを保持し、電子メールテンプレートのコードベース全体も保持しません。

注意してください、私が提案したものは、新しい要件を見込んで、数百行(またはそれ以上)のコードを含む成長するコードベースを目指しています。プログラムが3つまたは4つの異なるメールテンプレートしか提供しておらず、近い将来に予期される新しい要件がない場合は、私はそれをリファクタリングすることはありません。

3
Doc Brown