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

目錄

柚子快報(bào)激活碼778899分享:Java中23種設(shè)計(jì)模式

柚子快報(bào)激活碼778899分享:Java中23種設(shè)計(jì)模式

http://yzkb.51969.com/

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

1.單例模式(Singleton Pattern)

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

1.1 餓漢式

特點(diǎn):類加載時(shí)就初始化,線程安全

// 構(gòu)造方法私有化

private Singleton() {

}

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

private static Singleton singleton = new Singleton();

public static Singleton getInstance() {

return singleton;

}

1.2 懶漢式

特點(diǎn):第一次調(diào)用才初始化,避免內(nèi)存浪費(fèi)。

/*

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

*/

private static Singleton singleton;

public static synchronized Singleton getInstance() {

if (singleton == null) {

singleton = new Singleton();

}

return singleton;

}

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

特點(diǎn):安全且在多線程情況下能保持高性能

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)內(nèi)部類

特點(diǎn):效果類似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 枚舉

特點(diǎn):自動(dòng)支持序列化機(jī)制,絕對(duì)防止多次實(shí)例化

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

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

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

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

private static boolean singletonFlag = false;

private Singleton() {

if (singleton != null || singletonFlag) {

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

}

singletonFlag = true;

}

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

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

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

1.7 容器式單例

當(dāng)程序中的單例對(duì)象非常多的時(shí)候,則可以使用容器對(duì)所有單例對(duì)象進(jìn)行管理,如下:

public class ContainerSingleton {

private ContainerSingleton() {}

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

public static Object getInstance(Class clazz) throws Exception {

String className = clazz.getName();

// 當(dāng)容器中不存在目標(biāo)對(duì)象時(shí)則先生成對(duì)象再返回該對(duì)象

if (!singletonMap.containsKey(className)) {

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

singletonMap.put(className, instance);

return instance;

}

// 否則就直接返回容器里的對(duì)象

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單例

不保證整個(gè)應(yīng)用全局唯一,但保證線程內(nèi)部全局唯一,以空間換時(shí)間,且線程安全。

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他們的對(duì)象是不一樣的,但是線程內(nèi)部,他們的對(duì)象是一樣的,這就是線程內(nèi)部保證唯一。

1.9 總結(jié)

適用場(chǎng)景:

需要確保在任何情況下絕對(duì)只需要一個(gè)實(shí)例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。

優(yōu)點(diǎn):

在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開銷。可以避免對(duì)資源的多重占用。設(shè)置全局訪問點(diǎn),嚴(yán)格控制訪問。

缺點(diǎn):

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

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

2.1 簡(jiǎn)單工廠模式

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

2.1.1 基礎(chǔ)版

// 工廠類

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;

}

}

// 產(chǎn)品基類

public interface Coffee {

}

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

public class Cappuccino implements Coffee {

}

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

2.1.2 升級(jí)版

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

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

public static Coffee create(Class clazz) throws Exception {

if (clazz != null) {

return clazz.newInstance();

}

return null;

}

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

2.1.3 總結(jié)

適用場(chǎng)景:

工廠類負(fù)責(zé)創(chuàng)建的對(duì)象較少??蛻舳酥恍枰獋魅牍S類的參數(shù),對(duì)于如何創(chuàng)建的對(duì)象的邏輯不需要關(guān)心。

優(yōu)點(diǎn):

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

缺點(diǎn):

工廠類的職責(zé)相對(duì)過重,增加新的產(chǎn)品類型的時(shí)需要修改工廠類的判斷邏輯,違背了開閉原則。不易于擴(kuò)展過于復(fù)雜的產(chǎn)品結(jié)構(gòu)。

2.2 工廠方法模式

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

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

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

2.2.1 代碼實(shí)現(xiàn)

// 抽象工廠

public interface CoffeeFactory {

Coffee create();

}

// 具體工廠

public class CappuccinoFactory implements CoffeeFactory {

@Override

public Coffee create() {

return new Cappuccino();

}

}

// 抽象產(chǎn)品

public interface Coffee {

}

// 具體產(chǎn)品

public class Cappuccino implements Coffee {

}

2.2.2 總結(jié)

適用場(chǎng)景:

創(chuàng)建對(duì)象需要大量的重復(fù)代碼??蛻舳耍☉?yīng)用層)不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建和實(shí)現(xiàn)等細(xì)節(jié)。一個(gè)類通過其子類來指定創(chuàng)建哪個(gè)對(duì)象。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

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

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

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

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

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

3.1 代碼實(shí)現(xiàn)

// 咖啡店 抽象工廠

public interface CoffeeShopFactory {

// 咖啡類

Coffee createCoffee();

// 甜點(diǎn)類

Dessert createDessert();

}

// 美式風(fēng)格工廠

public class AmericanFactory implements CoffeeShopFactory {

@Override

public Coffee createCoffee() {

return new Americano();

}

@Override

public Dessert createDessert() {

return new Cheesecake();

}

}

// 意式風(fēng)格工廠

public class ItalyFactory implements CoffeeShopFactory {

@Override

public Coffee createCoffee() {

return new Cappuccino();

}

@Override

public Dessert createDessert() {

return new Tiramisu();

}

}

類圖

3.2 總結(jié)

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

產(chǎn)品等級(jí):同一個(gè)繼承體系

適用場(chǎng)景:

