Java設計模式學習記錄-建造者模式
前言
今天周末,有小雨,正好也不用出門了,那就在家學習吧,經過了兩周的面試,拿到了幾個offer,但是都不是自己很想去的那種,要麼就是幾個人的初創小公司,要麼就是開發企業内部系統的這種傳統開發,感覺這種傳統開發已經不能給自己帶來多大的提升了,因為工作了這幾年這種系統經曆了不少了,整天的就是增删改查。創業小公司已經不想再去了,工作了這幾年去的都是這種小公司,風險大,壓力大,節奏快,沒時間沉澱學習。上上家東家還欠我幾個月工資呢,就是因為創業公司資金鍊斷了,然後老闆忽悠上司,上司再忽悠我們,後來實在發不出工資了,忽悠不住了,就大批大批的走人了。
是以現在很是糾結,大點公司又去不了小的公司還看不上,目前就是這麼個高不成低不就的狀态,是以還是抓緊時間學習,充實自己吧,哪怕現在進不去稍微大點的公司,那經過努力的學習後說不定還是有機會的,但是不努力是一點機會都沒有的。
好了,言歸正傳,這次要介紹的是建立型設計模式的最後一個,建造者模式,這個模式其實我在平時開發中用的很多,隻不過是用了這個模式的更深一種形式吧。後面我會介紹到這一部分内容的。
建造者模式
建造者模式能夠将一個複雜的建構與其表示相分離,使得同樣的建構過程可以建立不同的表示。這句話了解起來可能有點抽象,簡單來說就是調用相同的建立對象的方法(建造過程)可以建立出不同的對象。
還是舉例來說明吧,如果說我要建立一部手機,我需要先制造手機的幾個核心部件,例如:螢幕、電池、聽筒、話筒、機身等。
public class MobilePhone {
//手機螢幕
private String screen;
//電池
private String battery;
//話筒
private String microphone;
//聽筒
private String phoneReceiver;
//機身
private String phoneBody;
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getBattery() {
return battery;
}
public void setBattery(String battery) {
this.battery = battery;
}
public String getMicrophone() {
return microphone;
}
public void setMicrophone(String microphone) {
this.microphone = microphone;
}
public String getPhoneReceiver() {
return phoneReceiver;
}
public void setPhoneReceiver(String phoneReceiver) {
this.phoneReceiver = phoneReceiver;
}
public String getPhoneBody() {
return phoneBody;
}
public void setPhoneBody(String phoneBody) {
this.phoneBody = phoneBody;
}
}
每一部手機都是這個類的對象,在建立一部手機的時候都要保證這幾個核心部件的建立。是以建立手機是需要一個标準規範的,因為這幾個核心部件都可以是不同的型号,不同的型号的部件制造出來的手機也是不同的,這樣就有了下面建造規範的接口。
public interface IBuildPhone {
/**
* 建造手機螢幕
*/
void buildScreen();
/**
* 建造手機電池
*/
void buildBattery();
/**
* 建造手機聽筒
*/
void buildMicrophone();
/**
* 建造手機話筒
*/
void buildPhoneReceiver();
/**
* 建造手機機身
*/
void buildPhoneBody();
}
有了規範了,就可以建立手機了,先建立一個iphoneX。
public class IPhoneX implements IBuildPhone {
private MobilePhone mobilePhone;
public IPhoneX(){
mobilePhone = new MobilePhone();
}
/**
* 建造手機螢幕
*/
@Override
public void buildScreen() {
mobilePhone.setScreen("OLED顯示屏");
}
/**
* 建造手機電池
*/
@Override
public void buildBattery() {
mobilePhone.setBattery("2700mAh電池容量");
}
/**
* 建造手機聽筒
*/
@Override
public void buildMicrophone() {
mobilePhone.setMicrophone("聽筒");
}
/**
* 建造手機話筒
*/
@Override
public void buildPhoneReceiver() {
mobilePhone.setPhoneReceiver("話筒");
}
/**
* 建造手機機身
*/
@Override
public void buildPhoneBody() {
mobilePhone.setPhoneBody("iphoneX機身");
}
/**
* 建立手機
* @return
*/
public MobilePhone build(){
return mobilePhone;
}
}
建立手機的工具寫好了,下面就可以使用了。
public class Director {
/**
* 建造一部手機
* @param buildPhone
* @return
*/
public MobilePhone createMobilePhone(IBuildPhone buildPhone){
buildPhone.buildBattery();
buildPhone.buildMicrophone();
buildPhone.buildScreen();
buildPhone.buildPhoneReceiver();
buildPhone.buildPhoneBody();
return buildPhone.createMobilePhone();
}
@Test
public void thatTest(){
System.out.println(JSON.toJSONString(createMobilePhone(new IPhoneX())));
}
}
關鍵的方法在createMobilePhone()方法,這個方法接收一個IBuildPhone接口的對象,是以隻要符合這個建立手機規範的對象都可以建立一部手機。createMobilePhone()方法可以接收new IPhoneX()這樣一個對象,也可以接收new IPhone8()、new FindX()等等。
具體使用方法在thatTest()方法中。這個方法的運作結果是:
{"battery":"2700mAh電池容量","microphone":"聽筒","phoneBody":"iphoneX機身","phoneReceiver":"話筒","screen":"OLED顯示屏"}
上面這個例子的實作過程就使用了我們今天要說的建造者模式,我們來分析一下建造者模式的結構。
如下圖:

