欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報激活碼778899分享:Java中23種設計模式

柚子快報激活碼778899分享:Java中23種設計模式

http://yzkb.51969.com/

一、創(chuàng)建型模式

1.單例模式(Singleton Pattern)

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種模式涉及到一個單一的類,該類負責創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

1.1 餓漢式

特點:類加載時就初始化,線程安全

// 構造方法私有化

private Singleton() {

}

// 餓漢式創(chuàng)建單例對象

private static Singleton singleton = new Singleton();

public static Singleton getInstance() {

return singleton;

}

1.2 懶漢式

特點:第一次調用才初始化,避免內存浪費。

/*

* 懶漢式創(chuàng)建單例模式 由于懶漢式是非線程安全, 所以加上線程鎖保證線程安全

*/

private static Singleton singleton;

public static synchronized Singleton getInstance() {

if (singleton == null) {

singleton = new Singleton();

}

return singleton;

}

1.3 雙重檢驗鎖(double check lock)(DCL)

特點:安全且在多線程情況下能保持高性能

private volatile static Singleton singleton;

private Singleton (){}

public static Singleton getInstance() {

if (singleton == null) {

synchronized (Singleton.class) {

if (singleton == null) {

singleton = new Singleton();

}

}

}

return singleton;

}

1.4 靜態(tài)內部類

特點:效果類似DCL,只適用于靜態(tài)域

private static class SingletonHolder {

private static final Singleton INSTANCE = new Singleton();

}

private Singleton (){}

public static final Singleton getInstance() {

return SingletonHolder.INSTANCE;

}

1.5 枚舉

特點:自動支持序列化機制,絕對防止多次實例化

public enum Singleton {

INSTANCE;

}

1.6 破壞單例的幾種方式與解決方法

1.6.1 反序列化

Singleton singleton = Singleton.getInstance();

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt"));

oos.writeObject(singleton);

oos.flush();

oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt"));

Singleton singleton1 = (Singleton)ois.readObject();

ois.close();

System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@50134894

System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@5ccd43c2

可以看到反序列化后,兩個對象的地址不一樣了,那么這就是違背了單例模式的原則了,解決方法只需要在單例類里加上一個readResolve()方法即可,原因就是在反序列化的過程中,會檢測readResolve()方法是否存在,如果存在的話就會反射調用readResolve()這個方法。

private Object readResolve() {

return singleton;

}

//com.ruoyi.base.mapper.Singleton@50134894

//com.ruoyi.base.mapper.Singleton@50134894

1.6.2 反射

Singleton singleton = Singleton.getInstance();

Class singletonClass = Singleton.class;

Constructor constructor = singletonClass.getDeclaredConstructor();

constructor.setAccessible(true);

Singleton singleton1 = constructor.newInstance();

System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@32a1bec0

System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@22927a81

同樣可以看到,兩個對象的地址不一樣,這同樣是違背了單例模式的原則,解決辦法為使用一個布爾類型的標記變量標記一下即可,代碼如下:

private static boolean singletonFlag = false;

private Singleton() {

if (singleton != null || singletonFlag) {

throw new RuntimeException("試圖用反射破壞異常");

}

singletonFlag = true;

}

但是這種方法假如使用了反編譯,獲得了這個標記變量,同樣可以破壞單例,代碼如下:

Class singletonClass = Singleton.class;

Constructor constructor = singletonClass.getDeclaredConstructor();

constructor.setAccessible(true);

Singleton singleton = constructor.newInstance();

System.out.println(singleton); // com.ruoyi.base.mapper.Singleton@32a1bec0

Field singletonFlag = singletonClass.getDeclaredField("singletonFlag");

singletonFlag.setAccessible(true);

singletonFlag.setBoolean(singleton, false);

Singleton singleton1 = constructor.newInstance();

System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton@5e8c92f4

如果想使單例不被破壞,那么應該使用枚舉的方式去實現(xiàn)單例模式,枚舉是不可以被反射破壞單例的。

1.7 容器式單例

當程序中的單例對象非常多的時候,則可以使用容器對所有單例對象進行管理,如下:

public class ContainerSingleton {

private ContainerSingleton() {}

private static Map singletonMap = new ConcurrentHashMap<>();

public static Object getInstance(Class clazz) throws Exception {

String className = clazz.getName();

// 當容器中不存在目標對象時則先生成對象再返回該對象

if (!singletonMap.containsKey(className)) {

Object instance = Class.forName(className).newInstance();

singletonMap.put(className, instance);

return instance;

}

// 否則就直接返回容器里的對象

return singletonMap.get(className);

}

public static void main(String[] args) throws Exception {

SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);

SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);

System.out.println(instance1 == instance2); // true

}

}

1.8 ThreadLocal單例

不保證整個應用全局唯一,但保證線程內部全局唯一,以空間換時間,且線程安全。

public class ThreadLocalSingleton {

private ThreadLocalSingleton(){}

private static final ThreadLocal threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton());

public static ThreadLocalSingleton getInstance(){

return threadLocalInstance.get();

}

public static void main(String[] args) {

new Thread(() -> {

System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());

System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());

}).start();

new Thread(() -> {

System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());

System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());

}).start();

// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3

// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc

// Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3

// Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc

}

}

可以看到上面線程0和1他們的對象是不一樣的,但是線程內部,他們的對象是一樣的,這就是線程內部保證唯一。

1.9 總結

適用場景:

需要確保在任何情況下絕對只需要一個實例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。

優(yōu)點:

在內存中只有一個實例,減少了內存開銷??梢员苊鈱Y源的多重占用。設置全局訪問點,嚴格控制訪問。

缺點:

沒有接口,擴展困難。如果要擴展單例對象,只有修改代碼,沒有其它途徑。

2.工廠方法模式(Factory Method)

2.1 簡單工廠模式

簡單工廠模式不是23種設計模式之一,他可以理解為工廠模式的一種簡單的特殊實現(xiàn)。

2.1.1 基礎版

// 工廠類

public class CoffeeFactory {

public Coffee create(String type) {

if ("americano".equals(type)) {

return new Americano();

}

if ("mocha".equals(type)) {

return new Mocha();

}

if ("cappuccino".equals(type)) {

return new Cappuccino();

}

return null;

}

}

// 產品基類

public interface Coffee {

}

// 產品具體類,實現(xiàn)產品基類接口

public class Cappuccino implements Coffee {

}

基礎版是最基本的簡單工廠的寫法,傳一個參數過來,判斷是什么類型的產品,就返回對應的產品類型。但是這里有一個問題,就是參數是字符串的形式,這就很容易會寫錯,比如少寫一個字母,或者小寫寫成了大寫,就會無法得到自己想要的產品類了,同時如果新加了產品,還得在工廠類的創(chuàng)建方法中繼續(xù)加if,于是就有了升級版的寫法。

2.1.2 升級版

// 使用反射創(chuàng)建對象

// 加一個static變?yōu)殪o態(tài)工廠

public static Coffee create(Class clazz) throws Exception {

if (clazz != null) {

return clazz.newInstance();

}

return null;

}

升級版就很好的解決基礎版的問題,在創(chuàng)建的時候在傳參的時候不僅會有代碼提示,保證不會寫錯,同時在新增產品的時候只需要新增產品類即可,也不需要再在工廠類的方法里面新增代碼了。

2.1.3 總結

適用場景:

工廠類負責創(chuàng)建的對象較少。客戶端只需要傳入工廠類的參數,對于如何創(chuàng)建的對象的邏輯不需要關心。

優(yōu)點:

只需要傳入一個正確的參數,就可以獲取你所需要的對象,無須知道創(chuàng)建的細節(jié)。

缺點:

工廠類的職責相對過重,增加新的產品類型的時需要修改工廠類的判斷邏輯,違背了開閉原則。不易于擴展過于復雜的產品結構。

2.2 工廠方法模式

工廠方法模式是指定義一個創(chuàng)建對象的接口,讓實現(xiàn)這個接口的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行。

工廠方法模式主要有以下幾種角色:

抽象工廠(Abstract Factory):提供了創(chuàng)建產品的接口,調用者通過它訪問具體工廠的工廠方法來創(chuàng)建產品。具體工廠(ConcreteFactory):主要是實現(xiàn)抽象工廠中的抽象方法,完成具體產品的創(chuàng)建。抽象產品(Product):定義了產品的規(guī)范,描述了產品的主要特性和功能。具體產品(ConcreteProduct):實現(xiàn)了抽象產品角色所定義的接口,由具體工廠來創(chuàng)建,它和具體工廠之間一一對應。

2.2.1 代碼實現(xiàn)

// 抽象工廠

public interface CoffeeFactory {

Coffee create();

}

// 具體工廠

public class CappuccinoFactory implements CoffeeFactory {

@Override

public Coffee create() {

return new Cappuccino();

}

}

// 抽象產品

public interface Coffee {

}

// 具體產品

public class Cappuccino implements Coffee {

}

2.2.2 總結

適用場景:

創(chuàng)建對象需要大量的重復代碼。客戶端(應用層)不依賴于產品類實例如何被創(chuàng)建和實現(xiàn)等細節(jié)。一個類通過其子類來指定創(chuàng)建哪個對象。

優(yōu)點:

用戶只需要關系所需產品對應的工廠,無須關心創(chuàng)建細節(jié)。加入新產品符合開閉原則,提高了系統(tǒng)的可擴展性。

缺點:

類的數量容易過多,增加了代碼結構的復雜度。增加了系統(tǒng)的抽象性和理解難度。

3.抽象工廠模式(Abstract Factory)

抽象工廠模式是指提供一個創(chuàng)建一系列相關或相互依賴對象的接口,無須指定他們具體的類。

?工廠方法模式中考慮的是一類產品的生產,如電腦廠只生產電腦,電話廠只生產電話,這種工廠只生產同種類的產品,同種類產品稱為同等級產品,也就是說,工廠方法模式只考慮生產同等級的產品,但是現(xiàn)實生活中許多工廠都是綜合型工廠,能生產多等級(種類)的產品,如上面說的電腦和電話,本質上他們都屬于電器,那么他們就能在電器廠里生產出來,而抽象工廠模式就將考慮多等級產品的生產,將同一個具體工廠所生產的位于不同等級的一組產品稱為一個產品族,如上圖所示縱軸是產品等級,也就是同一類產品;橫軸是產品族,也就是同一品牌的產品,同一品牌的產品產自同一個工廠。

抽象工廠模式的主要角色如下:

抽象工廠(Abstract Factory):提供了創(chuàng)建產品的接口,它包含多個創(chuàng)建產品的方法,可以創(chuàng)建多個不同等級的產品。具體工廠(Concrete Factory):主要是實現(xiàn)抽象工廠中的多個抽象方法,完成具體產品的創(chuàng)建。抽象產品(Product):定義了產品的規(guī)范,描述了產品的主要特性和功能,抽象工廠模式有多個抽象產品。具體產品(ConcreteProduct):實現(xiàn)了抽象產品角色所定義的接口,由具體工廠來創(chuàng)建,它同具體工廠之間是多對一的關系。

3.1 代碼實現(xiàn)

// 咖啡店 抽象工廠

public interface CoffeeShopFactory {

// 咖啡類

Coffee createCoffee();

// 甜點類

Dessert createDessert();

}

// 美式風格工廠

public class AmericanFactory implements CoffeeShopFactory {

@Override

public Coffee createCoffee() {

return new Americano();

}

@Override

public Dessert createDessert() {

return new Cheesecake();

}

}

// 意式風格工廠

public class ItalyFactory implements CoffeeShopFactory {

@Override

public Coffee createCoffee() {

return new Cappuccino();

}

@Override

public Dessert createDessert() {

return new Tiramisu();

}

}

類圖

3.2 總結

產品族:一系列相關的產品,整合到一起有關聯(lián)性

產品等級:同一個繼承體系

適用場景:

客戶端(應用層)不依賴于產品類實例如何被創(chuàng)建和實現(xiàn)等細節(jié)。強調一系列相關的產品對象(屬于同一產品族)一起使用創(chuàng)建對象需要大量重復的代碼。提供一個產品類的庫,所有的產品以同樣的接口出現(xiàn),從而使客戶端不依賴于具體實現(xiàn)。

優(yōu)點:

當一個產品族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象。

缺點:

當產品族中需要增加一個新的產品時,所有的工廠類都需要進行修改。

4.原型模式(Prototype)

原型模式是指原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。調用者不需要知道任何創(chuàng)建細節(jié),不調用構造函數。

原型模式包含如下角色:

抽象原型類:規(guī)定了具體原型對象必須實現(xiàn)的的 clone() 方法。具體原型類:實現(xiàn)抽象原型類的 clone() 方法,它是可被復制的對象。訪問類:使用具體原型類中的 clone() 方法來復制新的對象。

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Student implements Cloneable {

private String name;

private String sex;

private Integer age;

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

public static void main(String[] args) throws Exception{

Student stu1 = new Student("張三", "男", 18);

Student stu2 = (Student)stu1.clone();

stu2.setName("李四");

System.out.println(stu1);// Student(name=張三, sex=男, age=18)

System.out.println(stu2);// Student(name=李四, sex=男, age=18)

}

}

可以看到,把一個學生復制過來,只是改了姓名而已,其他屬性完全一樣沒有改變,需要注意的是,一定要在被拷貝的對象上實現(xiàn)Cloneable接口,否則會拋出CloneNotSupportedException異常。

4.1 淺克隆

創(chuàng)建一個新對象,新對象的屬性和原來對象完全相同,對于非基本類型屬性,仍指向原有屬性所指向的對象的內存地址。

@Data

public class Clazz implements Cloneable {

private String name;

private Student student;

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Student implements Serializable {

private String name;

private String sex;

private Integer age;

}

public static void main(String[] args) throws Exception{

Clazz clazz1 = new Clazz();

clazz1.setName("高三一班");

Student stu1 = new Student("張三", "男", 18);

clazz1.setStudent(stu1);

System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=張三, sex=男, age=18))

Clazz clazz2 = (Clazz)clazz1.clone();

Student stu2 = clazz2.getStudent();

stu2.setName("李四");

System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18))

System.out.println(clazz2); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18))

}

可以看到,當修改了stu2的姓名時,stu1的姓名同樣也被修改了,這說明stu1和stu2是同一個對象,這就是淺克隆的特點,對具體原型類中的引用類型的屬性進行引用的復制。同時,這也可能是淺克隆所帶來的弊端,因為結合該例子的原意,顯然是想在班級中新增一名叫李四的學生,而非讓所有的學生都改名叫李四,于是我們這里就要使用深克隆。

