Step builder多阶段步骤构造器模式是一种对象创建软件设计模式。与传统构建器模式进行比较时,步骤构建器模式提供了一些简洁的好处。Step Builder模式的主要优势之一是为客户提供有关如何使用API的指南。它可以看作是构建器模式和状态机的混合,事实上,这种模式通常被称为构建对象的向导。
优点
- 通过对象创建过程逐步为API提供用户指南。
- 一旦对象处于一致状态,API用户就可以调用构建器的build()方法。
- 减少了创建不一致对象实例的机会。
- 对必填字段进行排序初始化。
- 流畅的API。
- 无需为字段验证提供validate()方法。
缺点
- 实现模式本身所需的代码可读性低。
- 没有eclipse插件来帮助代码生成。(另一方面,Builder模式生成器有很多代码生成器)。
案例:
由于Step Builder模式是一种创建性设计模式,因此我们将重点放在其目的 - 创建对象上。
API使用示例如下所示:Email email = Email.builder().from(EmailAddress.of("Microservices Weekly <mw@microservicesweekly.com>")) .to(EmailAddress.of("svlada@gmail.com")) .subject(Subject.of("Subject")) .content(Content.of("Test email")) .build();
|
这个API内部是如何实现的?
public class Email { private EmailAddress from; private List<EmailAddress> to; private List<EmailAddress> cc; private List<EmailAddress> bcc; private Subject subject; private Content content;
public static FromStep builder() { return new Builder(); }
public interface FromStep { ToStep from(EmailAddress from); }
public interface ToStep { SubjectStep to(EmailAddress... from); }
public interface SubjectStep { ContentStep subject(Subject subject); }
public interface ContentStep { Build content(Content content); }
public interface Build { Email build(); Build cc(EmailAddress... cc); Build bcc(EmailAddress... bcc); }
public static class Builder implements FromStep, ToStep, SubjectStep, ContentStep, Build { private EmailAddress from; private List<EmailAddress> to; private List<EmailAddress> cc; private List<EmailAddress> bcc; private Subject subject; private Content content;
@Override public Email build() { return new Email(this); } @Override public Build cc(EmailAddress... cc) { Objects.requireNonNull(cc); this.cc = new ArrayList<EmailAddress>(Arrays.asList(cc)); return this; } @Override public Build bcc(EmailAddress... bcc) { Objects.requireNonNull(bcc); this.bcc = new ArrayList<EmailAddress>(Arrays.asList(bcc)); return this; } @Override public Build content(Content content) { Objects.requireNonNull(content); this.content = content; return this; } @Override public ContentStep subject(Subject subject) { Objects.requireNonNull(subject); this.subject = subject; return this; } @Override public SubjectStep to(EmailAddress... to) { Objects.requireNonNull(to); this.to = new ArrayList<EmailAddress>(Arrays.asList(to)); return this; } @Override public ToStep from(EmailAddress from) { Objects.requireNonNull(from); this.from = from; return this; } }
private Email(Builder builder) { this.from = builder.from; this.to = builder.to; this.cc = builder.cc; this.bcc = builder.bcc; this.subject = builder.subject; this.content = builder.content; }
public EmailAddress getFrom() { return from; }
public List<EmailAddress> getTo() { return to; }
public List<EmailAddress> getCc() { return cc; }
public List<EmailAddress> getBcc() { return bcc; }
public Subject getSubject() { return subject; }
public Content getContent() { return content; } }
|
实施的经验法则:
- 向您的类添加依赖项。建议将private修饰符添加到类属性中。
- 将每个创建步骤定义为基类中的内部接口。
- 每个创建步骤都应该返回链中的下一步(界面)。
- 最后一步应该是名为“Build”的接口,它将提供build()方法。
- 定义一个内部静态Builder类,它实现所有已定义的步骤。
- 实现步骤接口方法。
新案例:public static class Coffee { private final CoffeeType type; // Compulsory, one of arabica, robusta, moka... private final Quantity quantity; // Compulsory private final Optional<Quantity> sugar; private final Optional<Quantity> cream; }
@FunctionalInterface interface RequireCoffeeType { RequireQuantity coffeeType(CoffeeType type); }
@FunctionalInterface interface RequireQuantity { FinalStage quantity(Quantity quantity); }
public final class FinalStage { private final CoffeeType type; // Obtained through the staged builder private final Quantity quantity; // Obtained through the staged builder private Optional<Quantity> sugar; // Regular builder for this optional field private Optional<Quantity> cream; // Regular builder for this optional field
// ....
public Coffee build() { return new Coffee(type, quantity, sugar, cream); } }
public static RequireCoffeeType builder() { return type -> quantity -> new FinalStage(type, quantity); }
|
它可以强制调用者调用所有阶段,最终获得构建方法,并确保不会忘记强制阶段,
遵循这种模式很难:
- 很难重用上面定义的阶段(功能接口)
- 很难在阶段提出替代选择。
让我们提出我们想要构建的以下事件: class UserConnected implements Event { private final User user; private final MailboxSession.SessionId sessionId; // Constructor & getters } class MailboxCreated implements Event { private final User user; private final MailboxSession.SessionId sessionId; private final MailboxId mailboxId;
// Constructor & getters }
|
这是两个创建事件,分别是用户连接上的事件和邮箱已经创建的事件。由于我们的事件具有类似的结构,我们最终会得到大量重复的代码!使用当前模式定义,分阶段构建器看起来像这样,没有其他选择,并且阶段重用:
public static class UserConnectedBuilder { @FunctionalInterface public interface RequireUser { RequireSessionId user(User user); }
@FunctionalInterface public interface RequireSessionId { FinalStage sessionId(MailboxSession.SessionId sessionId); }
public static class FinalStage { private final User user; private final MailboxSession.SessionId sessionId; // constructor
public UserConnected build() { return new UserConnected(user, sessionId); } }
public static RequireUser builder() { return user -> sessionId -> new FinalStage(user, sessionId); } }
public static class MailboxCreatedBuilder { @FunctionalInterface public interface RequireUser { RequireSessionId user(User user); }
@FunctionalInterface public interface RequireSessionId { RequireMailboxId sessionId(MailboxSession.SessionId sessionId); }
@FunctionalInterface public interface RequireMailboxId { FinalStage mailboxId(MailboxId mailboxId); }
public static class FinalStage { private final User user; private final MailboxSession.SessionId sessionId; private final MailboxId mailboxId; // constructor
public MailboxCreated build() { return new MailboxCreated(user, sessionId, mailboxId); } }
public static RequireUser builder() { return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId); } }
|
由于我们的事件具有类似的结构,我们最终会得到大量重复的代码!
我们可以看到,作为调用者,我们还需要明确指定每个阶段:
MailboxCreatedBuilder.builder() .user(User.fromUsername("bob")) .sessionId(SessionId.of(45)) .mailboxId(MailboxId.of(15)) .build();
MailboxCreatedBuilder.builder() // .mailboxSession(session) // not allowed .user(session.getUser()) .sessionId(session.getId()) .mailboxId(MailboxId.of(15)) .build();
|
希望我们可以使用一些Java特异功能来克服这些限制......
具有泛型的独立阶段
通过使我们的阶段成为通用的,我们可以让调用者指定下一个阶段(通过构建器方法签名),这将使阶段重用和解除彼此之间的阶段。
使用默认方法的替代(跳过阶段)
我们可以定义将两个阶段组合在一起的“元阶段”。然后,“元阶段”可以公开一种默认方法,允许将两个阶段分解为单个阶段。
上面的例子现在看起来像这样:
@FunctionalInterface public interface RequireUser<T> { T user(User user); }
@FunctionalInterface public interface RequireSessionId<T> { T sessionId(MailboxSession.SessionId sessionId); }
@FunctionalInterface // "meta-stage" session combining to stages into one public interface RequireSession<T> extends RequireUser<RequireSessionId<T>> { default T session(MailboxSession session) { return user(session.getUser()) .sessionId(session.getId()); } }
@FunctionalInterface public interface RequireMailboxId<T> { T mailboxId(MailboxId mailboxId); }
public static class UserConnectedBuilder { public static class FinalStage { private final User user; private final MailboxSession.SessionId sessionId; // constructor
public UserConnected build() { return new UserConnected(user, sessionId); } }
public static RequireSession<FinalStage> builder() { return user -> sessionId -> new FinalStage(user, sessionId); } }
public static class MailboxCreatedBuilder { public static class FinalStage { private final User user; private final MailboxSession.SessionId sessionId; private final MailboxId mailboxId; // constructor
public MailboxCreated build() { return new MailboxCreated(user, sessionId, mailboxId); } }
public static RequireSession<RequireMailboxId<FinalStage>> builder() { return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId); } }
|
现在,用户可以获得所需的便捷方法,更不用说代码共享了......
MailboxCreatedBuilder.builder() .user(User.fromUsername("bob")) .sessionId(SessionId.of(45)) .mailboxId(MailboxId.of(15)) .build();
MailboxCreatedBuilder.builder() .mailboxSession(session) // now allowed .mailboxId(MailboxId.of(15)) .build();
|
此外,构建器方法类型显式地向调用者公开所需的阶段,而不是仅暴露下一个阶段......