要分析常用框架spring、mybatis、springboot、springcloud等的源碼,首先要了解各種設計模式,因為框架里面應用了各種設計模式
一、設計思想
學習設計模式最重要的是掌握設計思想和設計原則,理解了設計思想和設計原則並運用到平時的編碼中是最重要的!!!
1. 我們先來看下面的問題:
天天加班編程,編程到底都做的是什么?
擼代碼,加班擼代碼,寫接口、寫類、寫方法
用設計模式或做設計的作用是什么?
指導、規定該如何擼代碼,如何來寫接口、寫類、寫方法
為什么要做設計、用設計模式?
代碼會變,為應對變化,為了以后方便擴展。做到以不變應萬變,做一個會偷懶的程序員!
2.下面來看一下什么是設計思想
首先是從現實出發理清現實,在寫代碼之前先從實際分析,然后就開始寫代碼,寫代碼時要區分出不變的代碼和會變化的代碼,會變得代碼會怎么變,使用者如何隔絕這種變化,所謂的隔絕這種變化就是不讓調用者感知到內部的變化,只需要很簡單的方式就能使用不必關心內部的邏輯,這樣的話就要用到各種設計模式。不同的變化方式對應不同的設計模式。
設計的最終體現:如何來定義類、接口、方法
3. 設計思想—OOP
3.1 OOP中的幾個元素
1)類是做什么用的?
模擬現實,封裝數據與代碼
2)接口是做什么用的?
定義相接的口子
定義功能使用者和功能提供者間的接口
3)為什么要有接口?
隔離變化
4)抽象類是做什么用的?
包容不變與變的
3.2 OOP的三大特性
1)多態為我們提供了什么?
一種實現變化的方式
3.3 類與類之間的關系有哪些?
二、設計原則
1. 找出變化,分開變化和不變的
隔離,封裝變化的部分,讓其他部分不受它的影響。
2. 面向接口編程 ——隔離變化的方式
使用者使用接口,提供者實現接口。“接口”可以是超類!
3. 依賴倒置原則(里氏替換原則)——隔離變化的方式
依賴抽象,不要依賴具體類!
4. 對修改閉合,對擴展開放——隔離變化的方式
可以繼承一個類或者接口擴展功能,但是不能修改類或者接口的原有功能
5. 多用組合,少用繼承——靈活變化的方式
“有一個”可能比“是一個”更好。
6. 單一職責原則——方法設計的原則
每個方法只負責一個功能,不要把很多功能寫在一個方法里面
三、各種設計模式介紹
應用設計模式的目的:
易擴展,易維護
少改代碼,不改代碼
1.策略模式
示例:
京東、天貓雙十一促銷,各種商品有多種不同的促銷活動:
滿減:滿400減50
每滿減:每滿100減20
數量折扣:買兩件8折、三件7折
數量減:滿三件減最低價的一件
……
顧客下單時可選擇多種促銷活動的其中一種來下單
后端代碼中如何來靈活應對訂單金額的計算?以后還會有很多的促銷活動出現!
1.1 該如何來實現訂單金額的計算?
控制器OrderController.java
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; /** * 計算訂單的促銷金額 */ @RequestMapping("prepare") public Order prepareOrder(Order order, String promotion) { …….. return this.orderService.prepareOrder(order, promotion); } }
OrderService.java改怎么來寫呢
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { // 該如何寫 return order; } }
1.2 這樣可以嗎?
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { switch (promotion) { case "promotion-1": // 促銷1的算法 …… break; case "promotion-2": // 促銷2的算法 …… break; case "promotion-3": // 促銷3的算法 …… break; …… } return order; } }
營銷活動有很多,這個switch會變得很龐大,不利於維護,並且很容易引入新的問題
1.3 改進一下,這樣是不是好些了?
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { switch (promotion) { case "promotion-1": // 促銷1的算法 return calPromotion1(order); case "promotion-2": // 促銷2的算法 return calPromotion2(order); case "promotion-3": // 促銷3的算法 return calPromotion3(order); …… } return order; } private Order calPromotion1(Order order) { System.out.println("促銷1計算.............................."); return order; } ……. }
把每個促銷算法單獨抽出一個方法,新加入一個促銷活動只需要新增一個方法和case就可以了
這里利用了設計原則的方法設計原則:單一職責原則
但是這樣寫還會存在如下問題:
營銷活動經常變,這個switch就得經常改,還得不斷加促銷的算法方法…….
改代碼是bug的源泉,我們希望少改動OrderService!!!
分析:這里變的是什么?
促銷的金額的算法!同一行為的不同算法!
我們不希望OrderService被算法代碼爆炸!
1.4 再次改進
同一行為的不同算法實現,我們可以用接口來定義行為,不同的算法分別去實現接口。
這里利用了設計原則:對修改關閉,對擴展開放!
這就是策略模式的應用!
策略模式的的定義:
策略模式定義了一系列的算法,並將每一個算法封裝起來,而且使他們可以相互替換,讓算法獨立於使用它的用戶而獨立變化。
1.5 使用策略模式再次改進后的OrderService
@Service public class OrderService { public Order prepareOrder(Order order, String promotion) { switch (promotion) { case "promotion-1": // 促銷1的算法 return new Promotion1Calculation().calculate(order); case "promotion-2": // 促銷2的算法 return new Promotion2Calculation().calculate(order); case "promotion-3": // 促銷3的算法 return new Promotion3Calculation().calculate(order); ...... } } }
但是switch中的代碼還是會不斷變!!!switch中需要知道所有的實現!
如何讓OrderService的代碼不要改變?
把變的部分移出去!改怎么移呢?
1.6 通過一個工廠來專門負責創建各種促銷計算實現,就把變化移出來了!
@Component public class PromotionCalculationFactory { public PromotionCalculation getPromotionCalculation(String promotion) { switch (promotion) { case "promotion-1": // 促銷1的算法 return new Promotion1Calculation(); case "promotion-2": // 促銷2的算法 return new Promotion2Calculation(); case "promotion-3": // 促銷3的算法 return new Promotion3Calculation(); ...... } } }
這是簡單工廠模式:所有產品由一個工廠創建
@Service public class OrderService { @Autowired private PromotionCalculationFactory promotionCalculationFactory; public Order prepareOrder(Order order, String promotion) { return promotionCalculationFactory.getPromotionCalculation(promotion).calculate(order); } }
想要工廠中的代碼也不要隨促銷的變化而變化,你覺得該怎么辦?
方式一:promotion = beanName
把各種促銷算法的實現交給spring容器來管理,用戶選擇的促銷活動promotion 作為bean的名字,在PromotionCalculationFactory 工廠里面通過getBean("promotion")就能拿到各種促銷算法的實現了
方式一的偽代碼實現:
spring里面的bean配置:
<bean id="promotion1" calss="Promotion1Calculation"> <bean id="promotion2" calss="Promotion2Calculation"> <bean id="promotion3" calss="Promotion3Calculation">
PromotionCalculationFactory 工廠改寫:
@Component public class PromotionCalculationFactory { public PromotionCalculation getPromotionCalculation(String promotion) { return getBean("promotion1/promotion2/promotion3"); } } }
方式二: 配置promotion與實現類的對應關系
把用戶選擇的促銷活動promotion和對應的促銷算法的實現類放到map里面,或者存到數據庫里面,在PromotionCalculationFactory 工廠里面通過map.get("promotion"),或者從數據庫里面獲取對應促銷算法的實現類路徑通過Class.forName("促銷算法的實現類路徑")就能拿到各種促銷算法的實現了
方式二的偽代碼實現:
PromotionCalculationFactory 工廠改寫:
package com.study.design.mode.service; import java.util.Map; import org.springframework.stereotype.Component; @Component public class PromotionCalculationFactory { private Map<String, PromotionCalculation> maps; public PromotionCalculation getPromotionCalculation(String promotion) { PromotionCalculation prom = maps.get(promotion); if (prom == null) { // 從配置的地方加載 prom = getFromDb(promotion); if (prom != null) maps.put(promotion, prom); } return prom; } public void init() { // 第一次將所有的促銷策略都加載到Map中 } private PromotionCalculation getFromDb(String promotion) { // 從數據庫中取到對應的類名 //配置的格式: promotion1=com.study.dn.promotion.calculation.Promotion1 String className = 從數據庫(或其他配置源)中獲得; // Class c = Class.forName(className); // 實例化 // 返回 } }
2. 工廠模式
2.1 簡單工廠模式
一個工廠負責創建所有實例。比如上面的策略模式中使用的就是簡單工廠模式
根據傳入的工廠類型參數String創建對應的實例(產品)
2.2 工廠方法模式
父類中定義工廠方法,各子類在+factoryMethod():Product方法里面實現具體的實例創建
使用者持有具體的工廠ChildAClass、ChildBClass、ChildCClass,傳入對應的工廠ChildAClass、ChildBClass、ChildCClass創建對應的工廠實例
2.3 抽象工廠模式
定義一個工廠接口,所有具體工廠實現工廠接口
使用者調用FactoryProducer的getFactory(type)方法傳入type,type為AFactory、BFactory、CFactory對應的類型,就會返回對應的工廠AFactory、BFactory、CFactory,不需要傳入AFactory、BFactory、CFactory,因為type已經跟AFactory、BFactory、CFactory綁定了。
3. 裝飾者模式
示例:促銷活動可多重疊加,該如何靈活實現訂單金額計算?
OrderController
@RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; /** * 計算訂單的促銷金額,促銷按給入的順從疊加 */ @RequestMapping("prepare") public Order prepareOrder(Order order, String... promotion) { return this.orderService.prepareOrder(order, promotion); } }
OrderService
@Service public class OrderService { @Autowired private PromotionCalculationFactory promotionCalculationFactory; public Order prepareOrder(Order order, String... promotion) { for (String p : promotion) { order = promotionCalculationFactory. getPromotionCalculation(p).calculate(order); } return order; } }
裝飾者模式的定義:以裝飾的方式,動態地將責任附加到對象上。
說明:
不改變具體類代碼(被裝飾者ConcreteComponent),動態疊加增強行為功能。
若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案
相較於前面的for循環,有何區別?
當需要對一個類的多個方法進行增強,使用者會隨意使用被增強方法時,for循環就不夠靈活了。
責任鏈和裝飾者模式完成的是相同的事情。
裝飾者模式-代碼示例:
共同的需裝飾的行為定義成接口
public interface Component { String methodA(); int methodB(); }
被裝飾者實現接口Component
public class ConcreteComponent implements Component { public String methodA() { return "concrete-object"; } public int methodB() { return 100; } }
裝飾者實現接口Component
public class Decorator implements Component { //裝飾者包含被裝飾者(被裝飾者實現的接口) protected Component component; public Decorator(Component component) { super(); this.component = component; } public String methodA() { return this.component.methodA(); } public int methodB() { return this.component.methodB(); } }
裝飾者派生出的裝飾者
public class DecoratorA extends Decorator { public DecoratorA(Component component) { super(component); } public String methodA() { //在這里可以進行前置增強,實現要處理的邏輯 return this.component.methodA() + " + A"; //在這里可以進行后置增強,實現要處理的邏輯 } public int methodB() { //在這里可以進行前置增強,實現要處理的邏輯 return this.component.methodB() + 10; //在這里可以進行后置增強,實現要處理的邏輯 } }
調用示例:
public class DecoratorSample { public static void main(String[] args) { //創建一個被裝飾者 Component cc = new ConcreteComponent(); //創建一個派生的裝飾者,同時把被裝飾者傳入裝飾者里面,即說的裝飾者包含被裝飾者 cc = new DecoratorA(cc); //方法調用 System.out.println(cc.methodA()); System.out.println(cc.methodB()); } }
輸出結果:
concrete-object + A
110
4. 代理模式
4.1 定義
代理模式的定義:為其他對象提供一種代理以控制對這個對象的訪問。
在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
作用:不改變原類的代碼,而增強原類對象的功能,可選擇前置、后置、環繞、異常處理增強
代理模式的類圖:
類圖與裝飾者模式一樣,那么代理模式和裝飾者模式有什么區別呢?
代理模式意在在代理中控制使用者對目標對象的訪問,以及進行功能增強。裝飾者模式意在對功能的疊加,比如對多種促銷活動的疊加
4.3 代理模式的實現方式
代理模式有兩種實現方式:
靜態代理:由程序員創建或由特定工具自動生成代理類源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
動態代理:代理類在程序運行時,運用反射機制動態創建而成。
靜態代理事先知道要代理的是什么,而動態代理不知道要代理什么東西,只有在運行時才知道。
4.3.1 靜態代理:
有一個土豪要找蒼老師約會,他不能直接和蒼老師約,需要經過一個中間代理Tony
需要被代理控制增強的行為定義成接口或者超類
public interface Girl { boolean dating(float length); }
代理和被代理的目標對象都要實現接口Girl
代理:
package com.study.design.mode.samples.proxy; /** * * @Description: 代理類實現Girl * @author leeSamll * @date 2018年11月24日 * */ public class Tony implements Girl { //代理類持有被代理的目標對象TeacherCang(目標對象實現的超類或者接口) private Girl girl; public Girl getGirl() { return girl; } public void setGirl(Girl girl) { this.girl = girl; } //代理:控制、增強被代理對象的行為 public boolean dating(float length) { // 前置增強 doSomethingBefore(); boolean res = this.girl.dating(length); // 后置增強 doSomethingAfter(); return res; } private void doSomethingBefore() { System.out.println("老板,這個我試過了,很不錯,推薦給你!"); } private void doSomethingAfter() { System.out.println("老板,你覺得怎樣,歡迎下次再約!"); } }
被代理的目標對象
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目標對象實現Girl * @author leeSamll * @date 2018年11月24日 * */ public class TeacherCang implements Girl { public boolean dating(float length) { if (length >= 1.7F) { System.out.println("身高可以,可以約!"); return true; } System.out.println("身高不可以,不可約!"); return false; } }
土豪使用者
package com.study.design.mode.samples.proxy; /** * * @Description: 使用者 * @author leeSamll * @date 2018年11月24日 * */ public class TuHao { private float length; public TuHao(float length) { super(); this.length = length; } public float getLength() { return length; } public void setLength(float length) { this.length = length; } //約會 public void dating(Girl g) { g.dating(length); } }
調用示例:
package com.study.design.mode.samples.proxy; /** * * @Description: 調用示例 * @author leeSamll * @date 2018年11月24日 * */ public class PlayGame { public static void main(String[] args) { //創建土豪(使用者)、蒼老師(目標對象)、tony(代理)三個對象 TuHao th = new TuHao(1.7F); Girl tc = new TeacherCang(); Tony tony = new Tony(); //tony對蒼老師進行代理 tony.setGirl(tc); //土豪和tony約 th.dating(tony); }
輸出結果:
老板,這個我試過了,很不錯,推薦給你!
身高可以,可以約!
老板,你覺得怎樣,歡迎下次再約!
靜態代理缺點:
擴展能力差
橫向擴展:代理更多的類
縱向擴展:增強更多的方法
可維護性差
由於靜態代理的擴展能力差、可維護性差,這就需要使用動態代理了!!!
4.3.2 動態代理
在運行時,動態為不同類的對象創建代理,增強功能。靈活擴展,易維護!
動態代理的實現方式:
JDK動態代理:只可對接口創建代理
CGLIB動態代理:可對接口、類創建代理
(1) JDK動態代理
在運行時,對接口創建代理對象
生成代理類$Proxy0的方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
參數說明:
ClassLoader loader:類加載器
Class<?>[] interfaces:需要被代理的目標對象實現的接口,可以傳入多個
InvocationHandler h:功能增強的接口
功能增強的接口:
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
參數說明:
Object proxy:被代理的目標對象(接口)
Method method:要調用的目標對象的方法
Object[] args:要調用的目標對象的方法的參數
eg1:JDK動態代理-代碼示例
被代理控制增強的行為生成接口:
Girl
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理控制增強的行為生成接口Girl * @author leeSamll * @date 2018年11月24日 * */ public interface Girl { boolean dating(float length); }
Boy
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理控制增強的行為生成接口Boy * @author leeSamll * @date 2018年11月24日 * */ public interface Boy { boolean dating(char cup); void show(); }
被代理的目標對象
TeacherCang
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目標對象TeacherCang實現Girl * @author leeSamll * @date 2018年11月24日 * */ public class TeacherCang implements Girl { public boolean dating(float length) { if (length >= 1.7F) { System.out.println("身高可以,可以約!"); return true; } System.out.println("身高不可以,不可約!"); return false; } }
TeacherChen
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目標對象TeacherChen實現Boy * @author leeSamll * @date 2018年11月24日 * */ public class TeacherChen implements Boy { public boolean dating(char cup) { if (cup == 'E') { System.out.println("這個女老板品德正好,可以約!"); return true; } System.out.println("這個女老板品德不行,不可以約!"); return false; } public void show() { System.out.println("開始進入拍攝模式。。。。。。。。"); } }
JDK動態代理
package com.study.design.mode.samples.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * * @Description: JDK動態代理 * @author leeSamll * @date 2018年11月24日 * */ public class TonyCompany { //動態生成代理對象 傳入target的是被代理的類 public static Object proxy(Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvationHandler(target)); } //特定的功能增強實現 private static class MyInvationHandler implements InvocationHandler { //被被代理的目標對象 private Object target; public MyInvationHandler(Object target) { super(); this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增強 doSomethingBefore(); // 調用被代理對象的方法 Object res = method.invoke(target, args); // 后置增強 doSomethingAfter(); return res; } private void doSomethingAfter() { System.out.println("老板,你覺得怎樣,歡迎下次再約!"); } private void doSomethingBefore() { System.out.println("老板,這個我試過了,很不錯,推薦給你!"); } } }
調用示例:
package com.study.design.mode.samples.proxy; /** * * @Description: 調用示例 * @author leeSamll * @date 2018年11月24日 * */ public class PlayGame { public static void main(String[] args) { System.out.println("----------------1.靜態代理TeacherCang-----------------------"); //創建土豪(使用者)、蒼老師(目標對象)、tony(代理)三個對象 TuHao th = new TuHao(1.7F); Girl tc = new TeacherCang(); Tony tony = new Tony(); //tony對蒼老師進行代理 tony.setGirl(tc); //土豪和tony約 th.dating(tony); System.out.println("----------------2.JDK動態代理TeacherCang-----------------------"); //生成代理類$Proxy0 Girl tony1 = (Girl) TonyCompany.proxy(tc); //土豪直接和代理tony約 th.dating(tony1); System.out.println("----------------3.JDK動態代理TeacherChen,橫向縱向擴展:代理更多的類和方法-----------------------"); //代理另外一個目標對象TeacherChen Boy tcc = new TeacherChen(); //生成代理類$Proxy0 Boy tony2 = (Boy) TonyCompany.proxy(tcc); //tony2約TeacherChen 縱向擴展:增強更多的方法 System.out.println("----------------3.1 JDK動態代理TeacherChen,調用TeacherChen的dating方法-----------------------"); tony2.dating('E'); System.out.println("----------------3.2 JDK動態代理TeacherChen,調用TeacherChen的show方法-----------------------"); tony2.show(); } }
輸出結果:
----------------1.靜態代理TeacherCang----------------------- 老板,這個我試過了,很不錯,推薦給你! 身高可以,可以約! 老板,你覺得怎樣,歡迎下次再約! ----------------2.JDK動態代理TeacherCang----------------------- 老板,這個我試過了,很不錯,推薦給你! 身高可以,可以約! 老板,你覺得怎樣,歡迎下次再約! ----------------3.JDK動態代理TeacherChen,橫向縱向擴展:代理更多的類和方法----------------------- ----------------3.1 JDK動態代理TeacherChen,調用TeacherChen的dating方法----------------------- 老板,這個我試過了,很不錯,推薦給你! 這個女老板品德正好,可以約! 老板,你覺得怎樣,歡迎下次再約! ----------------3.2 JDK動態代理TeacherChen,調用TeacherChen的show方法----------------------- 老板,這個我試過了,很不錯,推薦給你! 開始進入拍攝模式。。。。。。。。 老板,你覺得怎樣,歡迎下次再約!
(2) cglib動態代理
cglib是什么?
cglib( Byte Code Generation Library),一個高層次的java字節碼生成和轉換的api庫.
ASM:一個低層次的字節碼操作庫
它的主要用途
在運行期為類、接口生成動態代理對象。 以達到不改動原類代碼而實現功能增強的目的
常在哪里用它?
常在 AOP、test、orm框架中用來生成動態代理對象、攔截屬性訪問
如何使用它?
1)引入它的jar
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.6</version> </dependency>
2)學習它的API
https://github.com/cglib/cglib/wiki
cglib動態代理-類圖和API
說明:
實現思想和前面的JDK動態代理一樣,只是使用了不同的API。
代理類由Enhancer生成,代理類實現被代理的類或者接口,特定的功能增強的實現MyMethodInterceptor實現MethodInterceptor接口,特定的功能增強實現MyMethodInterceptor里面持有被代理的類或者接口target
eg2:cglib動態代理-代碼示例
被代理對象的接口:
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理控制增強的行為生成接口Girl * @author leeSamll * @date 2018年11月24日 * */ public interface Girl { boolean dating(float length); }
被代理對象:
package com.study.design.mode.samples.proxy; /** * * @Description: 被代理的目標對象TeacherCang實現Girl * @author leeSamll * @date 2018年11月24日 * */ public class TeacherCang implements Girl { public boolean dating(float length) { if (length >= 1.7F) { System.out.println("身高可以,可以約!"); return true; } System.out.println("身高不可以,不可約!"); return false; } }
cglib動態代理主類:
package com.study.design.mode.samples.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * * @Description: cglib動態代理 * @author leeSamll * @date 2018年11月24日 * */ public class CglibDemo { // 特定的功能增強的實現 static class MyMethodInterceptor implements MethodInterceptor { //特定的功能增強實現MyMethodInterceptor里面持有被代理的類或者接口target private Object target; public MyMethodInterceptor(Object target) { this.target = target; } //在intercept方法進行調用被代理類或者接口的方法之前進行攔截實現前置、后置、環繞、異常處理等功能的增強 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("**************** " + method.getName()); // 前置增強 doSomethingBefore(); // 返回值 Object res = null; // 這里可以調用父類的該方法,當是生成接口的代理時不可調用。 // Object res = methodProxy.invokeSuper(proxy, args); // 通過method來調用被代理對象的方法 if (this.target != null) { res = method.invoke(target, args); } // 后置增強 doSomethingAfter(); return res; } private void doSomethingBefore() { System.out.println("老板你好,這個我試過了,很不錯,推薦給你!"); } private void doSomethingAfter() { System.out.println("老板你覺得怎樣? 歡迎下次....."); } }; public static void main(String[] args) { //創建Enhancer對象用來生成代理類 Enhancer e = new Enhancer(); //創建需要被代理的類TeacherCang TeacherCang tc = new TeacherCang(); // 設置增強回調 e.setCallback(new MyMethodInterceptor(tc)); //對接口生成代理對象 System.out.println("--------------------cglib動態代理:對接口Girl進行代理----------------------"); //設置要代理的接口 e.setInterfaces(new Class[] { Girl.class }); //生成代理的接口的動態代理對象 Girl g = (Girl) e.create(); //調用被代理的接口的dating方法 g.dating(1.8f); // 對類生成代理對象 System.out.println("--------------------cglib動態代理:對類TeacherCang進行代理----------------------"); //設置要代理的類 e.setSuperclass(TeacherCang.class); //把前面的設置的接口Girl置為空 e.setInterfaces(null); //當有多個callback時,需要通過callbackFilter來指定被代理方法使用第幾個callback /* e.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { return 0; } });*/ //生成代理的類TeacherCang的動態代理對象 TeacherCang proxy = (TeacherCang) e.create(); //調用代理的類TeacherCang的dating方法 proxy.dating(1.8f); } }
輸出結果:
--------------------cglib動態代理:對接口Girl進行代理---------------------- **************** dating 老板你好,這個我試過了,很不錯,推薦給你! 身高可以,可以約! 老板你覺得怎樣? 歡迎下次..... --------------------cglib動態代理:對類TeacherCang進行代理---------------------- **************** dating 老板你好,這個我試過了,很不錯,推薦給你! 身高可以,可以約! 老板你覺得怎樣? 歡迎下次.....
5.責任鏈模式
5.1 應用場景
http web請求處理,請求過來后將經過轉碼、解析、參數封裝、鑒權......一系列的處理(責任),而且要經過多少處理是可以靈活調整的。
將所有的處理都寫在一個類中可否?
不行
分成多個類如何靈活組合在一起?
責任鏈:所有的處理者都加入到這個鏈式,一個處理完后,轉給下一個。
責任鏈模式具體實現步驟:
1)抽象出責任接口,具體責任邏輯實現責任接口。
2)根據處理過程需要,將具體責任實現邏輯組合成鏈
3)使用者使用鏈
典型代表:Filter(過濾器)、Intercept(攔截器)
責任鏈模式類圖:
和裝飾者模式的區別在哪里?
裝飾者模式意在功能的疊加,責任鏈模式意在鏈式的處理
eg:責任鏈模式代碼示例
抽象出責任接口:
/** * * @Description: 責任接口 * @author leeSamll * @date 2018年11月25日 * */ public interface Responsibility { void process(Request request, ResponsibilityChain chain); }
具體的責任邏輯實現責任接口:
ResponsibilityA
package com.study.design.mode.samples.responsibility; /** * * @Description: 具體的責任邏輯實現 * @author leeSamll * @date 2018年11月25日 * */ public class ResponsibilityA implements Responsibility { @Override public void process(Request request, ResponsibilityChain chain) { //前置增強 System.out.println("Before Responsibility-A done something..."); //ResponsibilityA處理完以后調用ResponsibilityChain的process方法交給下一個責任邏輯處理 chain.process(request); //后置增強 } }
ResponsibilityB
package com.study.design.mode.samples.responsibility; /** * * @Description: 具體的責任邏輯實現 * @author leeSamll * @date 2018年11月25日 * */ public class ResponsibilityB implements Responsibility { @Override public void process(Request request, ResponsibilityChain chain) { //前置增強 System.out.println("Before Responsibility-B done something..."); //ResponsibilityB處理完以后調用ResponsibilityChain的process方法交給下一個責任邏輯處理 chain.process(request); //后置增強 } }
責任鏈:
ResponsibilityChain
package com.study.design.mode.samples.responsibility; import java.util.ArrayList; import java.util.List; /** * * @Description: 責任鏈,所有的責任加到責任鏈里面進行處理 * @author leeSamll * @date 2018年11月25日 * */ public class ResponsibilityChain { //存放具體的責任邏輯 private List<Responsibility> responsibilitys; private int index = 0; public ResponsibilityChain() { this.responsibilitys = new ArrayList<>(); } //順序調用加入的責任邏輯,一個處理完以后交給下一個繼續處理,下一個處理完以后會通過this回調process看是否有下一個繼續處理 public void process(Request request) { if (this.index < this.responsibilitys.size()) { this.responsibilitys.get(index++).process(request, this); } } //加入具體的責任邏輯 public void register(Responsibility res) { this.responsibilitys.add(res); } }
請求接口:
package com.study.design.mode.samples.responsibility; /** * * @Description: 請求接口 * @author leeSamll * @date 2018年11月25日 * */ public interface Request { }
調用者調用示例
package com.study.design.mode.samples.responsibility; /** * * @Description: 調用者調用示例 * @author leeSamll * @date 2018年11月25日 * */ public class PlayGame { public static void main(String[] args) { //創建一個責任鏈 ResponsibilityChain chain = new ResponsibilityChain(); //往責任鏈里面加入具體的責任邏輯 chain.register(new ResponsibilityA()); chain.register(new ResponsibilityB()); //開始處理 chain.process(new Request() { }); } }
輸出結果:
Before Responsibility-A done something...
Before Responsibility-B done something...
6. 適配器模式
6.1 應用場景
使用者依賴的接口與提供者的接口不匹配時,就加一層適配,而不改兩端的代碼。
適配器模式類圖:
說明:
使用者使用Target接口,但是提供者Provider又沒有實現Target接口,這個時候就需要加一層適配Adaper,Adaper里面持有Provider,在Adapter的methodA()方法里面調用Provider的methodB方法
和代理、裝飾的區別在哪里?
適配器模式不進行功能增強
7. 外觀(門面)模式
7.1 應用場景
使用方要完成一個功能,需要調用提供方的多個接口、方法,調用過程復雜時,我們可以再提供一個高層接口(新的外觀),將復雜的調用過程向使用方隱藏。
外觀(門面)模式類圖:
這里使用了設計原則:最少知識原則(迪米特原則)
8. 觀察者模式
8.1 示例:微信公眾號,關注就可以收到推送的消息,取消關注,就不會再收到。
觀察者模式類圖:
說明:
主題Subject面向觀察者接口Observer編程,主題里面可以添加、刪除和通知觀察者Observer;
注意每個觀察者都有一個回調方法update,如果有變化就會在主題的notifyObservers()方法里面調用update方法,把最新的變化給到觀察者
變化之處:觀察者會變,觀察者的數量會變。
不變:主題的代碼要不受觀察者變化的影響。
觀察者模式定義:
定義了對象之間一對多的依賴關系,當一端對象改變狀態時,它的所有依賴者都會收到通知並自動更新(被調用更新方法)。也稱為:監聽模式、發布訂閱模式。提供一種對象之間松耦合的設計方式。
設計原則:為了交互對象之間的松耦合設計而努力!
8.2 Java中為我們提供了觀察者模式的通用實現
Java.util. Observable 可被觀察的(主題),具體主題擴展它。
java.util.Observer 觀察者接口,具體觀察者實現該接口。
主題Observable:
觀察者接口Observer
使用代碼示例:
package com.study.design.mode.samples; /** * * @Description: java中提供的觀察者設計模式 * @author leeSamll * @date 2018年11月25日 * */ import java.util.Observable; import java.util.Observer; public class ObserverSample { public static void main(String[] args) { //創建主題 Observable subject1 = new Observable() { //通知觀察者變化的數據data public synchronized void notifyObservers(Object data) { //設置 java.util.Observable.changed = true表示發生了改變 setChanged(); //調用父類的notifyObservers方法通知觀察者發生變化 //調用鏈java.util.Observable.notifyObservers(Object)->java.util.Observer.update(Observable, Object) super.notifyObservers(data); } }; //添加觀察者 subject1.addObserver(new Observer() { //主題回調觀察者的update方法通知改變 @Override public void update(Observable o, Object arg) { System.out.println("觀察者1收到通知被更新了..." + arg); } }); //添加觀察者 subject1.addObserver(new Observer() { //主題回調觀察者的update方法通知改變 @Override public void update(Observable o, Object arg) { System.out.println("觀察者2收到通知被更新了..." + arg); } }); //通知改變 subject1.notifyObservers("change1"); subject1.notifyObservers("change2"); } }
輸出結果:
觀察者2收到通知被更新了...change1
觀察者1收到通知被更新了...change1
觀察者2收到通知被更新了...change2
觀察者1收到通知被更新了...change2
9. 命令模式
示例:
請為你的系統設計一個命令行界面,用戶可輸入命令來執行某項功能。
系統的功能會不斷增加,命令也會不斷的增加。
如何將一項一項的加入到這個命令行界面?
如何讓我們的命令程序寫好以后,不因為功能的添加而修改,又可靈活的加入命令、功能。
命令模式類圖:
命令模式的定義:
以命令的方式,解耦調用者與功能的具體實現者,降低系統耦合度,提供了靈活性。
適用場景:Servlet、Controller、線程池
命令模式偽代碼示例:
package com.study.design.mode.samples.command; /** * * @Description: 命令模式 * @author liguangsheng * @date 2018年11月25日 * */ public class Receiver { //存放具體的命令實現 private Map<String,Command> commands; //把具體的命令和對應的實現加入commands public void register(String strCommand,Command command) { commands.put(strCommand,command); } //使用者調用receive方法傳入命令去執行 public void receive(String command) { Command commandObj = commands.get(command); if(null != commandObj) { commandObj.exceute(); return; } System.out.println("不支持此命令" + command); } }
命令模式與策略模式的區別:
命令模式類圖:
策略模式類圖:
區別:
策略模式側重的是一個行為的多個算法的實現,可互換算法。
命令模式側重的是為多個行為提供靈活的執行方式
10. 狀態模式
示例:一個類對外提供了多個行為,同時該類對象有多種狀態,不同的狀態下對外的行為表現不同,我們該如何來設計該類,讓它對狀態可以靈活擴展?
如請為無人自動咖啡售賣機開發一個控制程序。
說明:用戶可以在咖啡機上進行支付、退款、購買、取咖啡等操作
咖啡機狀態轉換圖:
說明:
不同的狀態下這四種操作將有不同的表現。如在沒有支付的狀態下,用戶在咖啡機上點退款、購買、取咖啡,和在已支付的狀態下做這三個操作。
普通實現:
package com.study.design.mode.samples.state; /** * * @Description: 普通的咖啡機: 沒有使用狀態模式的咖啡機 * @author liguangsheng * @date 2018年11月25日 * */ public class CoffeeMachine { final static int NO_PAY = 0; final static int PAY = 1; final static int SOLD = 2; final static int SOLD_OUT = 4; private int state = SOLD_OUT; private int store; public CoffeeMachine(int store) { this.store = store; if (this.store > 0) { this.state = NO_PAY; } } public void pay() { switch (this.state) { case NO_PAY: System.out.println("支付成功,請確定購買咖啡。"); this.state = PAY; break; case PAY: System.out.println("已支付成功,請確定購買咖啡。"); break; case SOLD: System.out.println("待取咖啡中,請稍后購買!"); break; case SOLD_OUT: System.out.println("咖啡已售罄,不可購買!"); } } public void refund() { switch (this.state) { case NO_PAY: System.out.println("你尚未支付,請不要亂按!"); break; case PAY: System.out.println("退款成功!"); this.state = NO_PAY; break; case SOLD: System.out.println("已購買,請取用!"); break; case SOLD_OUT: System.out.println("咖啡已售罄,不可購買!"); } } // 購買 public void buy() { switch (this.state) { case NO_PAY: System.out.println("你尚未支付,請不要亂按!"); break; case PAY: System.out.println("購買成功,請取用!"); this.state = SOLD; break; case SOLD: System.out.println("已購買,請取用!"); break; case SOLD_OUT: System.out.println("咖啡已售罄,不可購買!"); } } // 取coffee public void getCoffee() { switch (this.state) { case NO_PAY: System.out.println("你尚未支付,請不要亂按!"); break; case PAY: System.out.println("已購買,請取用!"); break; case SOLD: System.out.println("請放好杯子,3秒后將出咖啡!"); this.store--; if (this.store == 0) { this.state = SOLD_OUT; } else { this.state = NO_PAY; } break; case SOLD_OUT: System.out.println("咖啡已售罄,不可購買!"); } } }
如何讓狀態可以靈活擴展?
從分析可以看出,變化的是狀態,同時不同的狀態同一個行為的表現不同,這樣的話就可以把變化的狀態抽象生成接口,然后不同的狀態行為實現狀態接口做該狀態下的具體行為。這里可以采用狀態模式
狀態模式類圖:
狀態模式代碼示例:
把變化的狀態抽象生成接口State,里面含有不同狀態下的行為方法
package com.study.design.mode.samples.state; /** * * @Description: 狀態接口 * @author liguangsheng * @date 2018年11月25日 * */ public interface State { void pay(); void refund(); void buy(); void getCoffee(); }
不同的狀態實現狀態接口State
沒有支付狀態
package com.study.design.mode.samples.state; /** * * @Description: 沒有支付狀態 * @author liguangsheng * @date 2018年11月25日 * */ public class NoPayState implements State { private NewCoffeeMachine machine; public NoPayState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("支付成功,請去確定購買咖啡。"); this.machine.state = this.machine.PAY; } @Override public void refund() { System.out.println("你尚未支付,請不要亂按!"); } @Override public void buy() { System.out.println("你尚未支付,請不要亂按!"); } @Override public void getCoffee() { System.out.println("你尚未支付,請不要亂按!"); } }
已支付狀態
package com.study.design.mode.samples.state; /** * * @Description: 已支付狀態 * @author liguangsheng * @date 2018年11月25日 * */ public class PayState implements State { private NewCoffeeMachine machine; public PayState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("您已支付,請去確定購買!"); } @Override public void refund() { System.out.println("退款成功,請收好!"); this.machine.state = this.machine.NO_PAY; } @Override public void buy() { System.out.println("購買成功,請取用"); this.machine.state = this.machine.SOLD; } @Override public void getCoffee() { System.out.println("請先確定購買!"); } }
售出狀態
package com.study.design.mode.samples.state; /** * * @Description: 售出狀態 * @author liguangsheng * @date 2018年11月25日 * */ public class SoldOutState implements State { private NewCoffeeMachine machine; public SoldOutState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("當前狀態為售出,請取咖啡!"); } @Override public void refund() { System.out.println("當前狀態為售出,不能退款!"); } @Override public void buy() { System.out.println("當前狀態為售出,請取咖啡!"); } @Override public void getCoffee() { System.out.println("咖啡已出,請取咖啡!"); } }
售罄狀態
package com.study.design.mode.samples.state; /** * * @Description: 售罄狀態 * @author liguangsheng * @date 2018年11月25日 * */ public class SoldState implements State { private NewCoffeeMachine machine; public SoldState(NewCoffeeMachine machine) { this.machine = machine; } @Override public void pay() { System.out.println("咖啡已賣完,不能支付!"); } @Override public void refund() { System.out.println("不能退款!"); } @Override public void buy() { System.out.println("咖啡已賣完,不能購買!"); } @Override public void getCoffee() { System.out.println("咖啡已賣完!"); } }
使用了狀態模式的咖啡機
package com.study.design.mode.samples.state; /** * * @Description: 使用了狀態模式的咖啡機 * @author liguangsheng * @date 2018年11月25日 * */ public class NewCoffeeMachine { final State NO_PAY, PAY, SOLD, SOLD_OUT; State state; int store; //初始化狀態 public NewCoffeeMachine(int store) { NO_PAY = new NoPayState(this); PAY = new PayState(this); SOLD = new SoldState(this); SOLD_OUT = new SoldOutState(this); this.store = store; if (this.store > 0) { this.state = NO_PAY; } } //支付行為委托給當前狀態實例 public void pay() { this.state.pay(); } //退款行為委托給當前狀態實例 public void refund() { this.state.refund(); } //買咖啡行為委托給當前狀態實例 public void buy() { this.state.buy(); } //取咖啡行為委托給當前狀態實例 public void getCoffee() { this.state.getCoffee(); } }
狀態模式、命令模式、策略模式的區別
狀態模式類圖:
命令模式類圖:
策略模式類圖:
區別:
狀態模式應用於狀態機的情況
策略模式側重的是一個行為的多個算法的實現,可互換算法。
命令模式側重的是為多個行為提供靈活的執行方式
11. 橋接模式
11.1 示例:
請開發一個畫圖程序,可以畫各種顏色不同形狀的圖像,請用面向對象的思想設計圖像
分析:
1)比如有紅、黃、藍三種顏色
2)形狀有方形、圓、三角形
3)圓可以是紅圓、黃圓、藍圓
變化:
會從兩個維度發生變化:形狀、顏色
任其在這兩個維度各自變化,為這兩個維度搭個橋,讓他們可以融合在一起:橋接模式
橋接模式的實現步驟:
1)抽象:分別對各自的維度進行抽象,將共同部分抽取出來
2)組合:將抽象組合在一起(橋接)
橋接模式的定義:將多個維度的變化以抽象的方式組合在一起。使用者面向抽象。個維度間解耦,可自由變化。
12. 單例模式
12.1 飢漢式——可用
飢漢式1——可用
package com.study.design.mode.service; public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } }
飢漢式2——可用
package com.study.design.mode.service; public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return instance; } }
12.2 懶漢式
懶漢式1——不可用
package com.study.design.mode.service; public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
當兩個線程同時進入if里面時就會創建兩個實例,不是單例,線程不安全,所以不可用
懶漢式2——不推薦使用
package com.study.design.mode.service; public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
線程安全,但不推薦使用。缺點是實例化后就不應該再同步了,效率低
懶漢式3——不可用
package com.study.design.mode.service; public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
當兩個線程同時進入if里面時就會產生兩個實例,做不到單例
懶漢式4——雙重檢查——推薦使用
package com.study.design.mode.service; public class Singleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
注意:volatile關鍵字修飾很關鍵,保證可見性,一個線程先創建了,其他線程就就會看到這個改變,不會再創建,如果沒有這個關鍵字還是不能保證單例。
優點:線程安全;延遲加載;效率較高
懶漢式5——靜態內部類方式——推薦使用
package com.study.design.mode.service; public class Singleton { private Singleton() { } private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
優點:避免了線程不安全,延遲加載,效率高
原理:類的靜態屬性只會在第一次加載類的時候初始化。在這里,JVM的加載機制幫助我們保證了線程安全性,在類進行初始化時,別的線程是無法進入的
懶漢式6——用枚舉——推薦使用
package com.study.design.mode.service; public enum Singleton { INSTANCE; public void whateverMethod() { } }
13. 模板方法設計模式
示例:
當我們設計一個類時,我們能明確它對外提供的某個方法的內部執行步驟,但一些步驟,不同的子類有不同的行為時,我們該如何來設計該類?
可以用模板方法設計模式
優點:
1)封裝不變的部分,擴展可變的部分
2)提取公共代碼,便於維護。
3)行為由父控制,子類實現。
適用場景:
1)有多個子類共有的方法,且邏輯相同
2)重要的、復雜的方法,可以考慮作為模板方法
模板方法設計模式代碼示例:
package com.study.design.mode.service; public abstract class Game { protected abstract void initialize(); protected abstract void startPlay(); protected abstract void endPlay(); // 模板方法 public final void play() { // 初始化游戲 initialize(); // 開始游戲 startPlay(); // 結束游戲 endPlay(); } }
四、總結
設計模式總結
創建型模式
這些設計模式提供了一種在創建對象的同時隱藏創建邏輯的方式,而不是使用新的運算符直接實例化對象。這使得程序在判斷針對某個給定實例需要創建哪些對象時更加靈活。
- 工廠模式(Factory Pattern)
- 抽象工廠模式(Abstract Factory Pattern)
- 單例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
結構型模式
這些設計模式關注類和對象的組合。繼承的概念被用來組合接口和定義組合對象獲得新功能的方式。
- 適配器模式(Adapter Pattern)
- 橋接模式(Bridge Pattern)
- 過濾器模式(Filter、Criteria Pattern)
- 組合模式(Composite Pattern)
- 裝飾器模式(Decorator Pattern)
- 外觀模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
行為型模式
這些設計模式特別關注對象之間的通信。
- 責任鏈模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解釋器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 備忘錄模式(Memento Pattern)
- 觀察者模式(Observer Pattern)
- 狀態模式(State Pattern)
- 空對象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 訪問者模式(Visitor Pattern)
創建型模式:主要用來創建實例的,創建實例時不要跟具體類捆綁,而是通過工廠等來創建,從而使用者就和具體類解耦了
結構型模式:主要說的是如何來組合利用,把多個實例組合在一起
行為型模式:主要是多種行為、多種功能的的變化,怎么把多個行為功能組合在一起
設計原則總結
1. 變化隔離原則:找出變化,分開變化和不變的
隔離,封裝變化的部分,讓其他部分不受它的影響。
2. 面向接口編程 ——隔離變化的方式
使用者使用接口,提供者實現接口。“接口”可以是超類!
3. 依賴倒置原則(里氏替換原則)——隔離變化的方式
依賴抽象,不要依賴具體類!
4. 開閉原則:對修改閉合,對擴展開放——隔離變化的方式
可以繼承一個類或者接口擴展功能,但是不能修改類或者接口的原有功能
5. 最少知道原則,又稱迪米特法則
6. 多用組合,少用繼承——靈活變化的方式
“有一個”可能比“是一個”更好。
7. 單一職責原則——方法設計的原則
每個方法只負責一個功能,不要把很多功能寫在一個方法里面
最后,如果都忘記了,請一定要記住這三條
說明:如果前面的設計思想和設計原則都忘記了,就要找出變化,區分出不變的和變化的,把變化的部分獨立出接口,或者使用組合
示例代碼獲取地址:
https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/design-mode-study
參考文章: