Builder Pattern in Java

  • 建造者模式:
    1. 建造者模式定義
    2. 建造者模式應(yīng)用場景
    3. 實(shí)現(xiàn)案例
    4. Jdk中的建造者模式
    5. 建造者模式的優(yōu)點(diǎn)
    6. 建造者模式的缺點(diǎn)

建造者模式定義

   Builder模式旨在將“復(fù)雜”對象的構(gòu)造與其表示分開,以便相同的構(gòu)造過程可以創(chuàng)建不同表示。

建造者模式應(yīng)用場景

在Java編程中final類及其對象有許多好處(可以參考閱讀String的實(shí)現(xiàn)),建造者模式可以幫助我們創(chuàng)建具有大量狀態(tài)屬性的不可變類對象。

在實(shí)際編程中,我們經(jīng)常會有一些業(yè)務(wù)模塊中有些對象,一旦創(chuàng)建其中的某些狀態(tài)屬性就不可變的情景,比如網(wǎng)約車的Trip模塊(有id、owner、departure、destination、amount等屬性),很多的狀態(tài)一旦創(chuàng)建我們認(rèn)為再發(fā)生變化是沒有意義的;再比如我們在后端服務(wù)中構(gòu)建多查詢條件時,我們希望一個查詢條件一旦被構(gòu)建則不希望其發(fā)生變化。

正常情況下,如果我們通過構(gòu)建一個普通的Java類來定義Trip,我們需要為其final屬性字段定義構(gòu)造函數(shù),但是隨著系統(tǒng)的迭代,當(dāng)后期再次為Trip新增final字段時就不得不為Trip類新增一個新的參數(shù)構(gòu)造器,這是及其糟糕的,這時候如果我們一開始便采用建造者模式來定義Trip則后期可以切合“相同的構(gòu)造過程創(chuàng)建不同表示”的定義。

public Trip(String id) {...}
public Trip(String id, String owner) {...}
public Trip(String id, String owner, String departure, String destination) {...}

當(dāng)然你也可以通過setter方法來設(shè)置普通類型對象,或者創(chuàng)建更多參數(shù)的構(gòu)造函數(shù)。

實(shí)現(xiàn)案例

下面代碼示例是我自己隨機(jī)定義的,如果你的團(tuán)隊(duì)使用過Protocol Buffers協(xié)議來構(gòu)造Java類的話,那么你應(yīng)該對建造者模式非常熟悉了,Protobuf中最經(jīng)典的就是建造者模式實(shí)現(xiàn)。

package com.iblog.pattern.builder;

public class Trip {
    private final String id;
    private final String owner;
    private final String departure;
    private final String destination;
    private final long amount;
    private final long expiredAt;
    private final String createdBy;
    private final long createdAt;
    private final String lastUpdatedBy;
    private final long lastUpdatedAt;

    private Trip(Builder builder) {
        this.id = builder.id;
        this.owner = builder.owner;
        this.departure = builder.departure;
        this.destination = builder.destination;
        this.amount = builder.amount;
        this.expiredAt = builder.expiredAt;
        this.createdBy = builder.createdBy;
        this.createdAt = builder.createdAt;
        this.lastUpdatedBy = builder.lastUpdatedBy;
        this.lastUpdatedAt = builder.lastUpdatedAt;
    }

    public Builder toBuilder() {
        return new Builder()
                .setId(this.id)
                .setOwner(this.owner)
                .setDeparture(this.departure)
                .setDestination(this.destination)
                .setAmount(this.amount)
                .setExpiredAt(this.expiredAt)
                .setCreatedBy(this.createdBy)
                .setCreatedAt(this.createdAt)
                .setLastUpdatedBy(this.lastUpdatedBy)
                .setLastUpdatedAt(this.lastUpdatedAt);
    }

    public static class Builder {
        private String id;
        private String owner;
        private String departure;
        private String destination;
        private long amount;
        private long expiredAt;
        private String createdBy;
        private long createdAt;
        private String lastUpdatedBy;
        private long lastUpdatedAt;