4.2 深克隆

創(chuàng)建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。

@Data

public class Clazz implements Cloneable, Serializable {

private String name;

private Student student;

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

protected Object deepClone() throws IOException, ClassNotFoundException {

ByteArrayOutputStream bos = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(bos);

oos.writeObject(this);

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bis);

return ois.readObject();

}

}

public static void main(String[] args) throws Exception{

Clazz clazz1 = new Clazz();

clazz1.setName("高三一班");

Student stu1 = new Student("張三", "男", 18);

clazz1.setStudent(stu1);

Clazz clazz3 = (Clazz)clazz1.deepClone();

Student stu3 = clazz3.getStudent();

stu3.setName("王五");

System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=張三, sex=男, age=18))

System.out.println(clazz3); // Clazz(name=高三一班, student=Student(name=王五, sex=男, age=18))

}

可以看到,當修改了stu3的姓名時,stu1的姓名并沒有被修改了,這說明stu3和stu1已經是不同的對象了,說明Clazz中的Student也被克隆了,不再指向原有對象地址,這就是深克隆。這里需要注意的是,Clazz類和Student類都需要實現(xiàn)Serializable接口,否則會拋出NotSerializableException異常。

4.3 克隆破壞單例與解決辦法

PS:上面例子有的代碼,這里便不重復寫了,可以在上面的代碼基礎上添加以下代碼

// Clazz類

private static Clazz clazz = new Clazz();

private Clazz(){}

public static Clazz getInstance() {return clazz;}

// 測試

public static void main(String[] args) throws Exception{

Clazz clazz1 = Clazz.getInstance();

Clazz clazz2 = (Clazz)clazz1.clone();

System.out.println(clazz1 == clazz2); // false

}

可以看到clazz1和clazz2并不相等,也就是說他們并不是同一個對象,也就是單例被破壞了。

解決辦法也很簡單,首先第一個就是不實現(xiàn)Cloneable接口即可,但是不實現(xiàn)Cloneable接口進行clone則會拋出CloneNotSupportedException異常。第二個方法就是重寫clone()方法即可,如下:

@Override

protected Object clone() throws CloneNotSupportedException {

return clazz;

}

// 測試輸出

System.out.println(clazz1 == clazz2) // true

可以看到,上面clazz1和clazz2是相等的,即單例沒有被破壞。

另外我們知道,單例就是只有一個實例對象,如果重寫了clone()方法保證單例的話,那么通過克隆出來的對象則不可以重新修改里面的屬性,因為修改以后就會連同克隆對象一起被修改,所以是需要單例還是克隆,在實際應用中需要好好衡量。

4.4 總結

適用場景:

類初始化消耗資源較多。new產生的一個對象需要非常繁瑣的過程(數據準備、訪問權限等)。構造函數比較復雜。循環(huán)體中生產大量對象時。

優(yōu)點:

性能優(yōu)良,Java自帶的原型模式是基于內存二進制流的拷貝,比直接new一個對象性能上提升了許多??梢允褂蒙羁寺》绞奖4鎸ο蟮臓顟B(tài),使用原型模式將對象復制一份并將其狀態(tài)保存起來,簡化了創(chuàng)建的過程。

缺點:

必須配備克?。ɑ蛘呖煽截悾┓椒?。當對已有類進行改造的時候,需要修改代碼,違反了開閉原則。深克隆、淺克隆需要運用得當。

5.建造者模式(Builder)

建造者模式是將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創(chuàng)建不同的表示。用戶只需指定需要建造的類型就可以獲得對象,建造過程及細節(jié)不需要了解。

建造者(Builder)模式包含如下角色:

抽象建造者類(Builder):這個接口規(guī)定要實現(xiàn)復雜對象的那些部分的創(chuàng)建,并不涉及具體的部件對象的創(chuàng)建。具體建造者類(ConcreteBuilder):實現(xiàn) Builder 接口,完成復雜產品的各個部件的具體創(chuàng)建方法。在構造過程完成后,提供產品的實例。產品類(Product):要創(chuàng)建的復雜對象。指揮者類(Director):調用具體建造者來創(chuàng)建復雜對象的各個部分,在指導者中不涉及具體產品的信息,只負責保證對象各部分完整創(chuàng)建或按某種順序創(chuàng)建。

5.1 常規(guī)寫法

//產品類 電腦

@Data

public class Computer {

private String motherboard;

private String cpu;

private String memory;

private String disk;

private String gpu;

private String power;

private String heatSink;

private String chassis;

}

// 抽象 builder類(接口) 組裝電腦

public interface ComputerBuilder {

Computer computer = new Computer();

void buildMotherboard();

void buildCpu();

void buildMemory();

void buildDisk();

void buildGpu();

void buildHeatSink();

void buildPower();

void buildChassis();

Computer build();

}

// 具體 builder類 華碩ROG全家桶電腦(手動狗頭)

public class AsusComputerBuilder implements ComputerBuilder {

@Override

public void buildMotherboard() {

computer.setMotherboard("Extreme主板");

}

@Override

public void buildCpu() {

computer.setCpu("Inter 12900KS");

}

@Override

public void buildMemory() {

computer.setMemory("芝奇幻峰戟 16G*2");

}

@Override

public void buildDisk() {

computer.setDisk("三星980Pro 2T");

}

@Override

public void buildGpu() {

computer.setGpu("華碩3090Ti 水猛禽");

}

@Override

public void buildHeatSink() {

computer.setHeatSink("龍神二代一體式水冷");

}

@Override

public void buildPower() {

computer.setPower("雷神二代1200W");

}

@Override

public void buildChassis() {

computer.setChassis("太陽神機箱");

}

@Override

public Computer build() {

return computer;

}

}

// 指揮者類 指揮該組裝什么電腦

@AllArgsConstructor

public class ComputerDirector {

private ComputerBuilder computerBuilder;

public Computer construct() {

computerBuilder.buildMotherboard();

computerBuilder.buildCpu();

computerBuilder.buildMemory();

computerBuilder.buildDisk();

computerBuilder.buildGpu();

computerBuilder.buildHeatSink();

computerBuilder.buildPower();

computerBuilder.buildChassis();

return computerBuilder.build();

}

}

// 測試

public static void main(String[] args) {

ComputerDirector computerDirector = new ComputerDirector(new AsusComputerBuilder());

// Computer(motherboard=Extreme主板, cpu=Inter 12900KS, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=華碩3090Ti 水猛禽, power=雷神二代1200W, heatSink=龍神二代一體式水冷, chassis=太陽神機箱)

System.out.println(computerDirector.construct());

}

上面示例是建造者模式的常規(guī)用法,指揮者類ComputerDirector在建造者模式中具有很重要的作用,它用于指導具體構建者如何構建產品,控制調用先后次序,并向調用者返回完整的產品類,但是有些情況下需要簡化系統(tǒng)結構,可以把指揮者類和抽象建造者進行結合,于是就有了下面的簡化寫法。

5.2 簡化寫法

// 把指揮者類和抽象建造者合在一起的簡化建造者類

public class SimpleComputerBuilder {

private Computer computer = new Computer();

public void buildMotherBoard(String motherBoard){

computer.setMotherboard(motherBoard);

}

public void buildCpu(String cpu){

computer.setCpu(cpu);

}

public void buildMemory(String memory){

computer.setMemory(memory);

}

public void buildDisk(String disk){

computer.setDisk(disk);

}

public void buildGpu(String gpu){

computer.setGpu(gpu);

}

public void buildPower(String power){

computer.setPower(power);

}

public void buildHeatSink(String heatSink){

computer.setHeatSink(heatSink);

}

public void buildChassis(String chassis){

computer.setChassis(chassis);

}

public Computer build(){

return computer;

}

}

// 測試

public static void main(String[] args) {

SimpleComputerBuilder simpleComputerBuilder = new SimpleComputerBuilder();

simpleComputerBuilder.buildMotherBoard("Extreme主板");

simpleComputerBuilder.buildCpu("Inter 12900K");

simpleComputerBuilder.buildMemory("芝奇幻峰戟 16G*2");

simpleComputerBuilder.buildDisk("三星980Pro 2T");

simpleComputerBuilder.buildGpu("華碩3090Ti 水猛禽");

simpleComputerBuilder.buildPower("雷神二代1200W");

simpleComputerBuilder.buildHeatSink("龍神二代一體式水冷");

simpleComputerBuilder.buildChassis("太陽神機箱");

// Computer(motherboard=Extreme主板, cpu=Inter 12900K, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=華碩3090Ti 水猛禽, power=雷神二代1200W, heatSink=龍神二代一體式水冷, chassis=太陽神機箱)

System.out.println(simpleComputerBuilder.build());

}

可以看到,對比常規(guī)寫法,這樣寫確實簡化了系統(tǒng)結構,但同時也加重了建造者類的職責,也不是太符合單一職責原則,如果construct() 過于復雜,建議還是封裝到 Director 中。

5.3 鏈式寫法

// 鏈式寫法建造者類

public class SimpleComputerBuilder {

private Computer computer = new Computer();

public SimpleComputerBuilder buildMotherBoard(String motherBoard){

computer.setMotherboard(motherBoard);

return this;

}

public SimpleComputerBuilder buildCpu(String cpu){

computer.setCpu(cpu);

return this;

}

public SimpleComputerBuilder buildMemory(String memory){

computer.setMemory(memory);

return this;

}

public SimpleComputerBuilder buildDisk(String disk){

computer.setDisk(disk);

return this;

}

public SimpleComputerBuilder buildGpu(String gpu){

computer.setGpu(gpu);

return this;

}

public SimpleComputerBuilder buildPower(String power){

computer.setPower(power);

return this;

}

public SimpleComputerBuilder buildHeatSink(String heatSink){

computer.setHeatSink(heatSink);

return this;

}

public SimpleComputerBuilder buildChassis(String chassis){

computer.setChassis(chassis);

return this;

}

public Computer build(){

return computer;

}

}

// 測試

public static void main(String[] args) {

Computer asusComputer = new SimpleComputerBuilder().buildMotherBoard("Extreme主板")

.buildCpu("Inter 12900K")

.buildMemory("芝奇幻峰戟 16G*2")

.buildDisk("三星980Pro 2T")

.buildGpu("華碩3090Ti 水猛禽")

.buildPower("雷神二代1200W")

.buildHeatSink("龍神二代一體式水冷")

.buildChassis("太陽神機箱").build();

System.out.println(asusComputer);

}

可以看到,其實鏈式寫法與普通寫法的區(qū)別并不大,只是在建造者類組裝部件的時候,同時將建造者類返回即可,使用鏈式寫法使用起來更方便,某種程度上也可以提高開發(fā)效率。從軟件設計上,對程序員的要求比較高。比較常見的mybatis-plus中的條件構造器就是使用的這種鏈式寫法。

5.4 總結

適用場景:

適用于創(chuàng)建對象需要很多步驟,但是步驟順序不一定固定。如果一個對象有非常復雜的內部結構(屬性),把復雜對象的創(chuàng)建和使用進行分離。

優(yōu)點:

封裝性好,創(chuàng)建和使用分離。擴展性好,建造類之間獨立、一定程度上解耦。

缺點:

產生多余的Builder對象。產品內部發(fā)生變化,建造者都要修改,成本較大。

與工廠模式的區(qū)別:

建造者模式更注重方法的調用順序,工廠模式更注重創(chuàng)建對象。創(chuàng)建對象的力度不同,建造者模式創(chuàng)建復雜的對象,由各種復雜的部件組成,工廠模式創(chuàng)建出來的都一樣。關注點不同,工廠模式只需要把對象創(chuàng)建出來就可以了,而建造者模式中不僅要創(chuàng)建出這個對象,還要知道這個對象由哪些部件組成。建造者模式根據建造過程中的順序不一樣,最終的對象部件組成也不一樣。

與抽象工廠模式的區(qū)別:

抽象工廠模式實現(xiàn)對產品族的創(chuàng)建,一個產品族是這樣的一系列產品:具有不同分類維度的產品組合,采用抽象工廠模式則是不需要關心構建過程,只關心什么產品由什么工廠生產即可。建造者模式則是要求按照指定的藍圖建造產品,它的主要目的是通過組裝零配件而產生一個新產品。建造者模式所有函數加到一起才能生成一個對象,抽象工廠一個函數生成一個對象

二、結構型模式

1.代理模式(Proxy Pattern)

代理模式是指為其他對象提供一種代理,以控制對這個對象的訪問。代理對象在訪問對象和目標對象之間起到中介作用。

Java中的代理按照代理類生成時機不同又分為靜態(tài)代理和動態(tài)代理。靜態(tài)代理代理類在編譯期就生成,而動態(tài)代理代理類則是在Java運行時動態(tài)生成。動態(tài)代理又有JDK代理和CGLib代理兩種。

代理(Proxy)模式分為三種角色:

抽象角色(Subject): 通過接口或抽象類聲明真實角色和代理對象實現(xiàn)的業(yè)務方法。真實角色(Real Subject): 實現(xiàn)了抽象角色中的具體業(yè)務,是代理對象所代表的真實對象,是最終要引用的對象。代理角色(Proxy) : 提供了與真實角色相同的接口,其內部含有對真實角色的引用,它可以訪問、控制或擴展真實角色的功能。

1.1 靜態(tài)代理

靜態(tài)代理就是指我們在給一個類擴展功能的時候,我們需要去書寫一個靜態(tài)的類,相當于在之前的類上套了一層,這樣我們就可以在不改變之前的類的前提下去對原有功能進行擴展,靜態(tài)代理需要代理對象和目標對象實現(xiàn)一樣的接口。

// 火車站接口,有賣票功能

public interface TrainStation {

void sellTickets();

}

// 廣州火車站賣票

public class GuangzhouTrainStation implements TrainStation {

@Override

public void sellTickets() {

System.out.println("廣州火車站賣票啦");

}

}

// 代售點賣票(代理類)

public class ProxyPoint implements TrainStation {

// 目標對象(代理火車站售票)

private TrainStation station = new GuangzhouTrainStation();

@Override

public void sellTickets() {

System.out.println("代售加收5%手續(xù)費");

station.sellTickets();

}

public static void main(String[] args) {

ProxyPoint proxyPoint = new ProxyPoint();

// 代售加收5%手續(xù)費

// 廣州火車站賣票啦

proxyPoint.sellTickets();

}

}

// 測試

public static void main(String[] args) {

ProxyPoint proxyPoint = new ProxyPoint();

// 代售加收5%手續(xù)費

// 火車站賣票啦

proxyPoint.sellTickets();

}

