在之前的文章中我們已經學習了設計模式的基本原則和基本分類
下面我們來介紹第一種設計模式,建立型模式的主要關注點是怎樣建立物件,它的主要特點是「將物件的建立與使用分離」。
下面我們將從下面四個方面講述五種建立者模式:
首先我們來介紹單例模式
單例模式在我的文章中已經是第三次出現了,所以下面我們做一個簡單的介紹:
單例模式主要有以下角色:
單例模式的建立主要分為三種,下面我們來一一介紹:
/*
餓漢式:類載入就會導致該單範例物件被建立
*/
/**
* 餓漢式
* 靜態變數建立類的物件
*/
public class Singleton {
// 私有構造方法
private Singleton() {}
// 在成員位置建立該類的物件並直接初始化
private static Singleton instance = new Singleton();
// 對外提供靜態方法獲取該物件
public static Singleton getInstance() {
return instance;
}
}
/**
* 餓漢式
* 在靜態程式碼塊中建立該類物件
*/
public class Singleton {
//私有構造方法
private Singleton() {}
//在成員位置建立該類的物件
private static Singleton instance;
// 直接在靜態程式碼塊中進行初始化,一旦類產生就直接建立單例物件,為餓漢式
static {
instance = new Singleton();
}
//對外提供靜態方法獲取該物件
public static Singleton getInstance() {
return instance;
}
}
/*
懶漢式:類載入不會導致該單範例物件被建立,而是首次使用該物件時才會建立
*/
/**
* 懶漢式
* 執行緒不安全
*/
public class Singleton {
//私有構造方法
private Singleton() {}
//在成員位置建立該類的物件
private static Singleton instance;
//對外提供靜態方法獲取該物件
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 懶漢式
* 執行緒安全
*/
public class Singleton {
//私有構造方法
private Singleton() {}
//在成員位置建立該類的物件
private static Singleton instance;
//對外提供靜態方法獲取該物件
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 懶漢式
* 雙重檢查方式
*/
public class Singleton {
//私有構造方法
private Singleton() {}
private static volatile Singleton instance;
//對外提供靜態方法獲取該物件
public static Singleton getInstance() {
//第一次判斷,如果instance不為null,不進入搶鎖階段,直接返回實際
if(instance == null) {
synchronized (Singleton.class) {
//搶到鎖之後再次判斷是否為空
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
/**
* 懶漢式
* 靜態內部類方式
*/
public class Singleton {
//私有構造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對外提供靜態方法獲取該物件
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
/*
惡漢式:採用非類的方法建立單例物件
*/
/**
* 惡漢式
* 列舉方式
*/
public enum Singleton {
INSTANCE;
}
單例模式通常會出現三種錯誤,其中兩種是可以處理的:
/*
反射破壞單例物件原理:
通過反射獲得類本身,通過類的構造方法建立新的類物件以破壞單例物件
預防反射破壞單例物件原理:
修改類的構造方法,使構造方式失效或者使構造方法直接返回單例物件而不是建立新的物件
*/
/*程式碼展示*/
public class Singleton implements Serializable{
// 首先我們需要擁有一個私有的構造方法(為了防止其他物件呼叫構造方法產生新物件)
private Singleton(){
// 這裡我們需要做一個判斷,如果已存在單例物件,且其他物件呼叫構造方法,直接報錯(為了預防反射獲得類然後新創物件)
if( INSTANCE != null){
throw new RuntimeException("單例物件不可重複建立");
}
System.out.println("private Singleton");
}
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
public static void otherMethod(){
System.out.println("otherMethod");
}
}
/*
反序列化破壞單例物件原理:
呼叫readResolve方法使用位元組流輸入輸出獲得新的物件
預防反序列化破壞單例物件原理:
重寫readResolve方法使其直接返回單例物件
*/
/*程式碼展示*/
public class Singleton implements Serializable {
//私有構造方法
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//對外提供靜態方法獲取該物件
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 下面是為了解決序列化反序列化破解單例模式
*/
private Object readResolve() {
return SingletonHolder.INSTANCE;
}
}
下面我們來介紹工廠模式,主要會介紹到三種工廠模式的使用
在正式介紹工廠模式之前,我們首先給出沒有工廠模式會出現的問題:
具體問題:
/*
需求:
設計一個咖啡店點餐系統。
具體想法:
設計一個咖啡類(Coffee),並定義其兩個子類(美式咖啡【AmericanCoffee】和拿鐵咖啡【LatteCoffee】);
再設計一個咖啡店類(CoffeeStore),咖啡店具有點咖啡的功能。
問題展示:
在java中,萬物皆物件,這些物件都需要建立,如果建立的時候直接new該物件,就會對該物件耦合嚴重,假如我們要更換物件,所有new物件的地方都需要修改一遍,這顯然違背了軟體設計的開閉原則。如果我們使用工廠來生產物件,我們就只和工廠打交道就可以了,徹底和物件解耦,如果要更換物件,直接在工廠裡更換該物件即可,達到了與物件解耦的目的;所以說,工廠模式最大的優點就是:解耦。
*/
簡單工廠模式不是二十三種設計模式成員,他更傾向於是一種思想
簡單工廠包含如下角色:
我們如果使用簡單工廠模式去修改前面的問題:
具體分析:
/*
工廠(factory)處理建立物件的細節,一旦有了SimpleCoffeeFactory,CoffeeStore類中的orderCoffee()就變成此物件的客戶,後期如果需要Coffee物件直接從工廠中獲取即可。這樣也就解除了和Coffee實現類的耦合,同時又產生了新的耦合,CoffeeStore物件和SimpleCoffeeFactory工廠物件的耦合,工廠物件和商品物件的耦合。
後期如果再加新品種的咖啡,我們勢必要需求修改SimpleCoffeeFactory的程式碼,違反了開閉原則。工廠類的使用者端可能有很多,比如建立美團外賣等,這樣只需要修改工廠類的程式碼,省去其他的修改操作。
*/
/* 工廠類程式碼 */
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
我們分別給出簡單工廠模式的優缺點:
封裝了建立物件的過程,可以通過引數直接獲取物件。把物件的建立和業務邏輯層分開,這樣以後就避免了修改客戶程式碼,如果要實現新產品直接修改工廠類,而不需要在原始碼中修改,這樣就降低了客戶程式碼修改的可能性,更加容易擴充套件。
增加新產品時還是需要修改工廠類的程式碼,違背了「開閉原則」。
我們以簡單工廠模式為基準可以創新出靜態工廠模式(注:靜態工廠模式也不是二十三種設計模式成員):
public class SimpleCoffeeFactory {
// 將方法設定為靜態,主類可以直接使用工廠的方法而省去建立物件的一步
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
工廠方法模式屬於二十三種設計模式成員
定義一個用於建立物件的介面,讓子類決定範例化哪個產品類物件;工廠方法使一個產品類的範例化延遲到其工廠的子類。
工廠方法模式的主要角色:
我們如果使用工廠方法模式去修改前面的問題:
具體分析:
/*
要增加產品類時也要相應地增加工廠類,不需要修改工廠類的程式碼了,這樣就解決了簡單工廠模式的缺點。
工廠方法模式是簡單工廠模式的進一步抽象。由於使用了多型性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。
*/
/* 咖啡店類 */
public class CoffeeStore {
private CoffeeFactory factory;
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee(String type) {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
/* 抽象工廠 */
public interface CoffeeFactory {
Coffee createCoffee();
}
/* 具體工廠 */
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
我們分別給出工廠方法模式的優缺點:
使用者只需要知道具體工廠的名稱就可得到所要的產品,無須知道產品的具體建立過程;
在系統增加新的產品時只需要新增具體產品類和對應的具體工廠類,無須對原工廠進行任何修改,滿足開閉原則;
每增加一個產品就要增加一個具體產品類和一個對應的具體工廠類,這增加了系統的複雜度。
抽象工廠模式屬於二十三種設計模式成員
抽象工廠模式實際上就是工廠方法模式的升級模式,他將一系列相關的類的建立都產生在同一個工廠中,使其一個工廠類產生多個產品類
抽象工廠模式的主要角色如下:
我們的例題發生了一點點的改變,我們所需要的產品類增多後,單單使用工廠方法模式顯得過於繁雜:
具體分析:
/*
題目更改:
現咖啡店業務發生改變,不僅要生產咖啡還要生產甜點,如提拉米蘇、抹茶慕斯等,要是按照工廠方法模式,需要定義提拉米蘇類、抹茶慕斯類、提拉米蘇工廠、抹茶慕斯工廠、甜點工廠類,很容易發生類爆炸情況。其中拿鐵咖啡、美式咖啡是一個產品等級,都是咖啡;提拉米蘇、抹茶慕斯也是一個產品等級;拿鐵咖啡和提拉米蘇是同一產品族(也就是都屬於義大利風味),美式咖啡和抹茶慕斯是同一產品族(也就是都屬於美式風味)。所以這個案例可以使用抽象工廠模式實現。
*/
/* 抽象工廠 */
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
/* 具體工廠 */
//美式甜點工廠
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
//義大利風味甜點工廠
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
我們首先給出抽象工廠模式的適用場景:
當需要建立的物件是一系列相互關聯或相互依賴的產品族時,如電器工廠中的電視機、洗衣機、空調等。
系統中有多個產品族,但每次只使用其中的某一族產品。如有人只喜歡穿某一個品牌的衣服和鞋。
系統中提供了產品的類庫,且所有產品的介面相同,使用者端不依賴產品範例的建立細節和內部結構。
如:輸入法換面板,一整套一起換。生成不同作業系統的程式。
我們再分別給出抽象工廠模式的優缺點:
當一個產品族中的多個物件被設計成一起工作時,它能保證使用者端始終只使用同一個產品族中的物件。
當產品族中需要增加一個新的產品時,所有的工廠類都需要進行修改。
下面我們來介紹原型模式
首先我們對原型模式做一個簡單的介紹:
原型模式包含如下角色:
原型模式關係圖如下:
原型模式的實現主要分為兩種實現方式:
其主要實現方法來自於:
我們給出一個簡單的原型模式實現:
/* 抽象原型類:實際上就是Object,已經實現了clone方法 */
/* 具體原型類 */
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具體的原型物件建立完成!");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具體原型複製成功!");
return (Realizetype) super.clone();
}
}
/* 測試存取類 */
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype r1 = new Realizetype();
Realizetype r2 = r1.clone();
System.out.println("物件r1和r2是同一個物件?" + (r1 == r2));
}
}
我們首先給出一個簡單的案例:
那麼我們可以直接使用淺克隆來完成上述案例:
//獎狀類
public class Citation implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return (this.name);
}
public void show() {
System.out.println(name + "同學:在2020學年第一學期中表現優秀,被評為三好學生。特發此狀!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//測試存取類
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("張三");
//複製獎狀
Citation c2 = c1.clone();
//將獎狀的名字修改李四
c2.setName("李四");
c1.show();
c2.show();
}
}
但是當我們修改了內部屬性,將簡單物件改為了參照物件時,兩者的參照物件都會指向原本的地址,就會導致修改一處造成全部修改
所以我們可以採用深克隆來完成這個操作:
//獎狀類
public class Citation implements Cloneable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
void show() {
System.out.println(stu.getName() + "同學:在2020學年第一學期中表現優秀,被評為三好學生。特發此狀!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//學生類
public class Student {
private String name;
private String address;
public Student(String name, String address) {
this.name = name;
this.address = address;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
// 測試存取類
public class CitationTest1 {
public static void main(String[] args) throws Exception {
Citation c1 = new Citation();
Student stu = new Student("張三", "西安");
c1.setStu(stu);
//建立物件輸出流物件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//將c1物件寫出到檔案中
oos.writeObject(c1);
oos.close();
//建立物件出入流物件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//讀取物件
Citation c2 = (Citation) ois.readObject();
//獲取c2獎狀所屬學生物件
Student stu1 = c2.getStu();
stu1.setName("李四");
//判斷stu物件和stu1物件是否是同一個物件
System.out.println("stu和stu1是同一個物件?" + (stu == stu1));
c1.show();
c2.show();
}
}
最後我們來介紹建造者模式,它將一個複雜物件的構建與表示分離,使得同樣的構建過程可以建立不同的表示。
我們首先來介紹一下建造者模式:
建造者(Builder)模式包含如下角色:
抽象建造者類(Builder):這個介面規定要實現複雜物件的那些部分的建立,並不涉及具體的部件物件的建立。
具體建造者類(ConcreteBuilder):實現 Builder 介面,完成複雜產品的各個部件的具體建立方法。在構造過程完成後,提供產品的範例。
產品類(Product):要建立的複雜物件。
指揮者類(Director):呼叫具體建造者來建立複雜物件的各個部分,在指導者中不涉及具體產品的資訊,只負責保證物件各部分完整建立或按某種順序建立。
建造者模式關係圖如下:
我們同樣通過一個簡單的例子來展示建造者模式:
具體分析:
/*
題目介紹:
生產自行車是一個複雜的過程,它包含了車架,車座等元件的生產。
而車架又有碳纖維,鋁合金等材質的,車座有橡膠,真皮等材質。
對於自行車的生產就可以使用建造者模式。
關係圖介紹:
這裡Director是指揮者;
Bike是產品,包含車架,車座等元件;
Builder是抽象建造者,MobikeBuilder和OfoBuilder是具體的建造者;
*/
/* 具體程式碼展示 */
//自行車類
public class Bike {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
// 抽象 builder 類
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
//摩拜單車Builder類
public class MobikeBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("鋁合金車架");
}
@Override
public void buildSeat() {
mBike.setSeat("真皮車座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//ofo單車Builder類
public class OfoBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("碳纖維車架");
}
@Override
public void buildSeat() {
mBike.setSeat("橡膠車座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//指揮者類
public class Director {
private Builder mBuilder;
public Director(Builder builder) {
mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}
//測試類
public class Client {
public static void main(String[] args) {
showBike(new OfoBuilder());
showBike(new MobikeBuilder());
}
private static void showBike(Builder builder) {
Director director = new Director(builder);
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
/* 部分程式碼優化:有些情況下需要簡化系統結構,可以把指揮者類和抽象建造者進行結合 */
// 抽象 builder 類
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
public Bike construct() {
this.buildFrame();
this.BuildSeat();
return this.createBike();
}
}
首先我們給出建造者模式的主要適用場景:
然後我們給出建造者模式的優點:
最後我們給出建造者模式的缺點:
建造者模式的另一個營業場景也包括:
我們給出一個簡單例子:
/* 原版程式碼 */
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainboard() {
return mainboard;
}
public void setMainboard(String mainboard) {
this.mainboard = mainboard;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
//構建Phone物件
Phone phone = new Phone("intel","三星螢幕","金士頓","華碩");
System.out.println(phone);
}
}
/* 建造者模式程式碼 */
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("華碩")
.memory("金士頓")
.screen("三星")
.build();
System.out.println(phone);
}
}
關於建立者模式我們就介紹到這裡,後面我會繼續更新二十三種設計模式,希望能給你帶來幫助~
該文章屬於學習內容,具體參考B站黑馬程式設計師的Java設計模式詳解
這裡附上視訊連結:22.設計模式-建立型模式-單例設計模式概述_嗶哩嗶哩_bilibili