客戶端(應(yīng)用層)不依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建和實(shí)現(xiàn)等細(xì)節(jié)。強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品對(duì)象(屬于同一產(chǎn)品族)一起使用創(chuàng)建對(duì)象需要大量重復(fù)的代碼。提供一個(gè)產(chǎn)品類的庫(kù),所有的產(chǎn)品以同樣的接口出現(xiàn),從而使客戶端不依賴于具體實(shí)現(xiàn)。

優(yōu)點(diǎn):

當(dāng)一個(gè)產(chǎn)品族中的多個(gè)對(duì)象被設(shè)計(jì)成一起工作時(shí),它能保證客戶端始終只使用同一個(gè)產(chǎn)品族中的對(duì)象。

缺點(diǎn):

當(dāng)產(chǎn)品族中需要增加一個(gè)新的產(chǎn)品時(shí),所有的工廠類都需要進(jìn)行修改。

4.原型模式(Prototype)

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

原型模式包含如下角色:

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

@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)

}

}

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

4.1 淺克隆

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

@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))

}

可以看到,當(dāng)修改了stu2的姓名時(shí),stu1的姓名同樣也被修改了,這說明stu1和stu2是同一個(gè)對(duì)象,這就是淺克隆的特點(diǎn),對(duì)具體原型類中的引用類型的屬性進(jìn)行引用的復(fù)制。同時(shí),這也可能是淺克隆所帶來的弊端,因?yàn)榻Y(jié)合該例子的原意,顯然是想在班級(jí)中新增一名叫李四的學(xué)生,而非讓所有的學(xué)生都改名叫李四,于是我們這里就要使用深克隆。

4.2 深克隆

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

@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))

}

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

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

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

// Clazz類

private static Clazz clazz = new Clazz();

private Clazz(){}

public static Clazz getInstance() {return clazz;}

// 測(cè)試

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并不相等,也就是說他們并不是同一個(gè)對(duì)象,也就是單例被破壞了。

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

@Override

protected Object clone() throws CloneNotSupportedException {

return clazz;

}

// 測(cè)試輸出

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

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

另外我們知道,單例就是只有一個(gè)實(shí)例對(duì)象,如果重寫了clone()方法保證單例的話,那么通過克隆出來的對(duì)象則不可以重新修改里面的屬性,因?yàn)樾薷囊院缶蜁?huì)連同克隆對(duì)象一起被修改,所以是需要單例還是克隆,在實(shí)際應(yīng)用中需要好好衡量。

4.4 總結(jié)

適用場(chǎng)景:

類初始化消耗資源較多。new產(chǎn)生的一個(gè)對(duì)象需要非常繁瑣的過程(數(shù)據(jù)準(zhǔn)備、訪問權(quán)限等)。構(gòu)造函數(shù)比較復(fù)雜。循環(huán)體中生產(chǎn)大量對(duì)象時(shí)。

優(yōu)點(diǎn):

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

缺點(diǎn):

必須配備克?。ɑ蛘呖煽截悾┓椒?。當(dāng)對(duì)已有類進(jìn)行改造的時(shí)候,需要修改代碼,違反了開閉原則。深克隆、淺克隆需要運(yùn)用得當(dāng)。

5.建造者模式(Builder)

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

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

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

5.1 常規(guī)寫法

//產(chǎn)品類 電腦

@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全家桶電腦(手動(dòng)狗頭)

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("太陽(yáng)神機(jī)箱");

}

@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();

}

}

// 測(cè)試

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=太陽(yáng)神機(jī)箱)

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

}

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

5.2 簡(jiǎn)化寫法

// 把指揮者類和抽象建造者合在一起的簡(jiǎn)化建造者類

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;

}

}

// 測(cè)試

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("太陽(yáng)神機(jī)箱");

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

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

}

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

5.3 鏈?zhǔn)綄懛?/p>

// 鏈?zhǔn)綄懛ńㄔ煺哳?/p>

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;

}

}

// 測(cè)試

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("太陽(yáng)神機(jī)箱").build();

System.out.println(asusComputer);

}

可以看到,其實(shí)鏈?zhǔn)綄懛ㄅc普通寫法的區(qū)別并不大,只是在建造者類組裝部件的時(shí)候,同時(shí)將建造者類返回即可,使用鏈?zhǔn)綄懛ㄊ褂闷饋砀奖?,某種程度上也可以提高開發(fā)效率。從軟件設(shè)計(jì)上,對(duì)程序員的要求比較高。比較常見的mybatis-plus中的條件構(gòu)造器就是使用的這種鏈?zhǔn)綄懛ā?/p>

5.4 總結(jié)

適用場(chǎng)景:

適用于創(chuàng)建對(duì)象需要很多步驟,但是步驟順序不一定固定。如果一個(gè)對(duì)象有非常復(fù)雜的內(nèi)部結(jié)構(gòu)(屬性),把復(fù)雜對(duì)象的創(chuàng)建和使用進(jìn)行分離。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

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

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

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

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

二、結(jié)構(gòu)型模式

1.代理模式(Proxy Pattern)

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

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

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

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

1.1 靜態(tài)代理

靜態(tài)代理就是指我們?cè)诮o一個(gè)類擴(kuò)展功能的時(shí)候,我們需要去書寫一個(gè)靜態(tài)的類,相當(dāng)于在之前的類上套了一層,這樣我們就可以在不改變之前的類的前提下去對(duì)原有功能進(jìn)行擴(kuò)展,靜態(tài)代理需要代理對(duì)象和目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口。

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