可以從上面代碼看到,我們訪問的是ProxyPoint對象,也就是說ProxyPoint是作為訪問對象和目標對象的中介的,同時也對sellTickets方法進行了增強(代理點收取加收5%手續(xù)費)。

靜態(tài)代理的優(yōu)點是實現(xiàn)簡單,容易理解,只要確保目標對象和代理對象實現(xiàn)共同的接口或繼承相同的父類就可以在不修改目標對象的前提下進行擴展。

而缺點也比較明顯,那就是代理類和目標類必須有共同接口(父類),并且需要為每一個目標類維護一個代理類,當需要代理的類很多時會創(chuàng)建出大量代理類。一旦接口或父類的方法有變動,目標對象和代理對象都需要作出調整。

1.2 動態(tài)代理

代理類在代碼運行時創(chuàng)建的代理稱之為動態(tài)代理。動態(tài)代理中代理類并不是預先在Java代碼中定義好的,而是運行時由JVM動態(tài)生成,并且可以代理多個目標對象。

1.2.1 jdk動態(tài)代理

JDK動態(tài)代理是Java JDK自帶的一個動態(tài)代理實現(xiàn), 位于java.lang.reflect包下。

// 火車站接口,有賣票功能

public interface TrainStation {

void sellTickets();

}

// 廣州火車站賣票

public class GuangzhouTrainStation implements TrainStation {

@Override

public void sellTickets() {

System.out.println("廣州火車站賣票啦");

}

}

// 深圳火車站賣票

public class ShenzhenTrainStation implements TrainStation {

@Override

public void sellTickets() {

System.out.println("深圳火車站賣票啦");

}

}

// 代售點賣票(代理類)

public class ProxyPoint implements InvocationHandler {

private TrainStation trainStation;

public TrainStation getProxyObject(TrainStation trainStation) {

this.trainStation = trainStation;

Class clazz = trainStation.getClass();

return (TrainStation) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("代售火車票收取5%手續(xù)費");

return method.invoke(this.trainStation, args);

}

}

// 測試

public static void main(String[] args) {

ProxyPoint proxy = new ProxyPoint();

TrainStation guangzhouTrainStation = proxy.getProxyObject(new GuangzhouTrainStation());

// 代售火車票收取5%手續(xù)費

// 廣州火車站賣票啦

guangzhouTrainStation.sellTickets();

TrainStation shenzhenTrainStation = proxy.getProxyObject(new ShenzhenTrainStation());

// 代售火車票收取5%手續(xù)費

// 深圳火車站賣票啦

shenzhenTrainStation.sellTickets();

}

優(yōu)點:

使用簡單、維護成本低。Java原生支持,不需要任何依賴。解決了靜態(tài)代理存在的多數問題。

缺點:

由于使用反射,性能會比較差。只支持接口實現(xiàn),不支持繼承, 不滿足所有業(yè)務場景。

1.2.2 CGLIB動態(tài)代理

CGLIB是一個強大的、高性能的代碼生成庫。它可以在運行期擴展Java類和接口,其被廣泛應用于AOP框架中(Spring、dynaop)中, 用以提供方法攔截。CGLIB比JDK動態(tài)代理更強的地方在于它不僅可以接管Java接口, 還可以接管普通類的方法。

cglib

cglib

${cglib-version}

// 代售點賣票(代理類)

public class ProxyPoint implements MethodInterceptor {

public TrainStation getProxyObject(Class trainStation) {

//創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類,下一步就是設置幾個參數

Enhancer enhancer =new Enhancer();

//設置父類的字節(jié)碼對象

enhancer.setSuperclass(trainStation);

//設置回調函數

enhancer.setCallback(this);

//創(chuàng)建代理對象并返回

return (TrainStation) enhancer.create();

}

@Override

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

System.out.println("代售火車票收取5%手續(xù)費");

return methodProxy.invokeSuper(o, objects);

}

}

// 測試

public static void main(String[] args) {

ProxyPoint proxy = new ProxyPoint();

TrainStation guangzhouTrainStation = proxy.getProxyObject(GuangzhouTrainStation.class);

// 代售火車票收取5%手續(xù)費

// 廣州火車站賣票啦

guangzhouTrainStation.sellTickets();

TrainStation shenzhenTrainStation = proxy.getProxyObject(ShenzhenTrainStation.class);

// 代售火車票收取5%手續(xù)費

// 深圳火車站賣票啦

shenzhenTrainStation.sellTickets();

}

1.3 總結

應用場景:

保護目標對象。增強目標對象。

優(yōu)點:

代理模式能將代理對象與真實被調用的目標對象分離。一定程度上降低了系統(tǒng)的耦合程度,易于擴展。代理可以起到保護目標對象的作用。增強目標對象的職責。

缺點:

代理模式會造成系統(tǒng)設計中類的數目增加。在客戶端和目標對象之間增加了一個代理對象,請求處理速度變慢。增加了系統(tǒng)的復雜度。

兩種動態(tài)代理的對比:

JDK動態(tài)代理的特點:

需要實現(xiàn)InvocationHandler接口, 并重寫invoke方法。被代理類需要實現(xiàn)接口, 它不支持繼承。JDK 動態(tài)代理類不需要事先定義好, 而是在運行期間動態(tài)生成。JDK 動態(tài)代理不需要實現(xiàn)和被代理類一樣的接口, 所以可以綁定多個被代理類。主要實現(xiàn)原理為反射, 它通過反射在運行期間動態(tài)生成代理類, 并且通過反射調用被代理類的實際業(yè)務方法。cglib的特點:

cglib動態(tài)代理中使用的是FastClass機制。cglib生成字節(jié)碼的底層原理是使用ASM字節(jié)碼框架。cglib動態(tài)代理需創(chuàng)建3份字節(jié)碼,所以在第一次使用時會比較耗性能,但是后續(xù)使用較JDK動態(tài)代理方式更高效,適合單例bean場景。cglib由于是采用動態(tài)創(chuàng)建子類的方法,對于final方法,無法進行代理。

2.適配器模式(Adapter?Class/Object)

適配器模式,它的功能是將一個類的接口變成客戶端所期望的另一種接口,從而使原本因接口不匹配而導致無法在一起工作的兩個類能夠一起工作。適配器模式分為類適配器模式和對象適配器模式,前者類之間的耦合度比后者高,且要求程序員了解現(xiàn)有組件庫中的相關組件的內部結構,所以應用相對較少些。

適配器模式(Adapter)包含以下主要角色:

目標(Target)接口:當前系統(tǒng)業(yè)務所期待的接口,它可以是抽象類或接口。適配者(Adaptee)類:它是被訪問和適配的現(xiàn)存組件庫中的組件接口。適配器(Adapter)類:它是一個轉換器,通過繼承或引用適配者的對象,把適配者接口轉換成目標接口,讓客戶按目標接口的格式訪問適配者。

2.1 類適配器

類適配器是通過定義一個適配器類來實現(xiàn)當前系統(tǒng)的業(yè)務接口,同時又繼承現(xiàn)有組件庫中已經存在的組件來實現(xiàn)的,類圖如下:

類圖

// 適配者 220V電壓

public class AC220 {

public int output() {

System.out.println("輸出220V交流電");

return 220;

}

}

// 目標 5V

public interface DC5 {

public int output5();

}

// 適配器類(電源適配器)

public class PowerAdapter extends AC220 implements DC5 {

@Override

public int output5() {

int output220 = super.output();

int output5 = output220 / 44;

System.out.println(output220 + "V適配轉換成" + output5 + "V");

return output5;

}

}

// 測試

public static void main(String[] args) {

PowerAdapter powerAdapter = new PowerAdapter();

// 輸出220V交流電

powerAdapter.output();

// 輸出220V交流電

// 220V適配轉換成5V

powerAdapter.output5();

}

通過上面代碼例子可以看出,類適配器有一個很明顯的缺點,就是違背了合成復用原則。結合上面的例子,假如我不是220V的電壓了,是380V電壓呢?那就要多建一個380V電壓的適配器了。同理,由于Java是單繼承的原因,如果不斷的新增適配者,那么就要無限的新增適配器,于是就有了對象適配器。

2.2 對象適配器

對象適配器的實現(xiàn)方式是通過現(xiàn)有組件庫中已經實現(xiàn)的組件引入適配器類中,該類同時實現(xiàn)當前系統(tǒng)的業(yè)務接口。

// 電源接口

public interface Power {

int output();

}

// 適配者 220V電壓

public class AC220 implements Power {

@Override

public int output() {

System.out.println("輸出220V交流電");

return 220;

}

}

// 目標 5V

public interface DC5 {

public int output5();

}

@AllArgsConstructor

public class PowerAdapter implements DC5 {

// 適配者

private Power power;

@Override

public int output5() {

int output220 = power.output();

int output5 = output220 / 44;

System.out.println(output220 + "V適配轉換成" + output5 + "V");

return output5;

}

}

// 測試

public static void main(String[] args) {

DC5 powerAdapter = new PowerAdapter(new AC220());

// 輸出220V交流電

// 220V適配轉換成5V

powerAdapter.output5();

}

可以看到,上面代碼中,只實現(xiàn)了目標接口,并沒有繼承適配者,而是將適配者類實現(xiàn)適配者接口,在適配器中引入適配者接口,當我們需要使用不同的適配者通過適配器進行轉換時,就無需再新建適配器類了,如上面例子,假如我需要380V的電源轉換成5V的,那么客戶端只需要調用適配器時傳入380V電源的類即可,就無需再新建一個380V電源的適配器了(PS:上述邏輯代碼中output220 / 44請忽略,可以根據實際情況編寫實際的通用邏輯代碼)。

2.3 接口適配器

接口適配器主要是解決類臃腫的問題,我們可以把所有相近的適配模式的方法都放到同一個接口里面,去實現(xiàn)所有方法,當客戶端需要哪個方法直接調用哪個方法即可。如上面例子所示,我們只是轉換成了5V電壓,那假如我要轉換成12V,24V,30V...呢?那按照上面的寫法就需要新建12V,24V,30V...的接口,這樣就會導致類過于多了。那么我們就可以把5V,12V,24V,30V...這些轉換方法,通通都寫到一個接口里去,這樣當我們需要轉換哪種就直接調用哪種即可。

// 這里例子 輸出不同直流電接口

public interface DC {

int output5();

int output12();

int output24();

int output30();

}

// 適配器類(電源適配器)

@AllArgsConstructor

public class PowerAdapter implements DC {

private Power power;

@Override

public int output5() {

// 具體實現(xiàn)邏輯

return 5;

}

@Override

public int output12() {

// 具體實現(xiàn)邏輯

return 12;

}

@Override

public int output24() {

// 具體實現(xiàn)邏輯

return 24;

}

@Override

public int output30() {

// 具體實現(xiàn)邏輯

return 30;

}

}

2.4 總結

適用場景:

已經存在的類,它的方法和需求不匹配(方法結構相同或相似)的情況。使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。

優(yōu)點:

能提高類的透明性和復用,現(xiàn)有的類復用但不需要改變。目標類和適配器類解耦,提高程序的擴展性。在很多業(yè)務場景中符合開閉原則。

缺點:

適配器編寫過程需要全面考慮,可能會增加系統(tǒng)的復雜性。增加代碼閱讀難度,降級代碼可讀性,過多使用適配器會使系統(tǒng)代碼變得凌亂。

3.裝飾模式(Decorator Pattern)

裝飾模式,是指在不改變原有對象的基礎上,將功能附加到對象上,提供了比繼承更有彈性的替代方案(擴展原有對象的功能)

裝飾(Decorator)模式中的角色:

抽象構件(Component)角色 :定義一個抽象接口以規(guī)范準備接收附加責任的對象。具體構件(Concrete Component)角色 :實現(xiàn)抽象構件,通過裝飾角色為其添加一些職責。抽象裝飾(Decorator)角色 : 繼承或實現(xiàn)抽象構件,并包含具體構件的實例,可以通過其子類擴展具體構件的功能。具體裝飾(ConcreteDecorator)角色 :實現(xiàn)抽象裝飾的相關方法,并給具體構件對象添加附加的責任。

3.1 繼承方式

舉一個簡單的例子,假如現(xiàn)在有一碟炒飯,每個人的口味不一樣,有些人喜歡加雞蛋,有些人喜歡加雞蛋火腿,有些人喜歡加雞蛋火腿胡蘿卜等,那么就會發(fā)現(xiàn),如果采用繼承的方式去實現(xiàn)這個例子,那么沒加一個配料,都需要創(chuàng)建新的配料類去繼承上一個舊的配料類,那么久而久之,就會產生很多類了,而且還不利于擴展,代碼如下:

// 炒飯類

public class FriedRice {

String getDesc() {

return "炒飯";

}

Integer getPrice() {

return 5;

}

}

// 炒飯加雞蛋類

public class FriedRiceAddEgg extends FriedRice{

String getDesc() {

return super.getDesc() + "+雞蛋";

}

Integer getPrice() {

return super.getPrice() + 2;

}

}

// 炒飯加雞蛋加火腿類

public class FriedRiceAddEggAndHam extends FriedRiceAddEgg {

String getDesc() {

return super.getDesc() + "+火腿";

}

Integer getPrice() {

return super.getPrice() + 3;

}

}

// 測試方法

public static void main(String[] args) {

FriedRice friedRice = new FriedRice();

System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元");// 炒飯5元

FriedRice friedRiceAddEgg = new FriedRiceAddEgg();

System.out.println(friedRiceAddEgg.getDesc() + friedRiceAddEgg.getPrice() + "元"); // 炒飯+雞蛋7元

FriedRice friedRiceAddEggAndHam = new FriedRiceAddEggAndHam();

System.out.println(friedRiceAddEggAndHam.getDesc() + friedRiceAddEggAndHam.getPrice() + "元");// 炒飯+雞蛋+火腿10元

}

可以從上面看到,如果我們只需要炒飯加火腿,那么我們還需要創(chuàng)建一個FriedRiceAddHam類去繼承FriedRice類,所以繼承的方式擴展性非常不好,且需要定義非常多的子類,下面就可以用裝飾器模式去改進它。

3.2 裝飾器模式方式

// 炒飯類

public class FriedRice {

String getDesc() {

return "炒飯";

}

Integer getPrice() {

return 5;

}

}

// 配料表

public abstract class Ingredients extends FriedRice{

private FriedRice friedRice;

public Ingredients(FriedRice friedRice) {

this.friedRice = friedRice;

}

String getDesc() {

return this.friedRice.getDesc();

}

Integer getPrice() {

return this.friedRice.getPrice();

}

}