        /**
         * In actual combat, you can define the required fields as final types,
         * then set fields values through this constructor method.
         */
        public Builder() {}

        public Trip build() {
            return new Trip(this);
        }

        public Builder setId(String id) {
            this.id = id;
            return this;
        }

        public Builder setOwner(String owner) {
            this.owner = owner;
            return this;
        }

        public Builder setDeparture(String departure) {
            this.departure = departure;
            return this;
        }

        public Builder setDestination(String destination) {
            this.destination = destination;
            return this;
        }

        public Builder setAmount(long amount) {
            this.amount = amount;
            return this;
        }

        public Builder setExpiredAt(long expiredAt) {
            this.expiredAt = expiredAt;
            return this;
        }

        public Builder setCreatedBy(String createdBy) {
            this.createdBy = createdBy;
            return this;
        }

        public Builder setCreatedAt(long createdAt) {
            this.createdAt = createdAt;
            return this;
        }

        public Builder setLastUpdatedBy(String lastUpdatedBy) {
            this.lastUpdatedBy = lastUpdatedBy;
            return this;
        }

        public Builder setLastUpdatedAt(long lastUpdatedAt) {
            this.lastUpdatedAt = lastUpdatedAt;
            return this;
        }
    }

    public String getId() {
        return id;
    }

    public String getOwner() {
        return owner;
    }

    public String getDeparture() {
        return departure;
    }

    public String getDestination() {
        return destination;
    }

    public long getAmount() {
        return amount;
    }

    public long getExpiredAt() {
        return expiredAt;
    }

    public String getCreatedBy() {
        return createdBy;
    }

    public long getCreatedAt() {
        return createdAt;
    }

    public String getLastUpdatedBy() {
        return lastUpdatedBy;
    }

    public long getLastUpdatedAt() {
        return lastUpdatedAt;
    }

    @Override
    public String toString() {
        // you can cover this method.
        return super.toString();
    }
}

編寫一個使用測試類:

package com.iblog.pattern.builder;

import org.junit.Test;

import static org.junit.Assert.*;

public class TripTest {

    @Test
    public void testTripBuilder() {
        Trip.Builder builder = new Trip.Builder();
        builder.setId("1")
                .setOwner("lance")
                .setDeparture("wuhan")
                .setDestination("shanghai");
        Trip trip = builder.build();

        assertEquals("1", trip.getId());
        assertEquals("lance", trip.getOwner());
        assertEquals("wuhan", trip.getDeparture());
        assertEquals("shanghai", trip.getDestination());
        assertEquals(0, trip.getAmount());
        assertNull(trip.getCreatedBy());
    }
}

Jdk中的建造者模式

java.lang.Appendable接口所有的實(shí)現(xiàn)類都是建造者模式的實(shí)例:

  1. java.lang.StringBuilder#appen()
  2. java.lang.StringBuffer#appen()
  3. java.nio.ByteBuffer#put()

建造者模式的優(yōu)點(diǎn)

從案例中使用建造者模式之后,代碼行數(shù)增加了一倍;但是建造者模式的優(yōu)勢在于,代碼的設(shè)計(jì)更加靈活,而且美觀易讀;可以以高度可讀的形式呈現(xiàn)給他人。

建造者模式可以去除多參構(gòu)造函數(shù),使得代碼更容易擴(kuò)展,也無需考慮構(gòu)造函數(shù)中傳遞null值;對于及其必要屬性可以在建造者類中通過構(gòu)造器設(shè)置,構(gòu)建不可變對象邏輯非常清晰簡明;在使用建造者模式聲明的Object實(shí)例化過程中,調(diào)用者可以很方便靈活地設(shè)置屬性,知道調(diào)用build方法。

建造者模式的缺點(diǎn)

建造者模式會使得代碼行數(shù)增加,甚至超過setter和getter方法的一倍,這是建造者模式最大的缺點(diǎn),但是這并不能掩蓋建造者模式代碼高度可讀的優(yōu)點(diǎn)。

代碼參考:pattern-example

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容