使用管道流實現Java 8階段構建器

banq發表於2019-01-26

 Step builder多階段步驟構造器模式是一種物件建立軟體設計模式。與傳統構建器模式進行比較時,步驟構建器模式提供了一些簡潔的好處。Step Builder模式的主要優勢之一是為客戶提供有關如何使用API​​的指南。它可以看作是構建器模式和狀態機的混合,事實上,這種模式通常被稱為構建物件的嚮導。
優點
  1. 透過物件建立過程逐步為API提供使用者指南。
  2. 一旦物件處於一致狀態,API使用者就可以呼叫構建器的build()方法。
  3. 減少了建立不一致物件例項的機會。
  4. 對必填欄位進行排序初始化。
  5. 流暢的API。
  6. 無需為欄位驗證提供validate()方法。

缺點
  1. 實現模式本身所需的程式碼可讀性低。
  2. 沒有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;
    }
}

實施的經驗法則:
  1. 向您的類新增依賴項。建議將private修飾符新增到類屬性中。
  2. 將每個建立步驟定義為基類中的內部介面。
  3. 每個建立步驟都應該返回鏈中的下一步(介面)。
  4. 最後一步應該是名為“Build”的介面,它將提供build()方法。
  5. 定義一個內部靜態Builder類,它實現所有已定義的步驟。
  6. 實現步驟介面方法。

新案例:

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();



此外,構建器方法型別顯式地向呼叫者公開所需的階段,而不是僅暴露下一個階段......

相關文章