// 雞蛋配料

public class Egg extends Ingredients {

public Egg(FriedRice friedRice) {

super(friedRice);

}

String getDesc() {

return super.getDesc() + "+雞蛋";

}

Integer getPrice() {

return super.getPrice() + 2;

}

}

// 火腿配料

public class Ham extends Ingredients {

public Ham(FriedRice friedRice){

super(friedRice);

}

String getDesc() {

return super.getDesc() + "+火腿";

}

Integer getPrice() {

return super.getPrice() + 3;

}

}

// 測試方法

public static void main(String[] args) {

FriedRice friedRice = new FriedRice();

System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元"); // 炒飯5元

friedRice = new Egg(friedRice);

System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元"); // 炒飯+雞蛋7元

friedRice = new Egg(friedRice);

System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元");// 炒飯+雞蛋+雞蛋9元

friedRice = new Ham(friedRice);

System.out.println(friedRice.getDesc() + friedRice.getPrice() + "元");// 炒飯+雞蛋+雞蛋+火腿12元

}

可以看到,使用裝飾器模式的方法實現(xiàn),與普通的繼承方法實現(xiàn),最大的區(qū)別就是一種配料只有一個類,而且在加配料的時候,也可以直接想加多少就加多少,不需要說一個雞蛋一個類,兩個雞蛋也要創(chuàng)建一個類,這樣可以帶來比繼承更加靈活的擴展功能,使用也更加方便。

3.3 總結

裝飾器模式與代理模式對比:

裝飾器模式就是一種特殊的代理模式。裝飾器模式強調自身的功能擴展,用自己說了算的透明擴展,可動態(tài)定制的擴展;代理模式強調代理過程的控制。獲取目標對象構建的地方不同,裝飾者是從外界傳遞進來的,可以通過構造方法傳遞;靜態(tài)代理是在代理類內部創(chuàng)建,以此來隱藏目標對象。

適用場景:

用于擴展一個類的功能或者給一個類添加附加職責。動態(tài)的給一個對象添加功能,這些功能同樣也可以再動態(tài)的撤銷。

優(yōu)點:

裝飾器是繼承的有力補充,比繼承靈活,不改變原有對象的情況下動態(tài)地給一個對象擴展功能,即插即用。通過使用不同裝飾類以及這些裝飾類的排列組合,可實現(xiàn)不同效果。裝飾器完全遵守開閉原則。

缺點:

會出現(xiàn)更多的代碼,更多的類,增加程序的復雜性。動態(tài)裝飾時,多層裝飾會更復雜。

4.橋接模式(Bridge Pattern)

橋接模式也稱為橋梁模式、接口模式或者柄體(Handle and Body)模式,是將抽象部分與他的具體實現(xiàn)部分分離,使它們都可以獨立地變化,通過組合的方式建立兩個類之間的聯(lián)系,而不是繼承。

橋接(Bridge)模式包含以下主要角色:

抽象化(Abstraction)角色 :定義抽象類,并包含一個對實現(xiàn)化對象的引用。擴展抽象化(Refined Abstraction)角色 :是抽象化角色的子類,實現(xiàn)父類中的業(yè)務方法,并通過組合關系調用實現(xiàn)化角色中的業(yè)務方法。實現(xiàn)化(Implementor)角色 :定義實現(xiàn)化角色的接口,供擴展抽象化角色調用。具體實現(xiàn)化(Concrete Implementor)角色 :給出實現(xiàn)化角色接口的具體實現(xiàn)。

4.1 代碼實現(xiàn)

下面以一個多系統(tǒng)多視頻格式文件播放為例子:

// 視頻接口

public interface Video {

void decode(String fileName);

}

// MP4格式類

public class Mp4 implements Video{

@Override

public void decode(String fileName) {

System.out.println("MP4視頻文件:"+ fileName);

}

}

// RMVB格式類

public class Rmvb implements Video{

@Override

public void decode(String fileName) {

System.out.println("rmvb文件:" + fileName);

}

}

// 操作系統(tǒng)抽象類

@AllArgsConstructor

public abstract class OperatingSystem {

Video video;

public abstract void play(String fileName);

}

// iOS系統(tǒng)

public class Ios extends OperatingSystem {

public Ios(Video video){

super(video);

}

@Override

public void play(String fileName) {

video.decode(fileName);

}

}

// windows系統(tǒng)

public class Windows extends OperatingSystem {

public Windows(Video video){

super(video);

}

@Override

public void play(String fileName) {

video.decode(fileName);

}

}

類關系圖

可以通過類圖看到,視頻類和操作系統(tǒng)類之間通過OperatingSystem類橋接關聯(lián)起來。

4.2 總結

適用場景:

在抽象和具體實現(xiàn)之間需要增加更多的靈活性的場景。一個類存在兩個(或多個)獨立變化的維度,而這兩個(或多個)維度都需要獨立進行擴展。不希望使用繼承,或因為多層繼承導致系統(tǒng)類的個數劇增。

優(yōu)點:

分離抽象部分及其具體實現(xiàn)部分。提高了系統(tǒng)的擴展性。符合開閉原型。符合合成復用原則。

缺點:

增加了系統(tǒng)的理解與設計難度。需要正確地識別系統(tǒng)中兩個獨立變化的維度。

5.外觀模式(Facade)

外觀模式又稱門面模式,提供了一個統(tǒng)一的接口,用來訪問子系統(tǒng)中的一群接口。

特征:門面模式定義了一個高層接口,讓子系統(tǒng)更容易使用。

外觀(Facade)模式包含以下主要角色:

外觀(Facade)角色:為多個子系統(tǒng)對外提供一個共同的接口。子系統(tǒng)(Sub System)角色:實現(xiàn)系統(tǒng)的部分功能,客戶可以通過外觀角色訪問它。

5.1 代碼實現(xiàn)

下面以一個智能音箱實現(xiàn)起床睡覺一鍵操作電器的場景,通過代碼模擬一下這個場景:

public class Light {

public void on() {

System.out.println("開燈");

}

public void off() {

System.out.println("關燈");

}

}

public class Tv {

public void on() {

System.out.println("開電視");

}

public void off() {

System.out.println("關電視");

}

}

public class Fan {

public void on() {

System.out.println("開風扇");

}

public void off() {

System.out.println("關風扇");

}

}

public class SmartSpeaker {

private Light light;

private Tv tv;

private Fan fan;

public SmartSpeaker() {

light = new Light();

tv = new Tv();

fan = new Fan();

}

public void say(String order) {

if (order.contains("起床")) {

getUp();

} else if (order.contains("睡覺")) {

sleep();

} else {

System.out.println("我還聽不懂你說的啥!");

}

}

public void getUp() {

System.out.println("起床");

light.on();

tv.on();

fan.off();

}

public void sleep() {

System.out.println("睡覺");

light.off();

tv.off();

fan.on();

}

}

public static void main(String[] args) {

SmartSpeaker smartSpeaker = new SmartSpeaker();

//睡覺

//關燈

//關電視

//開風扇

smartSpeaker.say("我要睡覺了!");

//起床

//開燈

//開電視

//關風扇

smartSpeaker.say("我起床了!");

//我還聽不懂你說的啥!

smartSpeaker.say("Emmm");

}

5.2 總結

適用場景:

對分層結構系統(tǒng)構建時,使用外觀模式定義子系統(tǒng)中每層的入口點可以簡化子系統(tǒng)之間的依賴關系。當一個復雜系統(tǒng)的子系統(tǒng)很多時,外觀模式可以為系統(tǒng)設計一個簡單的接口供外界訪問。當客戶端與多個子系統(tǒng)之間存在很大的聯(lián)系時,引入外觀模式可將它們分離,從而提高子系統(tǒng)的獨立性和可移植性。

優(yōu)點:

簡化了調用過程,無需深入了解子系統(tǒng),以防給子系統(tǒng)帶來風險。減少系統(tǒng)依賴、松散耦合。更好地劃分訪問層次,提高了安全性。遵循迪米特法則,即最少知道原則。

缺點:

當增加子系統(tǒng)和擴展子系統(tǒng)行為時,可能容易帶來未知風險。不符合開閉原則。某些情況下可能違背單一職責原則。

6.組合模式(Composite Pattern)

組合模式也稱為整體-部分(Part-Whole)模式,它的宗旨是通過將單個對象(葉子結點)和組合對象(樹枝節(jié)點)用相同的接口進行表示。

作用:使客戶端對單個對象和組合對象保持一致的方式處理。

組合模式主要包含三種角色: 抽象根節(jié)點(Component):定義系統(tǒng)各層次對象的共有方法和屬性,可以預先定義一些默認行為和屬性。樹枝節(jié)點(Composite):定義樹枝節(jié)點的行為,存儲子節(jié)點,組合樹枝節(jié)點和葉子節(jié)點形成一個樹形結構。葉子節(jié)點(Leaf):葉子節(jié)點對象,其下再無分支,是系統(tǒng)層次遍歷的最小單位。

6.1 代碼實現(xiàn)

下面以一個添加菜單的例子通過代碼實現(xiàn):

// 菜單組件

public abstract class MenuComponent {

String name;

Integer level;

public void add(MenuComponent menuComponent) {

throw new UnsupportedOperationException("不支持添加操作!");

}

public void remove(MenuComponent menuComponent) {

throw new UnsupportedOperationException("不支持刪除操作!");

}

public MenuComponent getChild(Integer i) {

throw new UnsupportedOperationException("不支持獲取子菜單操作!");

}

public String getName() {

throw new UnsupportedOperationException("不支持獲取名字操作!");

}

public void print() {

throw new UnsupportedOperationException("不支持打印操作!");

}

}

// 菜單類

public class Menu extends MenuComponent {

private List menuComponentList = new ArrayList<>();

public Menu(String name,int level){

this.level = level;

this.name = name;

}

@Override

public void add(MenuComponent menuComponent) {

menuComponentList.add(menuComponent);

}

@Override

public void remove(MenuComponent menuComponent) {

menuComponentList.remove(menuComponent);

}

@Override

public MenuComponent getChild(Integer i) {

return menuComponentList.get(i);

}

@Override

public void print() {

for (int i = 1; i < level; i++) {

System.out.print("--");

}

System.out.println(name);

for (MenuComponent menuComponent : menuComponentList) {

menuComponent.print();

}

}

}

// 子菜單類

public class MenuItem extends MenuComponent {

public MenuItem(String name,int level) {

this.name = name;

this.level = level;

}

@Override

public void print() {

for (int i = 1; i < level; i++) {

System.out.print("--");

}

System.out.println(name);

}

}

// 測試方法

public static void main(String[] args) {

//創(chuàng)建一級菜單

MenuComponent component = new Menu("系統(tǒng)管理",1);

MenuComponent menu1 = new Menu("用戶管理",2);

menu1.add(new MenuItem("新增用戶",3));

menu1.add(new MenuItem("修改用戶",3));

menu1.add(new MenuItem("刪除用戶",3));

MenuComponent menu2 = new Menu("角色管理",2);

menu2.add(new MenuItem("新增角色",3));

menu2.add(new MenuItem("修改角色",3));

menu2.add(new MenuItem("刪除角色",3));

menu2.add(new MenuItem("綁定用戶",3));

//將二級菜單添加到一級菜單中

component.add(menu1);

component.add(menu2);

//打印菜單名稱(如果有子菜單一塊打印)

component.print();

}

// 測試結果

系統(tǒng)管理

--用戶管理

----新增用戶

----修改用戶

----刪除用戶

--角色管理

----新增角色

----修改角色

----刪除角色

----綁定用戶

6.2 總結

適用場景:

希望客戶端可以忽略組合對象與單個對象的差異時。對象層次具備整體和部分,呈樹形結構(如樹形菜單,操作系統(tǒng)目錄結構,公司組織架構等)。

優(yōu)點:

清楚地定義分層次的復雜對象,表示對象的全部或部分層次。讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。簡化客戶端代碼。符合開閉原則。

缺點:

限制類型時會較為復雜。使設計變得更加抽象。

分類:

透明組合模式

透明組合模式中,抽象根節(jié)點角色中聲明了所有用于管理成員對象的方法,比如在示例中MenuComponent聲明了add() 、 remove() 、getChild()方法,這樣做的好處是確保所有的構件類都有相同的接口。透明組合模式也是組合模式的標準形式。透明組合模式的缺點是不夠安全,因為葉子對象和容器對象在本質上是有區(qū)別的,葉子對象不可能有下一個層次的對象,即不可能包含成員對象,因此為其提供 add()、remove() 等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段如果調用這些方法可能會出錯(如果沒有提供相應的錯誤處理代碼)安全組合模式

在安全組合模式中,在抽象構件角色中沒有聲明任何用于管理成員對象的方法,而是在樹枝節(jié)點Menu類中聲明并實現(xiàn)這些方法。安全組合模式的缺點是不夠透明,因為葉子構件和容器構件具有不同的方法,且容器構件中那些用于管理成員對象的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象編程,必須有區(qū)別地對待葉子構件和容器構件。

7.享元模式(Flyweight Pattern)

享元模式又稱為輕量級模式,是對象池的一種實現(xiàn),類似于線程池,線程池可以避免不停的創(chuàng)建和銷毀多個對象,消耗性能。提供了減少對象數量從而改善應用所需的對象結構的方式。宗旨:共享細粒度對象,將多個對同一對象的訪問集中起來。

享元(Flyweight )模式中存在以下兩種狀態(tài):

內部狀態(tài),即不會隨著環(huán)境的改變而改變的可共享部分。外部狀態(tài),指隨環(huán)境改變而改變的不可以共享的部分。享元模式的實現(xiàn)要領就是區(qū)分應用中的這兩種狀態(tài),并將外部狀態(tài)外部化。

享元模式的主要有以下角色:

抽象享元角色(Flyweight):通常是一個接口或抽象類,在抽象享元類中聲明了具體享元類公共的方法,這些方法可以向外界提供享元對象的內部數據(內部狀態(tài)),同時也可以通過這些方法來設置外部數據(外部狀態(tài))。具體享元(Concrete Flyweight)角色 :它實現(xiàn)了抽象享元類,稱為享元對象;在具體享元類中為內部狀態(tài)提供了存儲空間。通常我們可以結合單例模式來設計具體享元類,為每一個具體享元類提供唯一的享元對象。非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元類的子類都需要被共享,不能被共享的子類可設計為非共享具體享元類;當需要一個非共享具體享元類的對象時可以直接通過實例化創(chuàng)建。享元工廠(Flyweight Factory)角色 :負責創(chuàng)建和管理享元角色。當客戶對象請求一個享元對象時,享元工廠檢査系統(tǒng)中是否存在符合要求的享元對象,如果存在則提供給客戶;如果不存在的話,則創(chuàng)建一個新的享元對象。

