三、設(shè)計模式
3.1 設(shè)計模式簡介
- 軟件設(shè)計中的
三十六計
- 是人們在長期的軟件開發(fā)中的經(jīng)驗總結(jié)
- 是對某些特定問題的經(jīng)過實踐檢驗的特定解決方法
- 被廣泛運用在 Java 框架技術(shù)中
3.1.1 設(shè)計模式的優(yōu)點
- 設(shè)計模式是可復(fù)用的面向?qū)ο筌浖幕A(chǔ)
- 可以更加簡單方便地復(fù)用成功的設(shè)計和體系結(jié)構(gòu)
- 幫助開發(fā)者做出有利于系統(tǒng)復(fù)用的選擇,避免損害系統(tǒng)復(fù)用性的設(shè)計
- 使其他開發(fā)者更加容易理解其設(shè)計思路,便于團隊交流
3.1.2 設(shè)計模式分類
GoF(Gang of Four,四人組)設(shè)計模式分為23種
范圍/目的 | 創(chuàng)建型模式 | 結(jié)構(gòu)型模式 | 行為型模式 |
---|---|---|---|
類模式 | 工廠方法 | (類)適配器 | 模板方法解釋器 |
對象模式 | 單例 原型 抽象工廠 建造者 |
代理 (對象)適配器 橋接 裝飾 外觀 享元 組合 |
策略 命令 職責(zé)鏈 狀態(tài) 觀察者 中介者 迭代器 訪問者 備忘錄 |
3.1.3 面向?qū)ο笤O(shè)計原則
單一職責(zé)原則
- 一個類應(yīng)該有且僅有一個引起它變化的原因
- 一個類應(yīng)該只負責(zé)一個職責(zé)
開閉原則
- 對擴展開放,對修改關(guān)閉
里氏替換原則
- 引用基類的地方必須能透明地使用其子類的對象
- 可以用來判斷繼承關(guān)系是否合理
依賴倒置原則
- 依賴于抽象而不依賴于具體實現(xiàn),針對接口編程
接口隔離原則
- 盡量將龐大臃腫的接口拆分成更小更具體的接口
- 接口中只包含客戶感興趣的方法
迪米特法則
- 又稱最少知道原則
- 一個軟件實體應(yīng)當盡可能少地與其他實體發(fā)生相互作用
合成復(fù)用原則
- 盡量使用組合/聚合的方式而不是繼承關(guān)系達到軟件復(fù)用的目的
- 是 has-a 關(guān)系
3.2 簡單工廠模式
如何解決類似“Service與某個具體Dao實現(xiàn)”耦合的問題?
將創(chuàng)建工作轉(zhuǎn)移出來避免在Service中創(chuàng)建具體的Dao實現(xiàn)類,產(chǎn)生耦合
簡單工廠模式,又叫做靜態(tài)工廠方法模式,不屬于 GoF 的23種設(shè)計模式之一,可以理解為工廠模式的一個特殊實現(xiàn)
3.2.1 簡單工廠模式+依賴倒置原則
依據(jù)依賴倒置原則,使用setter方法傳遞依賴關(guān)系,減少Service對工廠類的依賴,降低耦合
public class NewsServiceImpl implements NewsService {
private NewsDao dao;
public void setDao(NewsDao dao) {
this.dao = dao;
}
… …
}
3.2.2 簡單工廠+參數(shù)
簡單工廠模式可以根據(jù)參數(shù)的不同返回不同類的實例,被創(chuàng)建的實例通常都具有共同的父類
// 創(chuàng)建NewsDao實例的工廠方法
public static NewsDao getInstance(String key) {
switch (key) {
case "mysql":
return new NewsDaoMySqlImpl();
case "oracle":
return new NewsDaoOracleImpl();
case "redis":
return new NewsDaoRedisImpl();
default:
throw new RuntimeException("無效的數(shù)據(jù)庫類型:" + key + " ,DAO獲取失敗");
}
}
要創(chuàng)建的產(chǎn)品不多且邏輯不復(fù)雜的情況,可以考慮簡單工廠模式
簡單工廠模式包含如下角色
- 工廠(Factory)
- 抽象產(chǎn)品(Product)
- 具體產(chǎn)品(Concrete Product)
增加新的產(chǎn)品需要修改,工廠方法的判斷邏輯,不符合開閉原則
3.3 工廠方法模式
3.3.1 實現(xiàn)方式
對簡單工廠模式的進一步抽象,工廠方法模式的主要角色如下
- 抽象產(chǎn)品(Product)
- 抽象工廠(Abstract Factory)
- 具體產(chǎn)品(Concrete Product)
- 具體工廠(Concrete Factory)
3.3.2 代碼案例
創(chuàng)建抽象工廠接口
public interface AbstractFactory {
public NewsDao getInstance();
}
為不同NewsDao實現(xiàn)創(chuàng)建相對應(yīng)的具體工廠
// 以生產(chǎn)NewsDaoMySqlImpl實例的工廠為例
public class MySqlDaoFactory implements AbstractFactory {
@Override
public NewsDao getInstance() {
return new NewsDaoMySqlImpl();
}
}
在測試方法中通過特定工廠生產(chǎn)相關(guān)的NewsDao實例
AbstractFactory factory = new MySqlDaoFactory();
// 改變具體工廠可創(chuàng)建不同產(chǎn)品
NewsDao dao = factory.getInstance();
3.3.3 優(yōu)缺點
優(yōu)點
- 只需要知道具體工廠就可得到所要的產(chǎn)品,無須知道產(chǎn)品的具體創(chuàng)建過程
- 基于多態(tài),便于對復(fù)雜邏輯進行封裝管理
- 增加新的產(chǎn)品時無須對原工廠進行任何修改,滿足開閉原則
缺點
- 每增加一個產(chǎn)品就要增加一個具體產(chǎn)品類和一個對應(yīng)的具體工廠類,這增加了系統(tǒng)的復(fù)雜度
3.4 代理設(shè)計模式
單一職責(zé)原則的體現(xiàn),包含如下角色
- 抽象主題(Subject)
- 真實主題(Real Subject)
- 代理(Proxy)
實現(xiàn)方式總體上分為靜態(tài)代理和動態(tài)代理
- 靜態(tài)代理由開發(fā)者針對抽象主題編寫相關(guān)的代理類實現(xiàn),編譯之后生成代理類的class文件
- 動態(tài)代理是在運行時動態(tài)生成的,在運行時動態(tài)生成代理類字節(jié)碼
3.4.1 基于接口的靜態(tài)代理實現(xiàn)
// 抽象主題接口 - 圖片
public interface Image {
void display();
}
// 真實主題類 - 真實圖片
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image from disk: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 代理類 - 圖片代理
public class ImageProxy implements Image {
private RealImage realImage;
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 調(diào)用代碼
public class Client {
public static void main(String[] args) {
// 創(chuàng)建代理對象并顯示圖片
Image image = new ImageProxy("example.jpg");
image.display();
}
}
3.4.2 代理模式優(yōu)點分析
- 代理模式將客戶與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度
- 代理對象可以對目標對象的功能進行擴展,目標對象和擴展功能職責(zé)清晰且不會產(chǎn)生耦合
3.4.3 動態(tài)代理
靜態(tài)代理需要手工編寫代理類,存在以下弊端
- 目標對象API發(fā)生變化,代理類也必須進行修改,增加工作量且不符合開閉原則
- 通過繼承得到的代理類只能對一種類型進行代理,組件較多時,代理類的開發(fā)工作量巨大
- 動態(tài)代理提供了運行時動態(tài)擴展對象行為的能力
- 能夠依據(jù)給定的業(yè)務(wù)規(guī)則,在運行時動態(tài)生成代理類
3.4.4 JDK 動態(tài)代理
從JDK 1.3版本開始引入
是面向接口的代理實現(xiàn)
- 要求被代理的目標對象必須通過抽象主題接口進行定義
核心API
- java.lang.reflect.InvocationHandler接口
- 代理方法的調(diào)用處理程序,負責(zé)為代理方法提供業(yè)務(wù)邏輯
- 包含方法:Object invoke(Object proxy, Method method, Object[] args)
- java.lang.reflect.Proxy類
- 負責(zé)動態(tài)創(chuàng)建代理類及其實例
- 主要方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
3.4.5 CGLIB 動態(tài)代理
如果被代理的目標對象不是通過接口進行定義的,JDK 動態(tài)代理將無法實施
- CGLIB(Code Generation Library)是一個功能強大,高性能的代碼生成庫
- 可以為沒有實現(xiàn)接口的類提供代理,原理是為需要代理的類動態(tài)生成一個子類作為其代理類
需要使用繼承和重寫機制,CGLIB動態(tài)代理對于final類或final方法無能為力
從cglib https://github.com/cglib/cglib/releases下載所需的 jar 文件文章來源:http://www.zghlxwxcb.cn/news/detail-695764.html
- cglib-nodep-x.x.x.jar
主要 API文章來源地址http://www.zghlxwxcb.cn/news/detail-695764.html
- net.sf.cglib.proxy.MethodInterceptor 接口
- 負責(zé)攔截父類的方法調(diào)用,以便加入代理的業(yè)務(wù)邏輯
- 包含方法
- Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
- net.sf.cglib.proxy.Enhancer 類
- 負責(zé)動態(tài)創(chuàng)建代理類及其實例
- 主要方法
- setSupperclass()
- setCallback()
- set…
- create()
3.4.6 JDK 和 CGLIB 動態(tài)代理的對比
- JDK 動態(tài)代理面向接口代理,只能對基于接口設(shè)計的目標對象進行代理
- CGLIB 動態(tài)代理可以通過繼承方式實現(xiàn),不依賴接口,但是不能代理 final 的類和方法
到了這里,關(guān)于BCSP-玄子Share-Java框基礎(chǔ)_工廠模式/代理模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!