柚子快報(bào)激活碼778899分享:Java中23種設(shè)計(jì)模式
柚子快報(bào)激活碼778899分享:Java中23種設(shè)計(jì)模式
一、創(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
Constructor
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
Constructor
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
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
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 extends Coffee> 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 extends TrainStation> 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接口, 還可以接管普通類的方法。
// 代售點(diǎn)賣票(代理類)
public class ProxyPoint implements MethodInterceptor {
public TrainStation getProxyObject(Class extends TrainStation> 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
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
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
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
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
private Handler
public Builder
if (this.head == null) {
this.head = this.tail = handler;
return this;
}
this.tail.next(handler);
this.tail = handler;
return this;
}
public Handler
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
@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
private List
private Integer cursor;
private T element;
public IteratorImpl(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
}
// 容器接口實(shí)現(xiàn)類
public class AggregateImpl
private List
@Override
public void add(T t) {
list.add(t);
}
@Override
public void remove(T t) {
list.remove(t);
}
@Override
public 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
public void showReport(IVisitor visitor) {
for (Employee employee : employeeList) {
employee.accept(visitor);
}
}
}
// 測(cè)試
public static void main(String[] args){
List
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ì)模式
好文閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。