7.1 代碼實現(xiàn)

下面通過查詢火車票的例子來用代碼進行模擬實現(xiàn):

// 抽象接口

public interface ITicket {

void show(String seat);

}

public class TrainTicket implements ITicket {

private String from;

private String to;

private Integer price;

public TrainTicket(String from, String to) {

this.from = from;

this.to = to;

}

@Override

public void show(String seat) {

this.price = new Random().nextInt(500);

System.out.println(from + "->" + to + ":" + seat + "價格:" + this.price);

}

}

// 工廠類

public class TicketFactory {

private static Map pool = new ConcurrentHashMap<>();

public static ITicket getTicket(String from, String to) {

String key = from + "->" + to;

if (pool.containsKey(key)) {

System.out.println("使用緩存獲取火車票:" + key);

return pool.get(key);

}

System.out.println("使用數據庫獲取火車票:" + key);

ITicket ticket = new TrainTicket(from, to);

pool.put(key, ticket);

return ticket;

}

}

// 測試

public static void main(String[] args) {

ITicket ticket = getTicket("北京", "上海");

//使用數據庫獲取火車票:北京->上海

//北京->上海:二等座價格:20

ticket.show("二等座");

ITicket ticket1 = getTicket("北京", "上海");

//使用緩存獲取火車票:北京->上海

//北京->上海:商務座價格:69

ticket1.show("商務座");

ITicket ticket2 = getTicket("上海", "北京");

//使用數據庫獲取火車票:上海->北京

//上海->北京:一等座價格:406

ticket2.show("一等座");

System.out.println(ticket == ticket1);//true

System.out.println(ticket == ticket2);//false

}

可以看到ticket和ticket2是使用數據庫查詢的,而ticket1是使用緩存查詢的,同時ticket == ticket1返回的是true,ticket == ticket2返回的是false,證明ticket和ticket1是共享的對象。

7.2 總結

適用場景:

一個系統(tǒng)有大量相同或者相似的對象,造成內存的大量耗費。對象的大部分狀態(tài)都可以外部化,可以將這些外部狀態(tài)傳入對象中。在使用享元模式時需要維護一個存儲享元對象的享元池,而這需要耗費一定的系統(tǒng)資源,因此,應當在需要多次重復使用享元對象時才值得使用享元模式。

優(yōu)點:

減少對象的創(chuàng)建,降低內存中對象的數量,降低系統(tǒng)的內存,提高效率。減少內存之外的其他資源占用。

缺點:

關注內、外部狀態(tài)。關注線程安全問題。使系統(tǒng)、程序的邏輯復雜化。

三、行為型模式

1.模板方法模式(Template method pattern)

模板方法模式通常又叫模板模式,是指定義一個算法的骨架,并允許之類為其中的一個或者多個步驟提供實現(xiàn)。模板方法模式使得子類可以在不改變算法結構的情況下,重新定義算法的某些步驟。

模板方法(Template Method)模式包含以下主要角色:

抽象類(Abstract Class):負責給出一個算法的輪廓和骨架。它由一個模板方法和若干個基本方法構成。

模板方法:定義了算法的骨架,按某種順序調用其包含的基本方法?;痉椒ǎ菏菍崿F(xiàn)算法各個步驟的方法,是模板方法的組成部分?;痉椒ㄓ挚梢苑譃槿N:

抽象方法(Abstract Method) :一個抽象方法由抽象類聲明、由其具體子類實現(xiàn)。具體方法(Concrete Method) :一個具體方法由一個抽象類或具體類聲明并實現(xiàn),其子類可以進行覆蓋也可以直接繼承。鉤子方法(Hook Method) :在抽象類中已經實現(xiàn),包括用于判斷的邏輯方法和需要子類重寫的空方法兩種。一般鉤子方法是用于判斷的邏輯方法,這類方法名一般為isXxx,返回值類型為boolean類型。具體子類(Concrete Class):實現(xiàn)抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的組成步驟。

1.1 代碼實現(xiàn)

下面以一個簡單的請假流程來通過代碼來實現(xiàn):

public abstract class DayOffProcess {

// 請假模板

public final void dayOffProcess() {

// 領取申請表

this.pickUpForm();

// 填寫申請信息

this.writeInfo();

// 簽名

this.signUp();

// 提交到不同部門審批

this.summit();

// 行政部備案

this.filing();

}

private void filing() {

System.out.println("行政部備案");

}

protected abstract void summit();

protected abstract void signUp();

private void writeInfo() {

System.out.println("填寫申請信息");

}

private void pickUpForm() {

System.out.println("領取申請表");

}

}

public class ZhangSan extends DayOffProcess {

@Override

protected void summit() {

System.out.println("張三簽名");

}

@Override

protected void signUp() {

System.out.println("提交到技術部審批");

}

}

public class Lisi extends DayOffProcess {

@Override

protected void summit() {

System.out.println("李四簽名");

}

@Override

protected void signUp() {

System.out.println("提交到市場部審批");

}

}

// 測試方法

public static void main(String[] args) {

DayOffProcess zhangsan = new ZhangSan();

//領取申請表

//填寫申請信息

//提交到技術部審批

//張三簽名

//行政部備案

zhangsan.dayOffProcess();

DayOffProcess lisi = new Lisi();

//領取申請表

//填寫申請信息

//提交到市場部審批

//李四簽名

//行政部備案

lisi.dayOffProcess();

}

1.2 總結

適用場景:

一次性實現(xiàn)一個算法不變的部分,并將可變的行為留給子類來實現(xiàn)。各子類中公共的行為被提取出來并集中到一個公共的父類中,從而避免代碼重復。

優(yōu)點:

利用模板方法將相同處理邏輯的代碼放到抽象父類中,可以提高代碼的復用性。將不同的代碼不同的子類中,通過對子類的擴展增加新的行為,提高代碼的擴展性。把不變的行為寫在父類上,去除子類的重復代碼,提供了一個很好的代碼復用平臺,符合開閉原則。

缺點:

類數目的增加,每一個抽象類都需要一個子類來實現(xiàn),這樣導致類的個數增加。類數量的增加,間接地增加了系統(tǒng)實現(xiàn)的復雜度。繼承關系自身缺點,如果父類添加新的抽象方法,所有子類都要改一遍。

2.策略模式(Strategy Pattern)

策略模式又叫政策模式(Policy Pattern),它是將定義的算法家族分別封裝起來,讓它們之間可以互相替換,從而讓算法的變化不會影響到使用算法的用戶??梢员苊舛嘀胤种У膇f......else和switch語句。

策略模式的主要角色如下:

抽象策略(Strategy)類:這是一個抽象角色,通常由一個接口或抽象類實現(xiàn)。此角色給出所有的具體策略類所需的接口。具體策略(Concrete Strategy)類:實現(xiàn)了抽象策略定義的接口,提供具體的算法實現(xiàn)或行為。環(huán)境(Context)類:持有一個策略類的引用,最終給客戶端調用。

2.1 普通案例(會員卡打折)

// 會員卡接口

public interface VipCard {

public void discount();

}

public class GoldCard implements VipCard {

@Override

public void discount() {

System.out.println("金卡打7折");

}

}

public class SilverCard implements VipCard {

@Override

public void discount() {

System.out.println("銀卡打8折");

}

}

public class CopperCard implements VipCard {

@Override

public void discount() {

System.out.println("銅卡打9折");

}

}

public class Normal implements VipCard {

@Override

public void discount() {

System.out.println("普通會員沒有折扣");

}

}

// 會員卡容器類

public class VipCardFactory {

private static Map map = new ConcurrentHashMap<>();

static {

map.put("gold", new GoldCard());

map.put("silver", new SilverCard());

map.put("copper", new CopperCard());

}

public static VipCard getVIPCard(String level) {

return map.get(level) != null ? map.get(level) : new Normal();

}

}

// 測試方法

public static void main(String[] args) {

//金卡打7折

VipCardFactory.getVIPCard("gold").discount();

//銀卡打8折

VipCardFactory.getVIPCard("silver").discount();

//普通會員沒有折扣

VipCardFactory.getVIPCard("other").discount();

}

用一個容器(Map)裝起來,可以通過傳進來的參數直接獲取對應的策略,避免了if...else。

2.2 支付方式案例

// 支付方式抽象類

public abstract class Payment {

public String pay(String uid, double money) {

double balance = queryBalance(uid);

if (balance < money) {

return "支付失敗!余額不足!欠" + (money - balance) + "元!";

}

return "支付成功!支付金額:" + money + "余額剩余:" + (balance - money);

}

protected abstract String getPaymentName();

protected abstract double queryBalance(String uid);

}

// 現(xiàn)金支付 默認方式

public class Cash extends Payment{

@Override

protected String getPaymentName() {

return "現(xiàn)金支付";

}

@Override

protected double queryBalance(String uid) {

return 1000;

}

}

// 支付寶類

public class AliPay extends Payment {

@Override

protected String getPaymentName() {

return "支付寶";

}

@Override

protected double queryBalance(String uid) {

return 500;

}

}

// 微信支付類

public class WeChatPay extends Payment {

@Override

protected String getPaymentName() {

return "微信支付";

}

@Override

protected double queryBalance(String uid) {

return 300;

}

}

// 支付方式容器策略類

public class PaymentStrategy {

private static Map map = new ConcurrentHashMap<>();

static {

map.put("WeChat", new WeChatPay());

map.put("Ali", new AliPay());

}

public static Payment getPayment(String payment) {

return map.get(payment) == null ? new Cash() : map.get(payment);

}

}

// 訂單交易類

@AllArgsConstructor

public class Order {

private String uid;

private double amount;

public String pay() {

return pay("cash");

}

public String pay(String key) {

Payment payment = PaymentStrategy.getPayment(key);

System.out.println("歡迎使用" + payment.getPaymentName());

System.out.println("本次交易金額:" + this.amount + ",開始扣款...");

return payment.pay(this.uid, this.amount);

}

}

// 測試方法

public static void main(String[] args) {

Order order = new Order("20221014001", 500);

//歡迎使用微信支付

//本次交易金額:500.0,開始扣款...

//支付失敗!余額不足!欠200.0元!

System.out.println(order.pay("WeChat"));

//歡迎使用支付寶

//本次交易金額:500.0,開始扣款...

//支付成功!支付金額:500.0余額剩余:0.0

System.out.println(order.pay("Ali"));

//歡迎使用現(xiàn)金支付

//本次交易金額:500.0,開始扣款...

//支付成功!支付金額:500.0余額剩余:500.0

System.out.println(order.pay());

}

2.3 總結

適用場景:

系統(tǒng)中有很多類,而它們的區(qū)別僅僅在于它們的行為不同。系統(tǒng)需要動態(tài)地在幾種算法中選擇一種。需要屏蔽算法規(guī)則。

優(yōu)點:

符合開閉原則。避免使用多重條件語句??梢蕴岣咚惴ǖ谋C苄院桶踩?。易于擴展。

缺點:

客戶端必須知道所有的策略,并且自行決定使用哪一個策略類。代碼中會產生非常多的策略類,增加維護難度。

3.命令模式(Command Pattern)

命令模式是對命令的封裝,每一個命令都是一個操作:請求的一方發(fā)出請求要求執(zhí)行一個操作;接收的一方收到請求,并執(zhí)行操作。命令模式解耦了請求方和接收方,請求方只需請求執(zhí)行命令,不用關心命令是怎樣被接收,怎樣被操作以及是否被執(zhí)行等。本質:解耦命令的請求與處理。

命令模式包含以下主要角色:

抽象命令類(Command)角色: 定義命令的接口,聲明執(zhí)行的方法。具體命令(Concrete Command)角色:具體的命令,實現(xiàn)命令接口;通常會持有接收者,并調用接收者的功能來完成命令要執(zhí)行的操作。實現(xiàn)者/接收者(Receiver)角色: 接收者,真正執(zhí)行命令的對象。任何類都可能成為一個接收者,只要它能夠實現(xiàn)命令要求實現(xiàn)的相應功能。調用者/請求者(Invoker)角色: 要求命令對象執(zhí)行請求,通常會持有命令對象,可以持有很多的命令對象。這個是客戶端真正觸發(fā)命令并要求命令執(zhí)行相應操作的地方,也就是說相當于使用命令對象的入口。

3.1 代碼實現(xiàn)

下面以一個播放器的例子來進行代碼實現(xiàn):

// 播放器類

public class Player {

public void play() {

System.out.println("正常播放");

}

public void pause() {

System.out.println("暫停播放");

}

public void stop() {

System.out.println("停止播放");

}

}

// 命令接口

public interface IAction {

void excuse();

}

// 播放命令類

@AllArgsConstructor

public class PlayAction implements IAction {

private Player player;

@Override

public void excuse() {

this.player.play();

}

}

// 暫停命令類

@AllArgsConstructor

public class PauseAction implements IAction {

private Player player;

@Override

public void excuse() {

this.player.pause();

}

}

// 停止命令類

@AllArgsConstructor

public class StopAction implements IAction{

private Player player;

@Override

public void excuse() {

this.player.stop();

}

}

// 控制器

public class Controller {

public void excuse(IAction action) {

action.excuse();

}

}

// 測試方法

public static void main(String[] args) {

// 正常播放

new Controller().excuse(new PlayAction(new Player()));

// 暫停播放

new Controller().excuse(new PauseAction(new Player()));

// 停止播放

new Controller().excuse(new StopAction(new Player()));

}

3.2 總結

適用場景:

現(xiàn)實語義中具備“命令”的操作(如命令菜單,shell命令...)。請求調用者和請求接收者需要解耦,使得調用者和接收者不直接交互。需要抽象出等待執(zhí)行的行為,比如撤銷操作和恢復操作等。需要支持命令宏(即命令組合操作)。

優(yōu)點:

通過引入中間件(抽象接口),解耦了命令的請求與實現(xiàn)。擴展性良好,可以很容易地增加新命令。支持組合命令,支持命令隊列。可以在現(xiàn)有的命令的基礎上,增加額外功能。

缺點:

具體命令類可能過多。增加 了程序的復雜度,理解更加困難。

4.職責鏈模式(chain of responsibility pattern)