public interface TrainStation {

void sellTickets();

}

// 廣州火車站賣票

public class GuangzhouTrainStation implements TrainStation {

@Override

public void sellTickets() {

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

}

}

// 代售點(diǎn)賣票(代理類)

public class ProxyPoint implements TrainStation {

// 目標(biāo)對(duì)象(代理火車站售票)

private TrainStation station = new GuangzhouTrainStation();

@Override

public void sellTickets() {

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

station.sellTickets();

}

public static void main(String[] args) {

ProxyPoint proxyPoint = new ProxyPoint();

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

// 廣州火車站賣票啦

proxyPoint.sellTickets();

}

}

// 測(cè)試

public static void main(String[] args) {

ProxyPoint proxyPoint = new ProxyPoint();

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

// 火車站賣票啦

proxyPoint.sellTickets();

}

可以從上面代碼看到,我們?cè)L問的是ProxyPoint對(duì)象,也就是說ProxyPoint是作為訪問對(duì)象和目標(biāo)對(duì)象的中介的,同時(shí)也對(duì)sellTickets方法進(jìn)行了增強(qiáng)(代理點(diǎn)收取加收5%手續(xù)費(fèi))。

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

而缺點(diǎn)也比較明顯,那就是代理類和目標(biāo)類必須有共同接口(父類),并且需要為每一個(gè)目標(biāo)類維護(hù)一個(gè)代理類,當(dāng)需要代理的類很多時(shí)會(huì)創(chuàng)建出大量代理類。一旦接口或父類的方法有變動(dòng),目標(biāo)對(duì)象和代理對(duì)象都需要作出調(diào)整。

1.2 動(dòng)態(tài)代理

代理類在代碼運(yùn)行時(shí)創(chuàng)建的代理稱之為動(dòng)態(tài)代理。動(dòng)態(tài)代理中代理類并不是預(yù)先在Java代碼中定義好的,而是運(yùn)行時(shí)由JVM動(dòng)態(tài)生成,并且可以代理多個(gè)目標(biāo)對(duì)象。

1.2.1 jdk動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理是Java JDK自帶的一個(gè)動(dòng)態(tài)代理實(shí)現(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("深圳火車站賣票啦");

}

}

// 代售點(diǎn)賣票(代理類)

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ù)費(fèi)");

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

}

}

// 測(cè)試

public static void main(String[] args) {

ProxyPoint proxy = new ProxyPoint();

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

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

// 廣州火車站賣票啦

guangzhouTrainStation.sellTickets();

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

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

// 深圳火車站賣票啦

shenzhenTrainStation.sellTickets();

}

優(yōu)點(diǎn):

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

缺點(diǎn):

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

1.2.2 CGLIB動(dòng)態(tài)代理

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

cglib

cglib

${cglib-version}

// 代售點(diǎn)賣票(代理類)

public class ProxyPoint implements MethodInterceptor {

public TrainStation getProxyObject(Class trainStation) {

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

Enhancer enhancer =new Enhancer();

//設(shè)置父類的字節(jié)碼對(duì)象

enhancer.setSuperclass(trainStation);

//設(shè)置回調(diào)函數(shù)

enhancer.setCallback(this);

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

return (TrainStation) enhancer.create();

}

@Override

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

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

return methodProxy.invokeSuper(o, objects);

}

}

// 測(cè)試

public static void main(String[] args) {

ProxyPoint proxy = new ProxyPoint();

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

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

// 廣州火車站賣票啦

guangzhouTrainStation.sellTickets();

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

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

// 深圳火車站賣票啦

shenzhenTrainStation.sellTickets();

}

1.3 總結(jié)

應(yīng)用場(chǎng)景:

保護(hù)目標(biāo)對(duì)象。增強(qiáng)目標(biāo)對(duì)象。

優(yōu)點(diǎn):

代理模式能將代理對(duì)象與真實(shí)被調(diào)用的目標(biāo)對(duì)象分離。一定程度上降低了系統(tǒng)的耦合程度,易于擴(kuò)展。代理可以起到保護(hù)目標(biāo)對(duì)象的作用。增強(qiáng)目標(biāo)對(duì)象的職責(zé)。

缺點(diǎn):

代理模式會(huì)造成系統(tǒng)設(shè)計(jì)中類的數(shù)目增加。在客戶端和目標(biāo)對(duì)象之間增加了一個(gè)代理對(duì)象,請(qǐng)求處理速度變慢。增加了系統(tǒng)的復(fù)雜度。

兩種動(dòng)態(tài)代理的對(duì)比:

JDK動(dòng)態(tài)代理的特點(diǎn):

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

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

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

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

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

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

2.1 類適配器

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

類圖

// 適配者 220V電壓

public class AC220 {

public int output() {

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

return 220;

}

}

// 目標(biāo) 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適配轉(zhuǎn)換成" + output5 + "V");

return output5;

}

}

// 測(cè)試

public static void main(String[] args) {

PowerAdapter powerAdapter = new PowerAdapter();

// 輸出220V交流電

powerAdapter.output();

// 輸出220V交流電

// 220V適配轉(zhuǎn)換成5V

powerAdapter.output5();

}

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

2.2 對(duì)象適配器

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

// 電源接口

public interface Power {

int output();

}

// 適配者 220V電壓

