建造者模式——不止提高代碼檔次
本文轉(zhuǎn)載自微信公眾號(hào)「JavaKeeper」,作者海星。轉(zhuǎn)載本文請(qǐng)聯(lián)系JavaKeeper公眾號(hào)。
簡(jiǎn)介
Builder Pattern,中文翻譯為建造者模式或者構(gòu)建者模式,也有人叫它生成器模式。
建造者模式是一種創(chuàng)建型設(shè)計(jì)模式, 使你能夠分步驟創(chuàng)建復(fù)雜對(duì)象。它允許用戶只通過(guò)指定復(fù)雜對(duì)象的類(lèi)型和內(nèi)容就可以構(gòu)建它們,用戶不需要知道內(nèi)部的具體構(gòu)建細(xì)節(jié)。
定義:將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。
hello world
程序員麼,先上個(gè) hello world 熱熱身
- public class User {
- private Long id;
- private String name;
- private Integer age; //可選
- private String desc; //可選
- private User(Builder builder) {
- this.id = builder.id;
- this.name = builder.name;
- this.age = builder.age;
- this.desc = builder.desc;
- }
- public static Builder newBuilder(Long id, String name) {
- return new Builder(id, name);
- }
- public Long getId() {return id;}
- public String getName() {return name;}
- public Integer getAge() {return age;}
- public String getDesc() {return desc;}
- @Override
- public String toString() {
- return "Builder{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", age=" + age +
- ", desc='" + desc + '\'' +
- '}';
- }
- public static class Builder {
- private Long id;
- private String name;
- private Integer age;
- private String desc;
- private Builder(Long id, String name) {
- Assert.assertNotNull("標(biāo)識(shí)不能為空",id);
- Assert.assertNotNull("名稱不能為空",name);
- this.id = id;
- this.name = name;
- }
- public Builder age(Integer age) {
- this.age = age;
- return this;
- }
- public Builder desc(String desc) {
- this.desc = desc;
- return this;
- }
- public User build() {
- return new User(this);
- }
- }
- public static void main(String[] args) {
- User user = User.newBuilder(1L, "starfish").age(22).desc("test").build();
- System.out.println(user.toString());
- }
- }
這樣的代碼有什么優(yōu)缺點(diǎn)呢?
主要優(yōu)點(diǎn):
- 明確了必填參數(shù)和可選參數(shù),在構(gòu)造方法中進(jìn)行驗(yàn)證;
- 可以定義為不可變類(lèi),初始化后屬性字段值不可變更;
- 賦值代碼可讀性較好,明確知道哪個(gè)屬性字段對(duì)應(yīng)哪個(gè)值;
- 支持鏈?zhǔn)椒椒ㄕ{(diào)用,相比于調(diào)用 Setter 方法,代碼更簡(jiǎn)潔。
主要缺點(diǎn):
- 代碼量較大,多定義了一個(gè) Builder 類(lèi),多定義了一套屬性字段,多實(shí)現(xiàn)了一套賦值方法;
- 運(yùn)行效率低,需要先創(chuàng)建 Builder 實(shí)例,再賦值屬性字段,再創(chuàng)建目標(biāo)實(shí)例,最后拷貝屬性字段。
當(dāng)然,以上代碼,就可以通過(guò) Lombok 的 @Builder 簡(jiǎn)化代碼
如果我們就那么三三兩兩個(gè)參數(shù),直接構(gòu)造函數(shù)配合 set 方法就能搞定的,就不用套所謂的模式了。
高射炮打蚊子——不合算
假設(shè)有這樣一個(gè)復(fù)雜對(duì)象, 在對(duì)其進(jìn)行構(gòu)造時(shí)需要對(duì)諸多成員變量和嵌套對(duì)象進(jìn)行繁復(fù)的初始化工作。這些初始化代碼通常深藏于一個(gè)包含眾多參數(shù)且讓人基本看不懂的構(gòu)造函數(shù)中;甚至還有更糟糕的情況, 那就是這些代碼散落在客戶端代碼的多個(gè)位置。
這時(shí)候才是構(gòu)造器模式上場(chǎng)的時(shí)候
上邊的例子,其實(shí)屬于簡(jiǎn)化版的建造者模式,只是為了方便構(gòu)建類(lèi)中的各個(gè)參數(shù),”正經(jīng)“的和這個(gè)有點(diǎn)差別,更傾向于用同樣的構(gòu)建過(guò)程分步創(chuàng)建不同的產(chǎn)品類(lèi)。
我們接著扯~
結(jié)構(gòu)
從 UML 圖上可以看到有 4 個(gè)不同的角色
- 抽象建造者(Builder):創(chuàng)建一個(gè) Produc 對(duì)象的各個(gè)部件指定的接口/抽象類(lèi)
- 具體建造者(ConcreteBuilder):實(shí)現(xiàn)接口,構(gòu)建和裝配各個(gè)組件
- 指揮者/導(dǎo)演類(lèi)(Director):構(gòu)建一個(gè)使用 Builder 接口的對(duì)象。負(fù)責(zé)調(diào)用適當(dāng)?shù)慕ㄔ煺邅?lái)組建產(chǎn)品,導(dǎo)演類(lèi)一般不與產(chǎn)品類(lèi)發(fā)生依賴關(guān)系,與導(dǎo)演類(lèi)直接交互的是建造者類(lèi)。
- 產(chǎn)品類(lèi)(Product):一個(gè)具體的產(chǎn)品對(duì)象
demo
假設(shè)我是個(gè)汽車(chē)工廠,需求就是能造各種車(chē)(或者造電腦、造房子、做煎餅、生成不同文件TextBuilder、HTMLBuilder等等,都是一個(gè)道理)
1、生成器(Builder)接口聲明在所有類(lèi)型生成器中通用的產(chǎn)品構(gòu)造步驟
- public interface CarBuilder {
- void setCarType(CarType type);
- void setSeats(int seats);
- void setEngine(Engine engine);
- void setGPS(GPS gps);
- }
2、具體的生成器(Concrete Builders)提供構(gòu)造過(guò)程的不同實(shí)現(xiàn)
- public class SportsCarBuilder implements CarBuilder {
- private CarType carType;
- private int seats;
- private Engine engine;
- private GPS gps;
- @Override
- public void setCarType(CarType type) {
- this.carType = type;
- }
- @Override
- public void setSeats(int seats) {
- this.seats = seats;
- }
- @Override
- public void setEngine(Engine engine) {
- this.engine = engine;
- }
- @Override
- public void setGPS(GPS gps) {
- this.gps = gps;
- }
- public Car getResult() {
- return new Car(carType, seats, engine, gps);
- }
- }
3、產(chǎn)品(Products)是最終生成的對(duì)象
- @Setter
- @Getter
- @ToString
- public class Car {
- private final CarType carType;
- private final int seats;
- private final Engine engine;
- private final GPS gps;
- private double fuel;
- public Car(CarType carType,int seats,Engine engine,GPS gps){
- this.carType = carType;
- this.seats = seats;
- this.engine = engine;
- this.gps = gps;
- }
- }
4、主管(Director)類(lèi)定義調(diào)用構(gòu)造步驟的順序,這樣就可以創(chuàng)建和復(fù)用特定的產(chǎn)品配置(Director 類(lèi)的構(gòu)造函數(shù)的參數(shù)是 CarBuilder,但實(shí)際上沒(méi)有實(shí)例傳遞出去作參數(shù),因?yàn)?CarBuilder 是接口或抽象類(lèi),無(wú)法產(chǎn)生對(duì)象實(shí)例,實(shí)際傳遞的是 Builder 的子類(lèi),根據(jù)子類(lèi)類(lèi)型,決定生產(chǎn)內(nèi)容)
- public class Director {
- public void constructSportsCar(CarBuilder builder){
- builder.setCarType(CarType.SPORTS_CAR);
- builder.setSeats(2);
- builder.setEngine(new Engine(2.0,0));
- builder.setGPS(new GPS());
- }
- public void constructCityCar(CarBuilder builder){
- builder.setCarType(CarType.CITY_CAR);
- builder.setSeats(4);
- builder.setEngine(new Engine(1.5,0));
- builder.setGPS(new GPS());
- }
- public void constructSUVCar(CarBuilder builder){
- builder.setCarType(CarType.SUV);
- builder.setSeats(4);
- builder.setEngine(new Engine(2.5,0));
- builder.setGPS(new GPS());
- }
- }
5、客戶端使用(最終結(jié)果從建造者對(duì)象中獲取,主管并不知道最終產(chǎn)品的類(lèi)型)
- public class Client {
- public static void main(String[] args) {
- Director director = new Director();
- SportsCarBuilder builder = new SportsCarBuilder();
- director.constructSportsCar(builder);
- Car car = builder.getResult();
- System.out.println(car.toString());
- }
- }
適用場(chǎng)景
適用場(chǎng)景其實(shí)才是理解設(shè)計(jì)模式最重要的,只要知道這個(gè)業(yè)務(wù)場(chǎng)景需要什么模式,網(wǎng)上浪~程序員能不會(huì)嗎
- 使用建造者模式可避免重疊構(gòu)造函數(shù)的出現(xiàn)。
假設(shè)你的構(gòu)造函數(shù)中有 N 個(gè)可選參數(shù),那 new 各種實(shí)例的時(shí)候就很麻煩,需要重載構(gòu)造函數(shù)多次
- 當(dāng)你希望使用代碼創(chuàng)建不同形式的產(chǎn)品 (例如石頭或木頭房屋) 時(shí), 可使用建造者模式。
如果你需要?jiǎng)?chuàng)建的各種形式的產(chǎn)品, 它們的制造過(guò)程相似且僅有細(xì)節(jié)上的差異, 此時(shí)可使用建造者模式。
- 使用生成器構(gòu)造組合樹(shù)或其他復(fù)雜對(duì)象。
建造者模式讓你能分步驟構(gòu)造產(chǎn)品。你可以延遲執(zhí)行某些步驟而不會(huì)影響最終產(chǎn)品。你甚至可以遞歸調(diào)用這些步驟, 這在創(chuàng)建對(duì)象樹(shù)時(shí)非常方便。
VS 抽象工廠
抽象工廠模式實(shí)現(xiàn)對(duì)產(chǎn)品家族的創(chuàng)建,一個(gè)產(chǎn)品家族是這樣的一系列產(chǎn)品:具有不同分類(lèi)維度的產(chǎn)品組合,采用抽象工廠模式不需要關(guān)心抽象過(guò)程,只關(guān)心什么產(chǎn)品由什么工廠生產(chǎn)即可。而建造者模式則是要求按照指定的藍(lán)圖建造產(chǎn)品,它的主要目的是通過(guò)組裝零配件而生產(chǎn)一個(gè)新的產(chǎn)品。
最后
設(shè)計(jì)模式,這玩意看簡(jiǎn)單的例子,肯定能看得懂,主要是結(jié)合自己的業(yè)務(wù)思考怎么應(yīng)用,讓系統(tǒng)設(shè)計(jì)更完善,懂了每種模式后,可以找找各種框架源碼或在 github 搜搜相關(guān)內(nèi)容,看看實(shí)際中是怎么應(yīng)用的。
參考
refactoringguru.cn