職責鏈模式是將鏈中每一個節(jié)點看作是一個對象,每個節(jié)點處理的請求均不同,且內部自動維護一個下一節(jié)點對象。當一個請求從鏈式的首端發(fā)出時,會沿著鏈的路徑依次傳遞給每一個節(jié)點對象,直至有對象處理這個請求為止。

職責鏈模式主要包含以下角色:

抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。具體處理者(Concrete Handler)角色:實現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。客戶類(Client)角色:創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關心處理細節(jié)和請求的傳遞過程。

4.1 代碼實現(xiàn)

下面以一個簡單的登錄校驗流程來通過代碼進行實現(xiàn):

// 用戶實體類

@Data

public class User {

private String username;

private String password;

private String role;

}

// handler抽象類

public abstract class Handler {

protected Handler next;

// 返回handler方便鏈式操作

public void next(Handler next) {

this.next = next;

}

// 流程開始的方法

public abstract void doHandler(User user);

}

// 校驗用戶名或者密碼是否為空

public class ValidateHandler extends Handler {

@Override

public void doHandler(User user) {

if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) {

System.out.println("用戶名或者密碼為空!");

return;

}

System.out.println("校驗通過");

next.doHandler(user);

}

}

// 登錄校驗,校驗用戶名是否匹配密碼

public class LoginHandler extends Handler {

@Override

public void doHandler(User user) {

if (!"pyy52hz".equals(user.getUsername()) || !"123456".equals(user.getPassword())) {

System.out.println("用戶名或者密碼不正確!請檢查!");

return;

}

user.setRole("admin");

System.out.println("登陸成功!角色為管理員!");

next.doHandler(user);

}

}

// 權限校驗

public class AuthHandler extends Handler {

@Override

public void doHandler(User user) {

if (!"admin".equals(user.getRole())) {

System.out.println("無權限操作!");

return;

}

System.out.println("角色為管理員,可以進行下一步操作!");

}

}

// 登錄流程

public class LoginService {

public void login(User user) {

Handler validateHandler = new ValidateHandler();

Handler loginHandler = new LoginHandler();

Handler authHandler = new AuthHandler();

validateHandler.next(loginHandler);

loginHandler.next(authHandler);

validateHandler.doHandler(user);

}

}

// 測試方法

public static void main(String[] args){

User user = new User();

//校驗通過

//用戶名或者密碼不正確!請檢查!

user.setUsername("pyy52hz");

user.setPassword("1234567");

LoginService loginService = new LoginService();

loginService.login(user);

//校驗通過

//登陸成功!角色為管理員!

//角色為管理員,可以進行下一步操作!

user.setUsername("pyy52hz");

user.setPassword("123456");

loginService.login(user);

}

4.3 結合建造者模式

與基礎版本區(qū)別主要是Handler類中新增一個Builder的內部類,以及流程類里改用鏈式寫法,具體如下:

// handler抽象類

public abstract class Handler {

protected Handler next;

// 返回handler方便鏈式操作

public Handler next(Handler next) {

this.next = next;

return next;

}

// 流程開始的方法

public abstract void doHandler(User user);

static class Builder {

private Handler head;

private Handler tail;

public Builder addHandler(Handler handler) {

if (this.head == null) {

this.head = this.tail = handler;

return this;

}

this.tail.next(handler);

this.tail = handler;

return this;

}

public Handler build() {

return this.head;

}

}

}

public class LoginService {

public void login(User user) {

Handler.Builder builder = new Handler.Builder();

builder.addHandler(new ValidateHandler())

.addHandler(new LoginHandler())

.addHandler(new AuthHandler());

builder.build().doHandler(user);

}

}

4.4 總結

適用場景:

多個對象可以處理同一請求,但具體由哪個對象處理則在運行時動態(tài)決定。在不明確指定接收者的情況下,向多個對象中的一個提交一個請求??蓜討B(tài)指定一組對象處理請求。

優(yōu)點:

將請求與處理解耦。請求處理者(節(jié)點對象)只需關注自己感興趣的請求進行處理即可,對于不感興趣的請求,直接轉發(fā)給下一級節(jié)點對象。具備鏈式傳遞處理請求功能,請求發(fā)送者無需知曉鏈路結構,只需等待請求處理結果。鏈路結構靈活,可以通過改變鏈路結構動態(tài)地新增或刪減責任。易于擴展新的請求處理類(節(jié)點),符合開閉原則。

缺點:

責任鏈太長或者處理時間過長,會影響整體性能。如果節(jié)點對象存在循環(huán)引用時,會造成死循環(huán),導致系統(tǒng)崩潰。

5.狀態(tài)模式(State Pattern)

狀態(tài)模式也稱為狀態(tài)機模式(State Machine Pattern),是允許對象在內部狀態(tài)發(fā)生改變時改變它的行為,對象看起來好像修改了它的類。

狀態(tài)模式包含以下主要角色:

環(huán)境(Context)角色:也稱為上下文,它定義了客戶程序需要的接口,維護一個當前狀態(tài),并將與狀態(tài)相關的操作委托給當前狀態(tài)對象來處理。抽象狀態(tài)(State)角色:定義一個接口,用以封裝環(huán)境對象中的特定狀態(tài)所對應的行為。具體狀態(tài)(Concrete State)角色:實現(xiàn)抽象狀態(tài)所對應的行為。

5.1 代碼實現(xiàn)

// 電梯狀態(tài)

public abstract class LiftState {

protected Context context;

public abstract void open();

public abstract void close();

public abstract void run();

public abstract void stop();

}

// 開門狀態(tài)

public class OpenState extends LiftState {

@Override

public void open() {

System.out.println("電梯門打開了");

}

@Override

public void close() {

super.context.setLiftState(Context.CLOSE_STATE);

super.context.close();

}

@Override

public void run() {

}

@Override

public void stop() {

}

}

// 關門狀態(tài)

public class CloseState extends LiftState {

@Override

public void open() {

super.context.setLiftState(Context.OPEN_STATE);

super.context.open();

}

@Override

public void close() {

System.out.println("電梯門關閉了!");

}

@Override

public void run() {

super.context.setLiftState(Context.RUN_STATE);

super.context.run();

}

@Override

public void stop() {

super.context.setLiftState(Context.STOP_STATE);

super.context.stop();

}

}

// 運行狀態(tài)

public class RunState extends LiftState {

@Override

public void open() {

}

@Override

public void close() {

}

@Override

public void run() {

System.out.println("電梯正在運行...");

}

@Override

public void stop() {

super.context.setLiftState(Context.STOP_STATE);

super.context.stop();

}

}

// 停止狀態(tài)

public class StopState extends LiftState {

@Override

public void open() {

super.context.setLiftState(Context.OPEN_STATE);

super.context.open();

}

@Override

public void close() {

super.context.setLiftState(Context.CLOSE_STATE);

super.context.close();

}

@Override

public void run() {

super.context.setLiftState(Context.RUN_STATE);

super.context.run();

}

@Override

public void stop() {

System.out.println("電梯停止了!");

}

}

// 上下文

public class Context {

private LiftState liftState;

public static final LiftState OPEN_STATE = new OpenState();

public static final LiftState CLOSE_STATE = new CloseState();

public static final LiftState RUN_STATE = new RunState();

public static final LiftState STOP_STATE = new StopState();

public void setLiftState(LiftState liftState) {

this.liftState = liftState;

this.liftState.setContext(this);

}

public void open() {

this.liftState.open();

}

public void close() {

this.liftState.close();

}

public void run() {

this.liftState.run();

}

public void stop() {

this.liftState.stop();

}

}

// 測試

public static void main(String[] args){

Context context = new Context();

context.setLiftState(new CloseState());

//電梯門打開了

//電梯門關閉了!

//電梯正在運行...

//電梯停止了!

context.open();

context.close();

context.run();

context.stop();

}

5.2 spring的狀態(tài)機

// Todo

5.3 總結

適用場景:

行為隨狀態(tài)改變而改變的場景。一個操作中含有龐大的多分支結構,并且這些分支取決于對象的狀態(tài)。

優(yōu)點:

結構清晰:將狀態(tài)獨立為類,消除了冗余的if...else或switch...case語句,使代碼更加簡潔,提高系統(tǒng)可維護性。將狀態(tài)轉換顯示化:通常的對象內部都是使用數值類型來定義狀態(tài),狀態(tài)的切換是通過賦值進行表現(xiàn),不夠直觀;而使用狀態(tài)類,在切換狀態(tài)時,是以不同的類進行表示,轉換目的更加明確。狀態(tài)類職責明確且具備擴展性。

缺點:

類膨脹:如果一個事物具備很多狀態(tài),則會造成狀態(tài)類太多。狀態(tài)模式的結構與實現(xiàn)都較為復雜,如果使用不當將導致程序結構和代碼的混亂。狀態(tài)模式對開閉原則的支持并不太好,對于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負責狀態(tài)轉換的源代碼,否則無法切換到新增狀態(tài),而且修改某個狀態(tài)類的行為也需修改對應類的源代碼。

6.觀察者模式(Observer Mode)

觀察者模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式,模型-視圖(Model/View)模式,源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。定義一種一對多的依賴關系,一個主題對象可被多個觀察者同時監(jiān)聽,使得每當主題對象狀態(tài)變化時,所有依賴于它的對象都會得到通知并被自動更新。

6.1 代碼實現(xiàn)

通過一個微信用戶(觀察者)訂閱公眾號(被觀察者)接收公眾號推送消息的例子來進行簡單的代碼實現(xiàn):

// 抽象觀察者接口

public interface Observer {

void update(String message);

}

// 微信用戶類 具體的觀察者

@AllArgsConstructor

public class WeixinUser implements Observer {

private String name;

@Override

public void update(String message) {

System.out.println(name + "接收到了消息(觀察到了):" + message);

}

}

// 被觀察者接口

public interface Observable {

// 新增用戶(新增觀察者)

void add(Observer observer);

// 移除用戶,或者說用戶取消訂閱(移除觀察者)

void del(Observer observer);

// 發(fā)布 推送消息

void notify(String message);

}

// 具體的被觀察者(公眾號)

public class Subject implements Observable {

// 觀察者列表(訂閱用戶)

private List list = new ArrayList<>();

@Override

public void add(Observer observer) {

list.add(observer);

}

@Override

public void del(Observer observer) {

list.remove(observer);

}

// 給每一個觀察者(訂閱者)推送消息

@Override

public void notify(String message) {

list.forEach(observer -> observer.update(message));

}

}

// 測試

public static void main(String[] args){

Observable o = new Subject();

WeixinUser user1 = new WeixinUser("張三");

WeixinUser user2 = new WeixinUser("李四");

WeixinUser user3 = new WeixinUser("王五");

o.add(user1);

o.add(user2);

o.add(user3);

o.notify("薛之謙演唱會要來到廣州啦!");

// 運行結果

// 張三接收到了消息(觀察到了):薛之謙演唱會要來到廣州啦!

// 李四接收到了消息(觀察到了):薛之謙演唱會要來到廣州啦!

// 王五接收到了消息(觀察到了):薛之謙演唱會要來到廣州啦!

}

6.2 JDK實現(xiàn)

在 Java 中,通過java.util.Observable類和 java.util.Observer接口定義了觀察者模式,只要實現(xiàn)它們的子類就可以編寫觀察者模式實例。

6.2.1 Observable類

Observable類是抽象目標類(被觀察者),它有一個Vector集合成員變量,用于保存所有要通知的觀察者對象,下面是它最重要的 3 個方法: void addObserver(Observer o) 方法:用于將新的觀察者對象添加到集合中。 void notifyObservers(Object arg) 方法:調用集合中的所有觀察者對象的update方法,通知它們數據發(fā)生改變。通常越晚加入集合的觀察者越先得到通知。 void setChange() 方法:用來設置一個boolean類型的內部標志,注明目標對象發(fā)生了變化。當它為true時,notifyObservers() 才會通知觀察者。

6.2.2 Observer 接口

Observer 接口是抽象觀察者,它監(jiān)視目標對象的變化,當目標對象發(fā)生變化時,觀察者得到通知,并調用 update 方法,進行相應的工作。

6.2.3 代碼實現(xiàn)

下面還是通過微信用戶訂閱公眾號的例子進行代碼實現(xiàn),方便對比他們之間的區(qū)別:

// 具體的被觀察者(公眾號)

@Data

@AllArgsConstructor

public class Subject extends Observable {

// 公眾號的名字

private String name;

// 公眾號發(fā)布消息

public void notifyMessage(String message) {

System.out.println(this.name + "公眾號發(fā)布消息:" + message + "請關注用戶留意接收!");

super.setChanged();

super.notifyObservers(message);

}

}

@AllArgsConstructor

public class WeixinUser implements Observer {

private String name;

/**

* @param o 被觀察者

* @param arg 被觀察者帶過來的參數,此例子中是公眾號發(fā)布的消息

*/

@Override

public void update(Observable o, Object arg) {

System.out.println(name + "關注了公眾號(被觀察者):" + ((Subject)o).getName() + ",接收到消息:" + arg);

}

}

// 測試

public static void main(String[] args){

WeixinUser user1 = new WeixinUser("張三");

WeixinUser user2 = new WeixinUser("李四");

WeixinUser user3 = new WeixinUser("王五");

Subject subject = new Subject("演唱會消息發(fā)布");

subject.addObserver(user1);

subject.addObserver(user2);

subject.addObserver(user3);

subject.notifyMessage("薛之謙演唱會要來到廣州啦!");

// 返回結果

// 演唱會消息發(fā)布公眾號發(fā)布消息:薛之謙演唱會要來到廣州啦!請關注用戶留意接收!

// 王五關注了公眾號(被觀察者):演唱會消息發(fā)布,接收到消息:薛之謙演唱會要來到廣州啦!

// 李四關注了公眾號(被觀察者):演唱會消息發(fā)布,接收到消息:薛之謙演唱會要來到廣州啦!

// 張三關注了公眾號(被觀察者):演唱會消息發(fā)布,接收到消息:薛之謙演唱會要來到廣州啦!

}

6.3 Google的Guava實現(xiàn)

EventBus 術語解釋備注事件(消息)可以向事件總線(EventBus)發(fā)布的對象通常是一個類,不同的消息事件用不同的類來代替,消息內容就是類里面的屬性訂閱向事件總線注冊監(jiān)聽者,以接受事件的行為EventBus.register(Object),參數就是監(jiān)聽者監(jiān)聽者提供一個處理方法,希望接受和處理事件的對象通常也是一個類,里面有消息的處理方法處理方法監(jiān)聽者提供的公共方法,事件總線使用該方法向監(jiān)聽者發(fā)送事件;該方法應使用 Subscribe 注解監(jiān)聽者里面添加一個 Subscribe 注解的方法,就可以認為是消息的處理方法發(fā)布消息通過事件總線向所有匹配的監(jiān)聽者提供事件EventBus.post(Object)