public class AC220 implements Power {

@Override

public int output() {

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

return 220;

}

}

// 目標(biāo) 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適配轉(zhuǎn)換成" + output5 + "V");

return output5;

}

}

// 測(cè)試

public static void main(String[] args) {

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

// 輸出220V交流電

// 220V適配轉(zhuǎn)換成5V

powerAdapter.output5();

}

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

2.3 接口適配器

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

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

public interface DC {

int output5();

int output12();

int output24();

int output30();

}

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

@AllArgsConstructor

public class PowerAdapter implements DC {

private Power power;

@Override

public int output5() {

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

return 5;

}

@Override

public int output12() {

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

return 12;

}

@Override

public int output24() {

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

return 24;

}

@Override

public int output30() {

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

return 30;

}

}

2.4 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

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

3.裝飾模式(Decorator Pattern)

裝飾模式,是指在不改變?cè)袑?duì)象的基礎(chǔ)上,將功能附加到對(duì)象上,提供了比繼承更有彈性的替代方案(擴(kuò)展原有對(duì)象的功能)

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

抽象構(gòu)件(Component)角色 :定義一個(gè)抽象接口以規(guī)范準(zhǔn)備接收附加責(zé)任的對(duì)象。具體構(gòu)件(Concrete Component)角色 :實(shí)現(xiàn)抽象構(gòu)件,通過裝飾角色為其添加一些職責(zé)。抽象裝飾(Decorator)角色 : 繼承或?qū)崿F(xiàn)抽象構(gòu)件,并包含具體構(gòu)件的實(shí)例,可以通過其子類擴(kuò)展具體構(gòu)件的功能。具體裝飾(ConcreteDecorator)角色 :實(shí)現(xiàn)抽象裝飾的相關(guān)方法,并給具體構(gòu)件對(duì)象添加附加的責(zé)任。

3.1 繼承方式

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

// 炒飯類

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;

}

}

// 測(cè)試方法

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元

}

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

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;

}

}

// 測(cè)試方法

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元

}

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

3.3 總結(jié)

裝飾器模式與代理模式對(duì)比:

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

適用場(chǎng)景:

用于擴(kuò)展一個(gè)類的功能或者給一個(gè)類添加附加職責(zé)。動(dòng)態(tài)的給一個(gè)對(duì)象添加功能,這些功能同樣也可以再動(dòng)態(tài)的撤銷。

優(yōu)點(diǎn):

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

缺點(diǎn):

會(huì)出現(xiàn)更多的代碼,更多的類,增加程序的復(fù)雜性。動(dòng)態(tài)裝飾時(shí),多層裝飾會(huì)更復(fù)雜。

4.橋接模式(Bridge Pattern)

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

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

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

4.1 代碼實(shí)現(xiàn)

下面以一個(gè)多系統(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);

}

}

類關(guān)系圖

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

4.2 總結(jié)

適用場(chǎng)景:

在抽象和具體實(shí)現(xiàn)之間需要增加更多的靈活性的場(chǎng)景。一個(gè)類存在兩個(gè)(或多個(gè))獨(dú)立變化的維度,而這兩個(gè)(或多個(gè))維度都需要獨(dú)立進(jìn)行擴(kuò)展。不希望使用繼承,或因?yàn)槎鄬永^承導(dǎo)致系統(tǒng)類的個(gè)數(shù)劇增。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

5.外觀模式(Facade)

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

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

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

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

5.1 代碼實(shí)現(xiàn)

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

public class Light {

public void on() {

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

}

public void off() {

System.out.println("關(guān)燈");

}

}

public class Tv {

public void on() {

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

}

public void off() {

System.out.println("關(guān)電視");

}

}

public class Fan {

public void on() {

System.out.println("開風(fēng)扇");

}

public void off() {

System.out.println("關(guān)風(fēng)扇");

}

}

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();

//睡覺

//關(guān)燈

//關(guān)電視

//開風(fēng)扇

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

//起床

//開燈

//開電視

//關(guān)風(fēng)扇

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

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

smartSpeaker.say("Emmm");

}

5.2 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

當(dāng)增加子系統(tǒng)和擴(kuò)展子系統(tǒng)行為時(shí),可能容易帶來未知風(fēng)險(xiǎn)。不符合開閉原則。某些情況下可能違背單一職責(zé)原則。

6.組合模式(Composite Pattern)

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

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

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

6.1 代碼實(shí)現(xiàn)

下面以一個(gè)添加菜單的例子通過代碼實(shí)現(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);

}

}

// 測(cè)試方法

public static void main(String[] args) {

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

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));

//將二級(jí)菜單添加到一級(jí)菜單中

component.add(menu1);

component.add(menu2);

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

component.print();

}

// 測(cè)試結(jié)果

系統(tǒng)管理

--用戶管理

----新增用戶

----修改用戶

----刪除用戶

--角色管理

----新增角色

----修改角色

----刪除角色

----綁定用戶

6.2 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

清楚地定義分層次的復(fù)雜對(duì)象,表示對(duì)象的全部或部分層次。讓客戶端忽略了層次的差異,方便對(duì)整個(gè)層次結(jié)構(gòu)進(jìn)行控制。簡(jiǎn)化客戶端代碼。符合開閉原則。

缺點(diǎn):

限制類型時(shí)會(huì)較為復(fù)雜。使設(shè)計(jì)變得更加抽象。

分類:

