首页 > 解决方案 > 如何为需要不同渲染参数的不同类型的电子邮件构建电子邮件请求类?

问题描述

目前,我的通知请求是这样的:

public class EmailRequest{
  public enum EmailType{
    TYPE_1,
    TYPE_2,
    ...
  }
  EmailType emailType;
  String toAddress;
  EmailRenderer renderer;
}

其中 EmailRenderer 是一个接口

public interface EmailRenderer{
  EmailMessage render()
}

现在,每种类型的电子邮件都有一个单独的渲染器接口实现,并且每个实现都包含一些必须由客户端提供的渲染数据。对于每个实现,此数据可能不同。

例子:

public class Type1EmailRenderer implements EmailRenderer{
  String param1;
  String param2;
  @Override
  EmailMessage render(){
    //rendering logic using the params
  }
}

但是,用户设置电子邮件类型和渲染器对我来说似乎是多余的。选择渲染器应该会自动给我 emailType。我应该如何重组请求以摆脱这种冗余?另外,我可以使用任何设计模式为我的用户提供渲染器吗?

标签: javaoopdesign-patterns

解决方案


我的回答基于这样一种说法,即抛开与编程相关的问题,在人类逻辑的层面上,如果我想发送一封电子邮件,我应该完全了解渲染器,这在我看来很奇怪。

根据我的理解,如果我有不同类型的电子邮件(您称它们为 TYPE_1 和 TYPE_2,为了更清楚起见,让我们给出更多“业务”名称,例如“dailyReport”或“广告”,您稍后会看到原因)我应该只是用我的数据(param1,param2)准备一个请求并发送它。只要相同的电子邮件类型假定将使用相同类型的渲染器,我根本不应该关心渲染器。

因此,可以说,类型“advertisement”具有强制参数String topic和可选参数String targetAudience,类型“dailyReport”具有Integer totalUsersCount和可选参数String mostActiveUserName

在这种情况下,我提出了一种主要基于 Builder 创建模式的混合方法:

  public class EmailRequestBuilder {

      private String toAddress;

      private EmailRequestBuilder(String to) {
          this.toAddress = to;
      }
      public static EmailRequestBuilder newEmailRequest(String to) {
          return new EmailRequestBuilder(to);
      } 

      public AdvertisementBuilder ofAdvertisementType(String topic) {
          return new AdvertisementBuilder(topic, this);
      }

      public DailyReportBuilder ofDailyReportType(Integer totalUsersCount) {
          return new DailyReportBuilder(totalUsersCount, this);
      }
      // all builders in the same package, hence package private build method,
      // concrete email type builders will call this method, I'll show at the end
      EmailRequest build(EmailType type, EmailRenderer emailRenderer) {              
          return new EmailRequest (to, type, emailRenderer);
      }
  }
  public class AdvertisementBuilder {
      private String topic;
      private EmailRequestBuilder emailRequestBuilder;
      // package private, so that only EmailRequestBuilder will be able to create it 
      AdvertisementBuilder(String topic, EmailRequestBuilder emailRequestBuilder) // mandatory parameters in constructor + reference to already gathered data {
          this.topic = topic;
          this.emailRequestBuilder = emailRequestBuilder;
      }

      // for optional parameters provide an explicit method that can be called 
      // but its not a mandatory call
      public AdvertisementBuilder withTargetAudience(String audience) {
          this.audience = audience;
          return this;
      }

      public EmailRequest buildRequest() {
          EmailRenderer renderer = new AdvertisementRenderer(topic, audience);   
          return emailRequestBuilder.build(EmailType.ADVERTISEMENT, renderer);
      }            
  }

  // A similar builder for DailyReport (I'll omit it but assume that there is a class
  class DailyReportBuilder {}

现在,关于它的好处是,现在您作为用户不会出错。与这种结构的典型交互将是:

  EmailRequest request =  EmailRequestBuilder.newEmailRequest("john.smith@gmail.com")
                     .ofAdvertisementType("sample topic") // its a mandatory param, you have to supply, can't go wrong
                     .withTargetAudience("target audience") // non-mandatory call
                     .buildRequest();

几点注意事项:

  • 一旦你通过调用ofDailyReportType/ ofAdvertisementType用户选择了一个类型,就不能真正提供不同电子邮件类型的参数,因为它被“路由”到没有错误参数方法的构建器。这直接意味着自动完成将在您的 IDE 中工作,使用此方法的人会感谢您;)

  • 通过这种方式添加新的电子邮件类型很容易,现有代码不会更改。

  • 也许使用这种方法, anenum EmailType将是多余的。我已将其保留在我的解决方案中,但如果不需要,您可能会放弃它。

  • 由于我有时会限制可见性(封装私有构建方法、构造函数等) - 创建请求将是 __the_only__ 方式,这意味着没有人会创建“内部”对象,因为它是可能的。至少一个恶意程序员会在破坏封装之前三思而后行:)


推荐阅读