@AllArgsConstructor

public class WeixinUser {

private String name;

@Subscribe

public void getMessage(Object arg) {

System.out.println(this.name + "接收到消息:" + arg);

}

// 測試

public static void main(String[] args){

// 消息總線

EventBus eventBus = new EventBus();

eventBus.register(new WeixinUser("張三"));

eventBus.register(new WeixinUser("李四"));

eventBus.post("薛之謙演唱會要來到廣州啦!");

// 返回結果

// 張三接收到消息:薛之謙演唱會要來到廣州啦!

// 李四接收到消息:薛之謙演唱會要來到廣州啦!

}

}

6.4 總結

適用場景:

當一個抽象模型包含兩個方面內容,其中一個方面依賴于另一個方面。其他一個或多個對象的變化依賴于另一個對象的變化。實現(xiàn)類似廣播機制的功能,無需知道具體收聽者,只需分發(fā)廣播,系統(tǒng)中感興趣的對象會自動接收該廣播。

多層級嵌套使用,形成一種鏈式觸發(fā)機制,使得事件具備跨域(跨越兩種觀察者類型)通知。

優(yōu)點:

觀察者和被觀察者是松耦合(抽象耦合)的,符合依賴倒置原則。分離了表示層(觀察者)和數據邏輯層(被觀察者),并且建立了一套觸發(fā)機制,使得數據的變化可以相應到多個表示層上。實現(xiàn)了一對多的通訊機制,支持事件注冊機制,支持興趣分發(fā)機制,當被觀察者觸發(fā)事件時,只有感興趣的觀察者可以接收到通知。

缺點:

如果觀察者數量過多,則事件通知會耗時較長。事件通知呈線性關系,如果其中一個觀察者處理事件卡殼,會影響后續(xù)的觀察者接收該事件。如果觀察者和被觀察者之間存在循環(huán)依賴,則可能造成兩者之間的循環(huán)調用,導致系統(tǒng)崩潰。

7.中介者模式(mediator pattern)

中介者模式又稱為調解者模式或調停者模式。用一個中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地相互作用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。

核心:通過中介者解耦系統(tǒng)各層次對象的直接耦合,層次對象的對外依賴通信統(tǒng)統(tǒng)交由中介者轉發(fā)。

中介者模式包含以下主要角色:

抽象中介者(Mediator)角色:它是中介者的接口,提供了同事對象注冊與轉發(fā)同事對象信息的抽象方法。具體中介者(ConcreteMediator)角色:實現(xiàn)中介者接口,定義一個 List 來管理同事對象,協(xié)調各個同事角色之間的交互關系,因此它依賴于同事角色。抽象同事類(Colleague)角色:定義同事類的接口,保存中介者對象,提供同事對象交互的抽象方法,實現(xiàn)所有相互影響的同事類的公共功能。具體同事類(Concrete Colleague)角色:是抽象同事類的實現(xiàn)者,當需要與其他同事對象交互時,由中介者對象負責后續(xù)的交互。

7.1 代碼實現(xiàn)

通過一個租房例子簡單實現(xiàn)下邏輯,房主通過中介公司發(fā)布自己的房子的信息,而租客則需要通過中介公司獲取到房子的信息:

// 抽象同事類

@AllArgsConstructor

public class Person {

protected String name;

protected MediatorCompany mediatorCompany;

}

// 房主

public class HouseOwner extends Person {

public HouseOwner(String name, MediatorCompany mediatorCompany) {

super(name, mediatorCompany);

}

// 聯(lián)絡方法

public void connection(String message) {

mediatorCompany.connection(this, message);

}

// 獲取消息

public void getMessage(String message) {

System.out.println("房主" + name + "獲取到的信息:" + message);

}

}

// 租客

public class Tenant extends Person {

public Tenant(String name, MediatorCompany mediatorCompany) {

super(name, mediatorCompany);

}

public void connection(String message) {

mediatorCompany.connection(this, message);

}

public void getMessage(String message) {

System.out.println("租客" + name + "獲取到的信息:" + message);

}

}

// 中介公司(中介者)

@Data

public class MediatorCompany {

private HouseOwner houseOwner;

private Tenant tenant;

public void connection(Person person, String message) {

// 房主需要通過中介獲取租客信息

if (person.equals(houseOwner)) {

this.tenant.getMessage(message);

} else { // 反之租客通過中介獲取房主信息

this.houseOwner.getMessage(message);

}

}

}

// 測試

public static void main(String[] args){

// 先創(chuàng)建三個角色,中介公司,房主,租客

MediatorCompany mediatorCompany = new MediatorCompany();

// 房主和租客都在同一家中介公司

HouseOwner houseOwner = new HouseOwner("張三", mediatorCompany);

Tenant tenant = new Tenant("李四", mediatorCompany);

// 中介公司獲取房主和租客的信息

mediatorCompany.setHouseOwner(houseOwner);

mediatorCompany.setTenant(tenant);

// 房主和租客都在這家中介公司發(fā)布消息,獲取到對應的消息

tenant.connection(tenant.name + "想租一房一廳!");

houseOwner.connection(houseOwner.name + "這里有!來看看唄!");

// 測試結果

// 房主張三獲取到的信息:李四想租一房一廳!

// 租客李四獲取到的信息:張三這里有!來看看唄!

}

7.2 總結

適用場景:

系統(tǒng)中對象之間存在復雜的引用關系,產生的我相互依賴關系結構混亂且難以理解。交互的公共行為,如果需要改變行為則可以增加新的中介者類。

優(yōu)點:

減少類間的依賴,將多對多依賴轉化成了一對多,降低了類間耦合。類間各司其職,符合迪米特法則。

缺點:

中介者模式中將原本多個對象直接的相互依賴變成了中介者和多個同事類的依賴關系。當同事類越多時,中介者就會越臃腫,變得復雜且難以維護。

8.迭代器模式(Iterator Pattern)

迭代器模式又稱為游標模式(Cursor Pattern),它提供一種順序訪問集合/容器對象元素的方法,而又無須暴露結合內部表示。

本質:抽離集合對象迭代行為到迭代器中,提供一致訪問接口。

迭代器模式主要包含以下角色:

抽象聚合(Aggregate)角色:定義存儲、添加、刪除聚合元素以及創(chuàng)建迭代器對象的接口。具體聚合(ConcreteAggregate)角色:實現(xiàn)抽象聚合類,返回一個具體迭代器的實例。抽象迭代器(Iterator)角色:定義訪問和遍歷聚合元素的接口,通常包含 hasNext()、next() 等方法。具體迭代器(Concretelterator)角色:實現(xiàn)抽象迭代器接口中所定義的方法,完成對聚合對象的遍歷,記錄遍歷的當前位置。

8.1 代碼實現(xiàn)

// 迭代器接口

public interface Iterator {

Boolean hasNext();

T next();

}

// 迭代器接口實現(xiàn)類

public class IteratorImpl implements Iterator {

private List list;

private Integer cursor;

private T element;

public IteratorImpl(List list) {

this.list = list;

}

@Override

public Boolean hasNext() {

return cursor < list.size();

}

@Override

public T next() {

element = list.get(cursor);

cursor++;

return element;

}

}

// 容器接口

public interface Aggregate {

void add(T t);

void remove(T t);

Iterator iterator();

}

// 容器接口實現(xiàn)類

public class AggregateImpl implements Aggregate {

private List list = new ArrayList<>();

@Override

public void add(T t) {

list.add(t);

}

@Override

public void remove(T t) {

list.remove(t);

}

@Override

public Iterator iterator() {

return new IteratorImpl<>(list);

}

}

PS:具體測試的話可以自己寫一個集合測試一下即可

8.2 總結

適用場景:

訪問一個集合對象的內容而無需暴露它的內部表示。為遍歷不同的集合結構提供一個統(tǒng)一的訪問接口。

優(yōu)點:

多態(tài)迭代:為不同的聚合結構提供一致的遍歷接口,即一個迭代接口可以訪問不同的聚集對象。簡化集合對象接口:迭代器模式將集合對象本身應該提供的元素迭代接口抽取到了迭代器中,使集合對象無須關心具體迭代行為。元素迭代功能多樣化:每個集合對象都可以提供一個或多個不同的迭代器,使的同種元素聚合結構可以有不同的迭代行為。解耦迭代與集合:迭代器模式封裝了具體的迭代算法,迭代算法的變化,不會影響到集合對象的架構。

缺點:

對于比較簡單的遍歷(像數組或者有序列表),使用迭代器方式遍歷較為繁瑣。增加了類的個數,在一定程度上增加了系統(tǒng)的復雜性。

9.訪問者模式(Visitor Pattern)

訪問者模式是一種將數據結構與數據操作分離的設計模式。是指封裝一些作用于某種數據結構中的各元素的操作。

特征:可以在不改變數據結構的前提下定義作用于這些元素的新的操作。

訪問者模式包含以下主要角色:

抽象訪問者(Visitor)角色:定義了對每一個元素 (Element) 訪問的行為,它的參數就是可以訪問的元素,它的方法個數理論上來講與元素類個數(Element的實現(xiàn)類個數)是一樣的,從這點不難看出,訪問者模式要求元素類的個數不能改變。具體訪問者(ConcreteVisitor)角色:給出對每一個元素類訪問時所產生的具體行為。抽象元素(Element)角色:定義了一個接受訪問者的方法( accept ),其意義是指,每一個元素都要可以被訪問者訪問。具體元素(ConcreteElement)角色: 提供接受訪問方法的具體實現(xiàn),而這個具體的實現(xiàn),通常情況下是使用訪問者提供的訪問該元素類的方法。對象結構(Object Structure)角色:定義當中所提到的對象結構,對象結構是一個抽象表述,具體點可以理解為一個具有容器性質或者復合對象特性的類,它會含有一組元素( Element ),并且可以迭代這些元素,供訪問者訪問。

9.1 代碼實現(xiàn)

// 訪問者接口

public interface IVisitor {

void visit(Engineer engineer);

void visit(Pm pm);

}

// 具體的訪問者類,訪問者角色(CEO)

public class CeoVisitor implements IVisitor {

@Override

public void visit(Engineer engineer) {

System.out.println(engineer.getName() + "KPI為:" + engineer.getKpi());

}

@Override

public void visit(Pm pm) {

System.out.println(pm.getName() + "KPI為:" + pm.getKpi());

}

}

// 具體的訪問者類,訪問者角色(CTO)

public class CtoVisitor implements IVisitor {

@Override

public void visit(Engineer engineer) {

System.out.println(engineer.getName() + "工作內容:" + engineer.getCodeLine() + "行代碼");

}

@Override

public void visit(Pm pm) {

System.out.println(pm.getName() + "工作內容:" + pm.getProject() + "個項目");

}

}

@Data

// 抽象元素(員工)

public abstract class Employee {

private String name;

private Integer kpi;

public Employee(String name) {

this.name = name;

this.kpi = new Random().nextInt(10);

}

public abstract void accept(IVisitor visitor);

}

// 具體元素(程序員)

public class Engineer extends Employee {

public Engineer(String name) {

super(name);

}

@Override

public void accept(IVisitor visitor) {

visitor.visit(this);

}

public Integer getCodeLine() {

return new Random().nextInt(10000);

}

}

// 具體元素(項目經理)

public class Pm extends Employee {

public Pm(String name) {

super(name);

}

@Override

public void accept(IVisitor visitor) {

visitor.visit(this);

}

public Integer getProject() {

return new Random().nextInt(10);

}

}

@AllArgsConstructor

public class Report {

private List employeeList;

public void showReport(IVisitor visitor) {

for (Employee employee : employeeList) {

employee.accept(visitor);

}

}

}

// 測試

public static void main(String[] args){

List employeeList = new ArrayList<>();

employeeList.add(new Engineer("工程師A"));

employeeList.add(new Engineer("工程師B"));

employeeList.add(new Pm("項目經理A"));

employeeList.add(new Engineer("工程師C"));

employeeList.add(new Engineer("工程師D"));

employeeList.add(new Pm("項目經理B"));

Report report = new Report(employeeList);

System.out.println("=============CEO==============");

report.showReport(new CeoVisitor());

System.out.println("=============CTO==============");

report.showReport(new CtoVisitor());

// =============CEO==============

// 工程師AKPI為:2

// 工程師BKPI為:4

// 項目經理AKPI為:4

// 工程師CKPI為:2

// 工程師DKPI為:0

// 項目經理BKPI為:0

// =============CTO==============

// 工程師A工作內容:5811行代碼

// 工程師B工作內容:9930行代碼

// 項目經理A工作內容:7個項目

// 工程師C工作內容:4591行代碼

// 工程師D工作內容:333行代碼

// 項目經理B工作內容:4個項目

}

9.2 偽動態(tài)雙分派

訪問者模式用到了一種偽動態(tài)雙分派的技術。

9.2.1 分派

變量被聲明時的類型叫做變量的靜態(tài)類型,有些人又把靜態(tài)類型叫做明顯類型;而變量所引用的對象的真實類型又叫做變量的實際類型。比如Map map = new HashMap() ,map變量的靜態(tài)類型是Map,實際類型是 HashMap 。根據對象的類型而對方法進行的選擇,就是分派(Dispatch),分派(Dispatch)又分為兩種,即靜態(tài)分派和動態(tài)分派。

靜態(tài)分派(Static Dispatch) 發(fā)生在編譯時期,分派根據靜態(tài)類型信息發(fā)生。靜態(tài)分派對于我們來說并不陌生,方法重載就是靜態(tài)分派。動態(tài)分派(Dynamic Dispatch) 發(fā)生在運行時期,動態(tài)分派動態(tài)地置換掉某個方法。Java通過方法的重寫支持動態(tài)分派。

9.2.2 偽動態(tài)雙分派

所謂雙分派技術就是在選擇一個方法的時候,不僅僅要根據消息接收者(receiver)的運行時區(qū)別,還要根據參數的運行時區(qū)別。

在上面代碼中,客戶端將IVisitor接口做為參數傳遞給Employee抽象類的變量調用的方法,這里完成第一次分派,這里是方法重寫,所以是動態(tài)分派,也就是執(zhí)行實際類型中的方法,同時也將自己this作為參數傳遞進去,這里就完成了第二次分派 ,這里的IVisitor接口中有多個重載的方法,而傳遞進行的是this,就是具體的實際類型的對象。