透明組合模式

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

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

7.享元模式(Flyweight Pattern)

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

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

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

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

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

7.1 代碼實(shí)現(xiàn)

下面通過查詢火車票的例子來用代碼進(jìn)行模擬實(shí)現(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 + "價(jià)格:" + 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("使用數(shù)據(jù)庫(kù)獲取火車票:" + key);

ITicket ticket = new TrainTicket(from, to);

pool.put(key, ticket);

return ticket;

}

}

// 測(cè)試

public static void main(String[] args) {

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

//使用數(shù)據(jù)庫(kù)獲取火車票:北京->上海

//北京->上海:二等座價(jià)格:20

ticket.show("二等座");

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

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

//北京->上海:商務(wù)座價(jià)格:69

ticket1.show("商務(wù)座");

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

//使用數(shù)據(jù)庫(kù)獲取火車票:上海->北京

//上海->北京:一等座價(jià)格:406

ticket2.show("一等座");

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

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

}

可以看到ticket和ticket2是使用數(shù)據(jù)庫(kù)查詢的,而ticket1是使用緩存查詢的,同時(shí)ticket == ticket1返回的是true,ticket == ticket2返回的是false,證明ticket和ticket1是共享的對(duì)象。

7.2 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

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

三、行為型模式

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

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

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

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

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

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

1.1 代碼實(shí)現(xiàn)

下面以一個(gè)簡(jiǎn)單的請(qǐng)假流程來通過代碼來實(shí)現(xiàn):

public abstract class DayOffProcess {

// 請(qǐng)假模板

public final void dayOffProcess() {

// 領(lǐng)取申請(qǐng)表

this.pickUpForm();

// 填寫申請(qǐng)信息

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("填寫申請(qǐng)信息");

}

private void pickUpForm() {

System.out.println("領(lǐng)取申請(qǐng)表");

}

}

public class ZhangSan extends DayOffProcess {

@Override

protected void summit() {

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

}

@Override

protected void signUp() {

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

}

}

public class Lisi extends DayOffProcess {

@Override

protected void summit() {

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

}

@Override

protected void signUp() {

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

}

}

// 測(cè)試方法

public static void main(String[] args) {

DayOffProcess zhangsan = new ZhangSan();

//領(lǐng)取申請(qǐng)表

//填寫申請(qǐng)信息

//提交到技術(shù)部審批

//張三簽名

//行政部備案

zhangsan.dayOffProcess();

DayOffProcess lisi = new Lisi();

//領(lǐng)取申請(qǐng)表

//填寫申請(qǐng)信息

//提交到市場(chǎng)部審批

//李四簽名

//行政部備案

lisi.dayOffProcess();

}

1.2 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

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

2.策略模式(Strategy Pattern)

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

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

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

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

// 會(huì)員卡接口

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("普通會(huì)員沒有折扣");

}

}

// 會(huì)員卡容器類

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();

}

}

// 測(cè)試方法

public static void main(String[] args) {

//金卡打7折

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

//銀卡打8折

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

//普通會(huì)員沒有折扣

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

}

用一個(gè)容器(Map)裝起來,可以通過傳進(jìn)來的參數(shù)直接獲取對(duì)應(yīng)的策略,避免了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)金支付 默認(rè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);

}

}

// 測(cè)試方法

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 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

符合開閉原則。避免使用多重條件語句??梢蕴岣咚惴ǖ谋C苄院桶踩浴R子跀U(kuò)展。

缺點(diǎn):

客戶端必須知道所有的策略,并且自行決定使用哪一個(gè)策略類。代碼中會(huì)產(chǎn)生非常多的策略類,增加維護(hù)難度。

3.命令模式(Command Pattern)

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

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

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

3.1 代碼實(shí)現(xiàn)

下面以一個(gè)播放器的例子來進(jìn)行代碼實(shí)現(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();

}

}

// 測(cè)試方法

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 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

通過引入中間件(抽象接口),解耦了命令的請(qǐng)求與實(shí)現(xiàn)。擴(kuò)展性良好,可以很容易地增加新命令。支持組合命令,支持命令隊(duì)列??梢栽诂F(xiàn)有的命令的基礎(chǔ)上,增加額外功能。

缺點(diǎn):

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

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

職責(zé)鏈模式是將鏈中每一個(gè)節(jié)點(diǎn)看作是一個(gè)對(duì)象,每個(gè)節(jié)點(diǎn)處理的請(qǐng)求均不同,且內(nèi)部自動(dòng)維護(hù)一個(gè)下一節(jié)點(diǎn)對(duì)象。當(dāng)一個(gè)請(qǐng)求從鏈?zhǔn)降氖锥税l(fā)出時(shí),會(huì)沿著鏈的路徑依次傳遞給每一個(gè)節(jié)點(diǎn)對(duì)象,直至有對(duì)象處理這個(gè)請(qǐng)求為止。

職責(zé)鏈模式主要包含以下角色:

抽象處理者(Handler)角色:定義一個(gè)處理請(qǐng)求的接口,包含抽象處理方法和一個(gè)后繼連接。具體處理者(Concrete Handler)角色:實(shí)現(xiàn)抽象處理者的處理方法,判斷能否處理本次請(qǐng)求,如果可以處理請(qǐng)求則處理,否則將該請(qǐng)求轉(zhuǎn)給它的后繼者??蛻纛悾–lient)角色:創(chuàng)建處理鏈,并向鏈頭的具體處理者對(duì)象提交請(qǐng)求,它不關(guān)心處理細(xì)節(jié)和請(qǐng)求的傳遞過程。

