使用管道流實現Java 8階段構建器
Step builder多階段步驟構造器模式是一種物件建立軟體設計模式。與傳統構建器模式進行比較時,步驟構建器模式提供了一些簡潔的好處。Step Builder模式的主要優勢之一是為客戶提供有關如何使用API的指南。它可以看作是構建器模式和狀態機的混合,事實上,這種模式通常被稱為構建物件的嚮導。
優點
- 通過物件建立過程逐步為API提供使用者指南。
- 一旦物件處於一致狀態,API使用者就可以呼叫構建器的build()方法。
- 減少了建立不一致物件例項的機會。
- 對必填欄位進行排序初始化。
- 流暢的API。
- 無需為欄位驗證提供validate()方法。
缺點
- 實現模式本身所需的程式碼可讀性低。
- 沒有eclipse外掛來幫助程式碼生成。(另一方面,Builder模式生成器有很多程式碼生成器)。
案例:
由於Step Builder模式是一種建立性設計模式,因此我們將重點放在其目的 - 建立物件上。
API使用示例如下所示:
Email email = Email.builder().from(EmailAddress.of(<font>"Microservices Weekly <[email protected]>"</font><font>)) .to(EmailAddress.of(</font><font>"[email protected]"</font><font>)) .subject(Subject.of(</font><font>"Subject"</font><font>)) .content(Content.of(</font><font>"Test email"</font><font>)) .build(); </font>
這個API內部是如何實現的?
<b>public</b> <b>class</b> Email { <b>private</b> EmailAddress from; <b>private</b> List<EmailAddress> to; <b>private</b> List<EmailAddress> cc; <b>private</b> List<EmailAddress> bcc; <b>private</b> Subject subject; <b>private</b> Content content; <b>public</b> <b>static</b> FromStep builder() { <b>return</b> <b>new</b> Builder(); } <b>public</b> <b>interface</b> FromStep { ToStep from(EmailAddress from); } <b>public</b> <b>interface</b> ToStep { SubjectStep to(EmailAddress... from); } <b>public</b> <b>interface</b> SubjectStep { ContentStep subject(Subject subject); } <b>public</b> <b>interface</b> ContentStep { Build content(Content content); } <b>public</b> <b>interface</b> Build { Email build(); Build cc(EmailAddress... cc); Build bcc(EmailAddress... bcc); } <b>public</b> <b>static</b> <b>class</b> Builder implements FromStep, ToStep, SubjectStep, ContentStep, Build { <b>private</b> EmailAddress from; <b>private</b> List<EmailAddress> to; <b>private</b> List<EmailAddress> cc; <b>private</b> List<EmailAddress> bcc; <b>private</b> Subject subject; <b>private</b> Content content; @Override <b>public</b> Email build() { <b>return</b> <b>new</b> Email(<b>this</b>); } @Override <b>public</b> Build cc(EmailAddress... cc) { Objects.requireNonNull(cc); <b>this</b>.cc = <b>new</b> ArrayList<EmailAddress>(Arrays.asList(cc)); <b>return</b> <b>this</b>; } @Override <b>public</b> Build bcc(EmailAddress... bcc) { Objects.requireNonNull(bcc); <b>this</b>.bcc = <b>new</b> ArrayList<EmailAddress>(Arrays.asList(bcc)); <b>return</b> <b>this</b>; } @Override <b>public</b> Build content(Content content) { Objects.requireNonNull(content); <b>this</b>.content = content; <b>return</b> <b>this</b>; } @Override <b>public</b> ContentStep subject(Subject subject) { Objects.requireNonNull(subject); <b>this</b>.subject = subject; <b>return</b> <b>this</b>; } @Override <b>public</b> SubjectStep to(EmailAddress... to) { Objects.requireNonNull(to); <b>this</b>.to = <b>new</b> ArrayList<EmailAddress>(Arrays.asList(to)); <b>return</b> <b>this</b>; } @Override <b>public</b> ToStep from(EmailAddress from) { Objects.requireNonNull(from); <b>this</b>.from = from; <b>return</b> <b>this</b>; } } <b>private</b> Email(Builder builder) { <b>this</b>.from = builder.from; <b>this</b>.to = builder.to; <b>this</b>.cc = builder.cc; <b>this</b>.bcc = builder.bcc; <b>this</b>.subject = builder.subject; <b>this</b>.content = builder.content; } <b>public</b> EmailAddress getFrom() { <b>return</b> from; } <b>public</b> List<EmailAddress> getTo() { <b>return</b> to; } <b>public</b> List<EmailAddress> getCc() { <b>return</b> cc; } <b>public</b> List<EmailAddress> getBcc() { <b>return</b> bcc; } <b>public</b> Subject getSubject() { <b>return</b> subject; } <b>public</b> Content getContent() { <b>return</b> content; } }
實施的經驗法則:
- 向您的類新增依賴項。建議將private修飾符新增到類屬性中。
- 將每個建立步驟定義為基類中的內部介面。
- 每個建立步驟都應該返回鏈中的下一步(介面)。
- 最後一步應該是名為“Build”的介面,它將提供build()方法。
- 定義一個內部靜態Builder類,它實現所有已定義的步驟。
- 實現步驟介面方法。
新案例:
<b>public</b> <b>static</b> <b>class</b> Coffee { <b>private</b> <b>final</b> CoffeeType type; <font><i>// Compulsory, one of arabica, robusta, moka...</i></font><font> <b>private</b> <b>final</b> Quantity quantity; </font><font><i>// Compulsory</i></font><font> <b>private</b> <b>final</b> Optional<Quantity> sugar; <b>private</b> <b>final</b> Optional<Quantity> cream; } @FunctionalInterface <b>interface</b> RequireCoffeeType { RequireQuantity coffeeType(CoffeeType type); } @FunctionalInterface <b>interface</b> RequireQuantity { FinalStage quantity(Quantity quantity); } <b>public</b> <b>final</b> <b>class</b> FinalStage { <b>private</b> <b>final</b> CoffeeType type; </font><font><i>// Obtained through the staged builder</i></font><font> <b>private</b> <b>final</b> Quantity quantity; </font><font><i>// Obtained through the staged builder</i></font><font> <b>private</b> Optional<Quantity> sugar; </font><font><i>// Regular builder for this optional field</i></font><font> <b>private</b> Optional<Quantity> cream; </font><font><i>// Regular builder for this optional field</i></font><font> </font><font><i>// ....</i></font><font> <b>public</b> Coffee build() { <b>return</b> <b>new</b> Coffee(type, quantity, sugar, cream); } } <b>public</b> <b>static</b> RequireCoffeeType builder() { <b>return</b> type -> quantity -> <b>new</b> FinalStage(type, quantity); } </font>
它可以強制呼叫者呼叫所有階段,最終獲得構建方法,並確保不會忘記強制階段,
遵循這種模式很難:
- 很難重用上面定義的階段(功能介面)
- 很難在階段提出替代選擇。
讓我們提出我們想要構建的以下事件:
<b>class</b> UserConnected implements Event { <b>private</b> <b>final</b> User user; <b>private</b> <b>final</b> MailboxSession.SessionId sessionId; <font><i>// Constructor & getters</i></font><font> } <b>class</b> MailboxCreated implements Event { <b>private</b> <b>final</b> User user; <b>private</b> <b>final</b> MailboxSession.SessionId sessionId; <b>private</b> <b>final</b> MailboxId mailboxId; </font><font><i>// Constructor & getters</i></font><font> } </font>
這是兩個建立事件,分別是使用者連線上的事件和郵箱已經建立的事件。由於我們的事件具有類似的結構,我們最終會得到大量重複的程式碼!
使用當前模式定義,分階段構建器看起來像這樣,沒有其他選擇,並且階段重用:
<b>public</b> <b>static</b> <b>class</b> UserConnectedBuilder { @FunctionalInterface <b>public</b> <b>interface</b> RequireUser { RequireSessionId user(User user); } @FunctionalInterface <b>public</b> <b>interface</b> RequireSessionId { FinalStage sessionId(MailboxSession.SessionId sessionId); } <b>public</b> <b>static</b> <b>class</b> FinalStage { <b>private</b> <b>final</b> User user; <b>private</b> <b>final</b> MailboxSession.SessionId sessionId; <font><i>// constructor</i></font><font> <b>public</b> UserConnected build() { <b>return</b> <b>new</b> UserConnected(user, sessionId); } } <b>public</b> <b>static</b> RequireUser builder() { <b>return</b> user -> sessionId -> <b>new</b> FinalStage(user, sessionId); } } <b>public</b> <b>static</b> <b>class</b> MailboxCreatedBuilder { @FunctionalInterface <b>public</b> <b>interface</b> RequireUser { RequireSessionId user(User user); } @FunctionalInterface <b>public</b> <b>interface</b> RequireSessionId { RequireMailboxId sessionId(MailboxSession.SessionId sessionId); } @FunctionalInterface <b>public</b> <b>interface</b> RequireMailboxId { FinalStage mailboxId(MailboxId mailboxId); } <b>public</b> <b>static</b> <b>class</b> FinalStage { <b>private</b> <b>final</b> User user; <b>private</b> <b>final</b> MailboxSession.SessionId sessionId; <b>private</b> <b>final</b> MailboxId mailboxId; </font><font><i>// constructor</i></font><font> <b>public</b> MailboxCreated build() { <b>return</b> <b>new</b> MailboxCreated(user, sessionId, mailboxId); } } <b>public</b> <b>static</b> RequireUser builder() { <b>return</b> user -> sessionId -> mailboxId -> <b>new</b> FinalStage(user, sessionId, mailboxId); } } </font>
由於我們的事件具有類似的結構,我們最終會得到大量重複的程式碼!
我們可以看到,作為呼叫者,我們還需要明確指定每個階段:
MailboxCreatedBuilder.builder() .user(User.fromUsername(<font>"bob"</font><font>)) .sessionId(SessionId.of(45)) .mailboxId(MailboxId.of(15)) .build(); MailboxCreatedBuilder.builder() </font><font><i>// .mailboxSession(session) // not allowed</i></font><font> .user(session.getUser()) .sessionId(session.getId()) .mailboxId(MailboxId.of(15)) .build(); </font>
希望我們可以使用一些Java特異功能來克服這些限制......
具有泛型的獨立階段
通過使我們的階段成為通用的,我們可以讓呼叫者指定下一個階段(通過構建器方法簽名),這將使階段重用和解除彼此之間的階段。
使用預設方法的替代(跳過階段)
我們可以定義將兩個階段組合在一起的“元階段”。然後,“元階段”可以公開一種預設方法,允許將兩個階段分解為單個階段。
上面的例子現在看起來像這樣:
@FunctionalInterface <b>public</b> <b>interface</b> RequireUser<T> { T user(User user); } @FunctionalInterface <b>public</b> <b>interface</b> RequireSessionId<T> { T sessionId(MailboxSession.SessionId sessionId); } @FunctionalInterface <font><i>// "meta-stage" session combining to stages into one</i></font><font> <b>public</b> <b>interface</b> RequireSession<T> <b>extends</b> RequireUser<RequireSessionId<T>> { <b>default</b> T session(MailboxSession session) { <b>return</b> user(session.getUser()) .sessionId(session.getId()); } } @FunctionalInterface <b>public</b> <b>interface</b> RequireMailboxId<T> { T mailboxId(MailboxId mailboxId); } <b>public</b> <b>static</b> <b>class</b> UserConnectedBuilder { <b>public</b> <b>static</b> <b>class</b> FinalStage { <b>private</b> <b>final</b> User user; <b>private</b> <b>final</b> MailboxSession.SessionId sessionId; </font><font><i>// constructor</i></font><font> <b>public</b> UserConnected build() { <b>return</b> <b>new</b> UserConnected(user, sessionId); } } <b>public</b> <b>static</b> RequireSession<FinalStage> builder() { <b>return</b> user -> sessionId -> <b>new</b> FinalStage(user, sessionId); } } <b>public</b> <b>static</b> <b>class</b> MailboxCreatedBuilder { <b>public</b> <b>static</b> <b>class</b> FinalStage { <b>private</b> <b>final</b> User user; <b>private</b> <b>final</b> MailboxSession.SessionId sessionId; <b>private</b> <b>final</b> MailboxId mailboxId; </font><font><i>// constructor</i></font><font> <b>public</b> MailboxCreated build() { <b>return</b> <b>new</b> MailboxCreated(user, sessionId, mailboxId); } } <b>public</b> <b>static</b> RequireSession<RequireMailboxId<FinalStage>> builder() { <b>return</b> user -> sessionId -> mailboxId -> <b>new</b> FinalStage(user, sessionId, mailboxId); } } </font>
現在,使用者可以獲得所需的便捷方法,更不用說程式碼共享了......
MailboxCreatedBuilder.builder() .user(User.fromUsername(<font>"bob"</font><font>)) .sessionId(SessionId.of(45)) .mailboxId(MailboxId.of(15)) .build(); MailboxCreatedBuilder.builder() .mailboxSession(session) </font><font><i>// now allowed</i></font><font> .mailboxId(MailboxId.of(15)) .build(); </font>
此外,構建器方法型別顯式地向呼叫者公開所需的階段,而不是僅暴露下一個階段......