雙分派實現(xiàn)動態(tài)綁定的本質,就是在重載方法委派的前面加上了繼承體系中覆蓋的環(huán)節(jié),由于覆蓋是動態(tài)的,所以重載就是動態(tài)的了。

9.3 總結

適用場景:

數據結構穩(wěn)定,作用于數據結構的操作經常變化的場景。需要數據結構與數據操作分離的場景。需要對不同數據類型(元素)進行操作,而不使用分支判斷具體類型的場景。

優(yōu)點:

解耦了數據結構與數據操作,使得操作集合可以獨立變化。擴展性好:可以通過擴展訪問者角色,實現(xiàn)對數據集的不同操作。元素具體類型并非單一,訪問者均可操作。各角色職責分離,符合單一職責原則。

缺點:

無法增加元素類型:若系統(tǒng)數據結構對象易于變化,經常有新的數據對象增加進來,則訪問者類必須增加對應元素類型的操作,違背了開閉原則。具體元素變更困難:具體元素增加屬性,刪除屬性等操作會導致對應的訪問者類需要進行相應的修改,尤其當有大量訪問者類時,修改訪問太大。違背依賴倒置原則:為了達到“區(qū)別對待”,訪問者依賴的是具體元素類型,而不是抽象。

10.備忘錄模式(Memento Pattern)

備忘錄模式又稱為快照模式(Snapshot Pattern)或令牌模式(Token Pattern),是指在不破壞封裝的前提下,捕獲一個對象的內部狀態(tài),并在對象之外保存這個狀態(tài),這樣以后就可將該對象恢復到原先保存的狀態(tài)。

特征:“后悔藥”

備忘錄模式的主要角色如下:

發(fā)起人(Originator)角色:記錄當前時刻的內部狀態(tài)信息,提供創(chuàng)建備忘錄和恢復備忘錄數據的功能,實現(xiàn)其他業(yè)務功能,它可以訪問備忘錄里的所有信息。備忘錄(Memento)角色:負責存儲發(fā)起人的內部狀態(tài),在需要的時候提供這些內部狀態(tài)給發(fā)起人。管理者(Caretaker)角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改。

備忘錄有兩個等效的接口:

窄接口:管理者(Caretaker)對象(和其他發(fā)起人對象之外的任何對象)看到的是備忘錄的窄接口(narror Interface),這個窄接口只允許他把備忘錄對象傳給其他的對象。寬接口:與管理者看到的窄接口相反,發(fā)起人對象可以看到一個寬接口(wide Interface),這個寬接口允許它讀取所有的數據,以便根據這些數據恢復這個發(fā)起人對象的內部狀態(tài)。

10.1 “白箱”備忘錄模式

下面就以游戲打怪為簡單的例子進行代碼實現(xiàn)(下面“黑箱”同這個例子):

備忘錄角色對任何對象都提供一個寬接口,備忘錄角色的內部所存儲的狀態(tài)就對所有對象公開。

// 游戲角色類

@Data

public class GameRole {

private Integer vit; // 生命力

private Integer atk; // 攻擊力

private Integer def; // 防御力

// 初始化狀態(tài)

public void init() {

this.vit = 100;

this.atk = 100;

this.def = 100;

}

// 戰(zhàn)斗到0

public void fight() {

this.vit = 0;

this.atk = 0;

this.def = 0;

}

// 保存角色狀態(tài)

public RoleStateMemento saveState() {

return new RoleStateMemento(this.vit, this.atk, this.def);

}

// 回復角色狀態(tài)

public void recoverState(RoleStateMemento roleStateMemento) {

this.vit = roleStateMemento.getVit();

this.atk = roleStateMemento.getAtk();

this.def = roleStateMemento.getDef();

}

// 展示狀態(tài)

public void showState() {

System.out.println("角色生命力:" + this.vit);

System.out.println("角色攻擊力:" + this.atk);

System.out.println("角色防御力:" + this.def);

}

}

// 游戲狀態(tài)存儲類(備忘錄類)

@Data

@AllArgsConstructor

public class RoleStateMemento {

private Integer vit; // 生命力

private Integer atk; // 攻擊力

private Integer def; // 防御力

}

// 角色狀態(tài)管理者類

@Data

public class RoleStateCaretaker {

private RoleStateMemento roleStateMemento;

}

// 測試結果

public static void main(String[] args){

System.out.println("===========打boss前狀態(tài)===========");

GameRole gameRole = new GameRole();

gameRole.init();

gameRole.showState();

// 保存進度

RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();

roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

System.out.println("===========打boss后狀態(tài)===========");

gameRole.fight();

gameRole.showState();

System.out.println("===========恢復狀態(tài)===========");

gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());

gameRole.showState();

// ===========打boss前狀態(tài)===========

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

// ===========打boss后狀態(tài)===========

// 角色生命力:0

// 角色攻擊力:0

// 角色防御力:0

// ===========恢復狀態(tài)===========

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

}

“白箱”備忘錄模式是破壞封裝性的,但是通過程序員自律,同樣可以在一定程度上實現(xiàn)大部分的用意。

10.2 “黑箱”備忘錄模式

備忘錄角色對發(fā)起人對象提供了一個寬接口,而為其他對象提供一個窄接口,在Java語言中,實現(xiàn)雙重接口的辦法就是將備忘錄類設計成發(fā)起人類的內部成員類。

將RoleStateMemento設為GameRole的內部類,從而將RoleStateMemento對象封裝在GameRole 里面;在外面提供一個標識接口Memento給RoleStateCaretaker及其他對象使用。這樣GameRole類看到的是RoleStateMemento所有的接口,而RoleStateCaretaker及其他對象看到的僅僅是標識接口Memento所暴露出來的接口,從而維護了封裝型。

// 窄接口,標識接口

public interface Memento {

}

// 角色狀態(tài)管理者類

@Data

public class RoleStateCaretaker {

private Memento memento;

}

// 游戲角色類

@Data

public class GameRole {

private Integer vit; // 生命力

private Integer atk; // 攻擊力

private Integer def; // 防御力

// 初始化狀態(tài)

public void init() {

this.vit = 100;

this.atk = 100;

this.def = 100;

}

// 戰(zhàn)斗到0

public void fight() {

this.vit = 0;

this.atk = 0;

this.def = 0;

}

// 保存角色狀態(tài)

public RoleStateMemento saveState() {

return new RoleStateMemento(this.vit, this.atk, this.def);

}

// 回復角色狀態(tài)

public void recoverState(Memento memento) {

RoleStateMemento roleStateMemento = (RoleStateMemento) memento;

this.vit = roleStateMemento.getVit();

this.atk = roleStateMemento.getAtk();

this.def = roleStateMemento.getDef();

}

// 展示狀態(tài)

public void showState() {

System.out.println("角色生命力:" + this.vit);

System.out.println("角色攻擊力:" + this.atk);

System.out.println("角色防御力:" + this.def);

}

// 備忘錄內部類

@Data

@AllArgsConstructor

private class RoleStateMemento implements Memento {

private Integer vit; // 生命力

private Integer atk; // 攻擊力

private Integer def; // 防御力

}

}

// 測試結果

public static void main(String[] args){

System.out.println("===========打boss前狀態(tài)===========");

GameRole gameRole = new GameRole();

gameRole.init();

gameRole.showState();

// 保存進度

RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();

roleStateCaretaker.setMemento(gameRole.saveState());

System.out.println("===========打boss后狀態(tài)===========");

gameRole.fight();

gameRole.showState();

System.out.println("===========恢復狀態(tài)===========");

gameRole.recoverState(roleStateCaretaker.getMemento());

gameRole.showState();

// ===========打boss前狀態(tài)===========

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

// ===========打boss后狀態(tài)===========

// 角色生命力:0

// 角色攻擊力:0

// 角色防御力:0

// ===========恢復狀態(tài)===========

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

}

10.3 總結

適用場景:

需要保存歷史快照的場景。希望在對象之外保存狀態(tài),且除了自己其他類對象無法訪問狀態(tài)保存具體內容。

優(yōu)點:

簡化發(fā)起人實體類職責,隔離狀態(tài)存儲與獲取,實現(xiàn)了信息的封裝,客戶端無需關心狀態(tài)的保存細節(jié)。提供狀態(tài)回滾功能。

缺點:

消耗資源:如果需要保存的狀態(tài)過多時,每一次保存都會消耗很多內存。

11.解釋器模式(interpreter pattern)

解釋器模式給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。

特征:為了解釋一種語言,而為語言創(chuàng)建的解釋器。

解釋器模式包含以下主要角色:

抽象表達式(Abstract Expression)角色:定義解釋器的接口,約定解釋器的解釋操作,主要包含解釋方法 interpret()。終結符表達式(Terminal Expression)角色:是抽象表達式的子類,用來實現(xiàn)文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表達式與之相對應。非終結符表達式(Nonterminal Expression)角色:也是抽象表達式的子類,用來實現(xiàn)文法中與非終結符相關的操作,文法中的每條規(guī)則都對應于一個非終結符表達式。環(huán)境(Context)角色:通常包含各個解釋器需要的數據或是公共的功能,一般用來傳遞被所有解釋器共享的數據,后面的解釋器可以從這里獲取這些值。客戶端(Client):主要任務是將需要分析的句子或表達式轉換成使用解釋器對象描述的抽象語法樹,然后調用解釋器的解釋方法,當然也可以通過環(huán)境角色間接訪問解釋器的解釋方法。

11.1 代碼實現(xiàn)

下面以簡單的加減乘除為例子實現(xiàn)解釋器模式:

// 抽象角色 定義解釋器

public interface Expression {

int interpret();

}

@AllArgsConstructor

public class NumberTerminal implements Expression {

private int number;

@Override

public int interpret() {

return this.number;

}

}

// 非終結表達式(抽象類)

@AllArgsConstructor

public abstract class NonTerminal implements Expression {

protected Expression left;

protected Expression right;

}

// 非終結表達式(加法)

public class PlusNonTerminal extends NonTerminal implements Expression {

public PlusNonTerminal(Expression left, Expression right) {

super(left, right);

}

@Override

public int interpret() {

return left.interpret() + right.interpret();

}

}

// 非終結表達式(減法)

public class MinusNonTerminal extends NonTerminal implements Expression {

public MinusNonTerminal(Expression left, Expression right) {

super(left, right);

}

@Override

public int interpret() {

return left.interpret() - right.interpret();

}

}

// 非終結表達式(乘法)

public class MclNonTerminal extends NonTerminal implements Expression {

public MclNonTerminal(Expression left, Expression right) {

super(left, right);

}

@Override

public int interpret() {

return left.interpret() * right.interpret();

}

}

// 非終結表達式(除法)

public class DivisionNonTerminal extends NonTerminal implements Expression {

public DivisionNonTerminal(Expression left, Expression right) {

super(left, right);

}

@Override

public int interpret() {

return left.interpret() / right.interpret();

}

}

// 計算器類(實現(xiàn)運算邏輯)

public class Cal {

private Expression left;

private Expression right;

private Integer result;

public Cal(String expression) {

this.parse(expression);

}

private Integer parse(String expression) {

// 獲取表達式元素

String [] elements = expression.split(" ");

for (int i = 0; i < elements.length; i++) {

String element = elements[i];

// 判斷是否是運算符號

if (OperatorUtils.isOperator(element)) {

// 運算符號的右邊就是右終結符

right = new NumberTerminal(Integer.valueOf(elements[++i]));

//計算結果

result = OperatorUtils.getNonTerminal(left, right, element).interpret();

// 計算結果重新成為左終結符

left = new NumberTerminal(result);

} else {

left = new NumberTerminal(Integer.valueOf(element));

}

}

return result;

}

public Integer cal() {

return result;

}

}

// 操作工具類

public class OperatorUtils {

// 判斷是不是非終結符

public static boolean isOperator(String symbol) {

return symbol.equals("+") || symbol.equals("-") || symbol.equals("*")|| symbol.equals("/");

}

// 簡單工廠

public static NonTerminal getNonTerminal(Expression left, Expression right, String symbol) {

if (symbol.equals("+")) {

return new PlusNonTerminal(left, right);

} else if (symbol.equals("-")) {

return new MinusNonTerminal(left, right);

} else if (symbol.equals("*")) {

return new MclNonTerminal(left, right);

} else if (symbol.equals("/")) {

return new DivisionNonTerminal(left, right);

}

return null;

}

}

// 測試

// PS:此處進行的邏輯僅僅實現(xiàn)從左到右運算,并沒有先乘除后加減的邏輯

public static void main(String[] args) {

System.out.println(new Cal("10 + 20 - 40 * 60").cal()); // -600

System.out.println(new Cal("20 + 50 - 60 * 2").cal()); // 20

}

11.2 Spring中的解釋器模式

public static void main(String[] args) {

ExpressionParser expressionParser = new SpelExpressionParser();

org.springframework.expression.Expression expression = expressionParser.parseExpression("10 + 20 + 30 * 4");

Integer value = expression.getValue(Integer.class);

System.out.println(value); // 150

expression = expressionParser.parseExpression("(10+20+30)*4");

value = expression.getValue(Integer.class);

System.out.println(value); // 240

}

可以看到Spring中解釋器寫的是比較完善的,不僅有先乘除后加減和先括號進行運算的日常計算規(guī)則,而且對于空格也并沒有要求,僅需要寫出完整的表達式即可運算出來。

11.3 總結

適用場景:

一些重復出現(xiàn)的問題可以用一種簡單的語言來進行表述。一個簡單語法需要解釋的場景。

優(yōu)點:

擴展性強:在解釋器模式中由于語法是由很多類表示的,當語法規(guī)則更改時,只需修改相應的非終結符表達式即可;若擴展語法時,只需添加相應非終結符類即可。增加了新的解釋表達式的方式。易于實現(xiàn)文法:解釋器模式對應的文法應當是比較簡單且易于實現(xiàn)的,過于復雜的語法并不適合使用解釋器模式。

缺點:

語法規(guī)則較復雜時,會引起類膨脹。執(zhí)行效率比較低

柚子快報激活碼778899分享:Java中23種設計模式

http://yzkb.51969.com/

好文閱讀

評論可見,查看隱藏內容

本文內容根據網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉載請注明,如有侵權,聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/15796378.html

發(fā)布評論

您暫未設置收款碼

請在主題配置——文章設置里上傳

掃描二維碼手機訪問

文章目錄