4.1 代碼實(shí)現(xiàn)

下面以一個(gè)簡(jiǎn)單的登錄校驗(yàn)流程來通過代碼進(jìn)行實(shí)現(xiàn):

// 用戶實(shí)體類

@Data

public class User {

private String username;

private String password;

private String role;

}

// handler抽象類

public abstract class Handler {

protected Handler next;

// 返回handler方便鏈?zhǔn)讲僮?/p>

public void next(Handler next) {

this.next = next;

}

// 流程開始的方法

public abstract void doHandler(User user);

}

// 校驗(yàn)用戶名或者密碼是否為空

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("校驗(yàn)通過");

next.doHandler(user);

}

}

// 登錄校驗(yàn),校驗(yàn)用戶名是否匹配密碼

public class LoginHandler extends Handler {

@Override

public void doHandler(User user) {

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

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

return;

}

user.setRole("admin");

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

next.doHandler(user);

}

}

// 權(quán)限校驗(yàn)

public class AuthHandler extends Handler {

@Override

public void doHandler(User user) {

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

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

return;

}

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

}

}

// 登錄流程

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);

}

}

// 測(cè)試方法

public static void main(String[] args){

User user = new User();

//校驗(yàn)通過

//用戶名或者密碼不正確!請(qǐng)檢查!

user.setUsername("pyy52hz");

user.setPassword("1234567");

LoginService loginService = new LoginService();

loginService.login(user);

//校驗(yàn)通過

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

//角色為管理員,可以進(jìn)行下一步操作!

user.setUsername("pyy52hz");

user.setPassword("123456");

loginService.login(user);

}

4.3 結(jié)合建造者模式

與基礎(chǔ)版本區(qū)別主要是Handler類中新增一個(gè)Builder的內(nèi)部類,以及流程類里改用鏈?zhǔn)綄懛?,具體如下:

// handler抽象類

public abstract class Handler {

protected Handler next;

// 返回handler方便鏈?zhǔn)讲僮?/p>

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 總結(jié)

適用場(chǎng)景:

多個(gè)對(duì)象可以處理同一請(qǐng)求,但具體由哪個(gè)對(duì)象處理則在運(yùn)行時(shí)動(dòng)態(tài)決定。在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求。可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求。

優(yōu)點(diǎn):

將請(qǐng)求與處理解耦。請(qǐng)求處理者(節(jié)點(diǎn)對(duì)象)只需關(guān)注自己感興趣的請(qǐng)求進(jìn)行處理即可,對(duì)于不感興趣的請(qǐng)求,直接轉(zhuǎn)發(fā)給下一級(jí)節(jié)點(diǎn)對(duì)象。具備鏈?zhǔn)絺鬟f處理請(qǐng)求功能,請(qǐng)求發(fā)送者無需知曉鏈路結(jié)構(gòu),只需等待請(qǐng)求處理結(jié)果。鏈路結(jié)構(gòu)靈活,可以通過改變鏈路結(jié)構(gòu)動(dòng)態(tài)地新增或刪減責(zé)任。易于擴(kuò)展新的請(qǐng)求處理類(節(jié)點(diǎn)),符合開閉原則。

缺點(diǎn):

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

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

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

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

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

5.1 代碼實(shí)現(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() {

}

}

// 關(guān)門狀態(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("電梯門關(guān)閉了!");

}

@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();

}

}

// 運(yùn)行狀態(tài)

public class RunState extends LiftState {

@Override

public void open() {

}

@Override

public void close() {

}

@Override

public void run() {

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

}

@Override

public void stop() {

super.context.setLiftState(Context.STOP_STATE);

super.context.stop();

}

}

// 停止?fàn)顟B(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();

}

}

// 測(cè)試

public static void main(String[] args){

Context context = new Context();

context.setLiftState(new CloseState());

//電梯門打開了

//電梯門關(guān)閉了!

//電梯正在運(yùn)行...

//電梯停止了!

context.open();

context.close();

context.run();

context.stop();

}

5.2 spring的狀態(tài)機(jī)

// Todo

5.3 總結(jié)

適用場(chǎng)景:

行為隨狀態(tài)改變而改變的場(chǎng)景。一個(gè)操作中含有龐大的多分支結(jié)構(gòu),并且這些分支取決于對(duì)象的狀態(tài)。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

6.觀察者模式(Observer Mode)

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

6.1 代碼實(shí)現(xiàn)

通過一個(gè)微信用戶(觀察者)訂閱公眾號(hào)(被觀察者)接收公眾號(hào)推送消息的例子來進(jìn)行簡(jiǎn)單的代碼實(shí)現(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);

}

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

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);

}

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

@Override

public void notify(String message) {

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

}

}

// 測(cè)試

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("薛之謙演唱會(huì)要來到廣州啦!");

// 運(yùn)行結(jié)果

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

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

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

}

6.2 JDK實(shí)現(xiàn)

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

6.2.1 Observable類

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

6.2.2 Observer 接口

Observer 接口是抽象觀察者,它監(jiān)視目標(biāo)對(duì)象的變化,當(dāng)目標(biāo)對(duì)象發(fā)生變化時(shí),觀察者得到通知,并調(diào)用 update 方法,進(jìn)行相應(yīng)的工作。