在建造者模式的結構圖中包含如下4個角色。
Builder(抽象建造者):它(IBuildPhone)為建立一個産品的各個部件指定了标準,規定了要建立複雜對象需要建立哪些部分,并不直接建立對象的具體部分。
ConcreteBuilder(具體建造者):它實作了Builder接口(IPhoneX),實作各個部分的具體構造和裝配方法,定義并明确它所建立的複雜對象,也可以提供一個方法傳回建立好的複雜産品對象。
Product(産品角色):它(MobilePhone)是被建造的複雜對象,包含多個組成部分,具體建造者建立該産品的内部表示并定義它的裝配過程。
Director(指揮者):指揮者(Director),它複雜安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關系,可以在Director的方法中調用建造者對象的部件構造與裝配方法,完成建造複雜對象的任務。用戶端一般隻需與Director進行互動。
建造者模式靈活使用
好了,建造者模式到這裡就算是介紹完了,然後說一說我們平時在項目中是怎麼使用建造者模式的。先說一下場景,我們一般在開發的過程中都是需要分層的,MVC這個不一般人都不陌生吧,Model-View-Controller。(我這裡隻是舉例子不一定真的項目中就這樣用)那我們的資料在每一層的傳輸過程中如果需要增加或删除些額外的功能怎麼實作呢?
還是舉例子吧,如下面一個實體類:
public class Person {
private Long id;
private String name;
private int age;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
如果說這個類是一個orm架構需要的實體類,它最好的場景是隻被後端的資料操作使用,但是controller中有一個add方法,這個方法是新增一個人員,add方法接收的參數是一個人員對象,但是這個對象和上面這個Person得屬性有些差别,例如這個對象裡面有請求ip,以及這個對象中沒有id這個字段(id在資料庫中自增,是以前端不允許傳過來id )。這個時候就不能使用Person類的對象作為add的方法了,需要再建立一個類專門來給Controller使用。
如下代碼:
/**
* Controller使用的參數類
*/
public class PersonVO {
private String name;
private int age;
private String address;
//ip位址
private String requestIP;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getRequestIP() {
return requestIP;
}
public void setRequestIP(String requestIP) {
this.requestIP = requestIP;
}
}
參數對象可以建立了, 但是PersonVO的對象是需要轉成Person的對象的,這樣才能插入到資料庫中(資料庫的insert方法的參數是Person對象)。這種轉換操作其實也簡單如下代碼:
public Person convert2Person(PersonVO personVO){
Person person = new Person();
person.setName(personVO.getName());
person.setAge(personVO.getAge());
person.setAddress(personVO.getAddress());
return person;
}
但是我們通常是不這麼做的,因為如果要轉換的這個對象的字段很多那需要寫很多次對象調setter方法來進行指派。一種方式是直接寫一個将所有屬性當做參數的構造方法,直接一個一個把屬性值傳入就可以了,這種方式最簡單暴力。還有一種方式就是需要包裝一下這種方式,把Person改造一下。
public class Person {
private Long id;
private String name;
private int age;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
Person(){}
Person(String name,int age,String address){
this.name = name;
this.age = age;
this.address = address;
}
public static Person builder(){
return new Person();
}
public Person name(String name){
this.name = name;
return this;
}
public Person age(int age) {
this.age = age;
return this;
}
public Person address(String address) {
this.address = address;
return this;
}
public Person build(){
return new Person(name,age,address);
}
}
後面新增了兩個構造函數,以及一個builder()方法和一個build()方法,還有幾個指派方法,需要注意的是指派方法和setter方法的差別,這樣的指派方法是在指派後将目前對象傳回,用來實作鍊式調用。
這樣在對象轉換的時候就可以這樣用了:
public Person convert2Person(PersonVO personVO){
return Person.builder()
.name(personVO.getName())
.age(personVO.getAge())
.address(personVO.getAddress())
.build();
}
這種方式其實也是一種建造者模式的應用,這種方式在建構對象的過程實作起來更靈活,例如如果這個對象就隻有前兩個參數有值,address是沒有内容的,那可以直接這樣寫:
public Person convert2Person(PersonVO personVO){
return Person.builder()
.name(personVO.getName())
.age(personVO.getAge())
.build();
}
在填充了兩個屬性後就直接調用build()方法區建立對象。
其實為了實作這種建立對象的方式,每次除了寫getter/setter方法後還需要寫這麼多其他的代碼,這樣是有點麻煩的,是以在日常的開發過程中,我們是沒必要寫額外的代碼來實作這種方式,可以用工具來實作。推薦一個工具包Lombok,我們的開發工具是使用IDEA,IDEA在使用Lombok時是需要下載下傳一個lombok的插件,然後在項目中依賴lombok的工具包,就可以使用了。使用了lombok後的代碼變的非常簡潔,連getter/setter方法都不用寫了。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private Long id;
private String name;
private int age;
private String address;
}
@Data 這個注解代表實作了所有非final屬性的getter/setter方法,以及重寫了toString方法和hashCode方法。
@AllArgsConstructor 這個注解代表實作了一個包含全部屬性為參數的構造方法(Person(Long id,String name,int age, String address))。
@NoArgsConstructor 這個注解代表實作了一個沒有任何參數的構造方法(Person())。
@Builder 這個注解代表實作了上面介紹的那種靈活的建立對象的建造者模式(使用這個注解時需要依賴上面3個注解,原因看這種方式的實作過程就能明白了)。
在建立對象時,使用方式沒有變化也是鍊式調用方法指派,這裡就不再寫建立對象的過程了。
其實lombok還有一些其他的注解也很強大,使用這個工具包的好處是,不但使代碼變得簡潔,也提高了開發效率。
在這裡想到了jQuery插件倡導的那個原則:“寫的更少,做的更多”。