6.2.3 代碼實(shí)現(xiàn)

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

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

@Data

@AllArgsConstructor

public class Subject extends Observable {

// 公眾號(hào)的名字

private String name;

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

public void notifyMessage(String message) {

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

super.setChanged();

super.notifyObservers(message);

}

}

@AllArgsConstructor

public class WeixinUser implements Observer {

private String name;

/**

* @param o 被觀察者

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

*/

@Override

public void update(Observable o, Object arg) {

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

}

}

// 測(cè)試

public static void main(String[] args){

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

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

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

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

subject.addObserver(user1);

subject.addObserver(user2);

subject.addObserver(user3);

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

// 返回結(jié)果

// 演唱會(huì)消息發(fā)布公眾號(hào)發(fā)布消息:薛之謙演唱會(huì)要來到廣州啦!請(qǐng)關(guān)注用戶留意接收!

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

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

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

}

6.3 Google的Guava實(shí)現(xiàn)

EventBus 術(shù)語解釋備注事件(消息)可以向事件總線(EventBus)發(fā)布的對(duì)象通常是一個(gè)類,不同的消息事件用不同的類來代替,消息內(nèi)容就是類里面的屬性訂閱向事件總線注冊(cè)監(jiān)聽者,以接受事件的行為EventBus.register(Object),參數(shù)就是監(jiān)聽者監(jiān)聽者提供一個(gè)處理方法,希望接受和處理事件的對(duì)象通常也是一個(gè)類,里面有消息的處理方法處理方法監(jiān)聽者提供的公共方法,事件總線使用該方法向監(jiān)聽者發(fā)送事件;該方法應(yīng)使用 Subscribe 注解監(jiān)聽者里面添加一個(gè) Subscribe 注解的方法,就可以認(rèn)為是消息的處理方法發(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);

}

// 測(cè)試

public static void main(String[] args){

// 消息總線

EventBus eventBus = new EventBus();

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

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

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

// 返回結(jié)果

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

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

}

}

6.4 總結(jié)

適用場(chǎng)景:

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

多層級(jí)嵌套使用,形成一種鏈?zhǔn)接|發(fā)機(jī)制,使得事件具備跨域(跨越兩種觀察者類型)通知。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

7.中介者模式(mediator pattern)

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

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

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

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

7.1 代碼實(shí)現(xiàn)

通過一個(gè)租房例子簡(jiǎn)單實(shí)現(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)絡(luò)方法

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);

}

}

}

// 測(cè)試

public static void main(String[] args){

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

MediatorCompany mediatorCompany = new MediatorCompany();

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

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

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

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

mediatorCompany.setHouseOwner(houseOwner);

mediatorCompany.setTenant(tenant);

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

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

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

// 測(cè)試結(jié)果

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

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

}

7.2 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

中介者模式中將原本多個(gè)對(duì)象直接的相互依賴變成了中介者和多個(gè)同事類的依賴關(guān)系。當(dāng)同事類越多時(shí),中介者就會(huì)越臃腫,變得復(fù)雜且難以維護(hù)。

8.迭代器模式(Iterator Pattern)

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

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

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

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

8.1 代碼實(shí)現(xiàn)

// 迭代器接口

public interface Iterator {

Boolean hasNext();

T next();

}

// 迭代器接口實(shí)現(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();

}

// 容器接口實(shí)現(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:具體測(cè)試的話可以自己寫一個(gè)集合測(cè)試一下即可

8.2 總結(jié)

適用場(chǎng)景:

訪問一個(gè)集合對(duì)象的內(nèi)容而無需暴露它的內(nèi)部表示。為遍歷不同的集合結(jié)構(gòu)提供一個(gè)統(tǒng)一的訪問接口。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

9.訪問者模式(Visitor Pattern)

訪問者模式是一種將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離的設(shè)計(jì)模式。是指封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作。

特征:可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。

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

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

9.1 代碼實(shí)現(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() + "工作內(nèi)容:" + engineer.getCodeLine() + "行代碼");

}

@Override

public void visit(Pm pm) {

System.out.println(pm.getName() + "工作內(nèi)容:" + pm.getProject() + "個(gè)項(xiàng)目");

}

}

@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);

}

}

// 具體元素(項(xiàng)目經(jīng)理)

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);

}

}

}

// 測(cè)試

public static void main(String[] args){

List employeeList = new ArrayList<>();

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

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

employeeList.add(new Pm("項(xiàng)目經(jīng)理A"));

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

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

employeeList.add(new Pm("項(xiàng)目經(jīng)理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

// 項(xiàng)目經(jīng)理AKPI為:4

// 工程師CKPI為:2

// 工程師DKPI為:0

// 項(xiàng)目經(jīng)理BKPI為:0

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

// 工程師A工作內(nèi)容:5811行代碼

// 工程師B工作內(nèi)容:9930行代碼

// 項(xiàng)目經(jīng)理A工作內(nèi)容:7個(gè)項(xiàng)目

// 工程師C工作內(nèi)容:4591行代碼

// 工程師D工作內(nèi)容:333行代碼

// 項(xiàng)目經(jīng)理B工作內(nèi)容:4個(gè)項(xiàng)目

}

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

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

9.2.1 分派

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

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

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

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

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

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

9.3 總結(jié)

適用場(chǎng)景:

數(shù)據(jù)結(jié)構(gòu)穩(wěn)定,作用于數(shù)據(jù)結(jié)構(gòu)的操作經(jīng)常變化的場(chǎng)景。需要數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離的場(chǎng)景。需要對(duì)不同數(shù)據(jù)類型(元素)進(jìn)行操作,而不使用分支判斷具體類型的場(chǎng)景。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

10.備忘錄模式(Memento Pattern)

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

特征:“后悔藥”

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

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

備忘錄有兩個(gè)等效的接口:

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

10.1 “白箱”備忘錄模式

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

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

// 游戲角色類

@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);

}

// 回復(fù)角色狀態(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)存儲(chǔ)類(備忘錄類)

@Data

@AllArgsConstructor

public class RoleStateMemento {

private Integer vit; // 生命力

private Integer atk; // 攻擊力

private Integer def; // 防御力

}

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

@Data

public class RoleStateCaretaker {

private RoleStateMemento roleStateMemento;

}

// 測(cè)試結(jié)果

public static void main(String[] args){

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

GameRole gameRole = new GameRole();

gameRole.init();

gameRole.showState();

// 保存進(jìn)度

RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();

roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

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

gameRole.fight();

gameRole.showState();

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

gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());

gameRole.showState();

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

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

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

// 角色生命力:0

// 角色攻擊力:0

// 角色防御力:0

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

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

}

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

10.2 “黑箱”備忘錄模式

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

將RoleStateMemento設(shè)為GameRole的內(nèi)部類,從而將RoleStateMemento對(duì)象封裝在GameRole 里面;在外面提供一個(gè)標(biāo)識(shí)接口Memento給RoleStateCaretaker及其他對(duì)象使用。這樣GameRole類看到的是RoleStateMemento所有的接口,而RoleStateCaretaker及其他對(duì)象看到的僅僅是標(biāo)識(shí)接口Memento所暴露出來的接口,從而維護(hù)了封裝型。

// 窄接口,標(biāo)識(shí)接口

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);

}

// 回復(fù)角色狀態(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);

}

// 備忘錄內(nèi)部類

@Data

@AllArgsConstructor

private class RoleStateMemento implements Memento {

private Integer vit; // 生命力

private Integer atk; // 攻擊力

private Integer def; // 防御力

}

}

// 測(cè)試結(jié)果

public static void main(String[] args){

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

GameRole gameRole = new GameRole();

gameRole.init();

gameRole.showState();

// 保存進(jìn)度

RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();

roleStateCaretaker.setMemento(gameRole.saveState());

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

gameRole.fight();

gameRole.showState();

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

gameRole.recoverState(roleStateCaretaker.getMemento());

gameRole.showState();

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

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

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

// 角色生命力:0

// 角色攻擊力:0

// 角色防御力:0

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

// 角色生命力:100

// 角色攻擊力:100

// 角色防御力:100

}

10.3 總結(jié)

適用場(chǎng)景:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

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

11.解釋器模式(interpreter pattern)

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

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

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

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

11.1 代碼實(shí)現(xiàn)

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

// 抽象角色 定義解釋器

public interface Expression {

int interpret();

}

@AllArgsConstructor

public class NumberTerminal implements Expression {

private int number;

@Override

public int interpret() {

return this.number;

}

}

// 非終結(jié)表達(dá)式(抽象類)

@AllArgsConstructor

public abstract class NonTerminal implements Expression {

protected Expression left;

protected Expression right;

}

// 非終結(jié)表達(dá)式(加法)

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();

}

}

// 非終結(jié)表達(dá)式(減法)

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();

}

}

// 非終結(jié)表達(dá)式(乘法)

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();

}

}

// 非終結(jié)表達(dá)式(除法)

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();

}

}

// 計(jì)算器類(實(shí)現(xiàn)運(yù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) {

// 獲取表達(dá)式元素

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

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

String element = elements[i];

// 判斷是否是運(yùn)算符號(hào)

if (OperatorUtils.isOperator(element)) {

// 運(yùn)算符號(hào)的右邊就是右終結(jié)符

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

//計(jì)算結(jié)果

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

// 計(jì)算結(jié)果重新成為左終結(jié)符

left = new NumberTerminal(result);

} else {

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

}

}

return result;

}

public Integer cal() {

return result;

}

}

// 操作工具類

public class OperatorUtils {

// 判斷是不是非終結(jié)符

public static boolean isOperator(String symbol) {

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

}

// 簡(jiǎn)單工廠

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;

}

}

// 測(cè)試

// PS:此處進(jìn)行的邏輯僅僅實(shí)現(xiàn)從左到右運(yù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中解釋器寫的是比較完善的,不僅有先乘除后加減和先括號(hào)進(jìn)行運(yùn)算的日常計(jì)算規(guī)則,而且對(duì)于空格也并沒有要求,僅需要寫出完整的表達(dá)式即可運(yùn)算出來。

11.3 總結(jié)

適用場(chǎng)景:

一些重復(fù)出現(xiàn)的問題可以用一種簡(jiǎn)單的語言來進(jìn)行表述。一個(gè)簡(jiǎn)單語法需要解釋的場(chǎng)景。

優(yōu)點(diǎn):

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

缺點(diǎn):

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

柚子快報(bào)激活碼778899分享:Java中23種設(shè)計(jì)模式

http://yzkb.51969.com/

好文閱讀

評(píng)論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

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

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問

文章目錄