前言:上篇總結了創(chuàng)建型模型续挟,這周來總結下結構型模式详拙,如果說創(chuàng)建型模式是用來處理和優(yōu)化對象的創(chuàng)建和復用的問題的話质蕉,那么結構型模式則是用于通過繼承、合成/聚合等方式處理類與對象传睹,達到能更加適應'業(yè)務需求的變化和擴展'的結果
耳幢。另外,從手段而言欧啤,類與類的結構上合成/聚合是要優(yōu)于類的繼承的方式的睛藻,其可以在運行時改變組合對象的關系,更加靈活(橋接模式)邢隧。
結構型模型包括
1.代理模式
2.橋接模式
3.外觀模式
4.適配器模式
5.組合模式
6.享元模式
7.裝飾模式
代理模式
1.定義:為其他對象提供一種代理以控制對這個對象的訪問修档。
2.優(yōu)缺點:隔離實際調用對象,同名處理方法中可以加入其他細節(jié)而不用修改實際輸出的類府框,缺點是增加代理類吱窝。
3.總結:比較常用的設計模式讥邻,分為靜態(tài)和動態(tài)代理。
靜態(tài)代理:代理類和被代理類實現相同的接口院峡,即:含有相同的方法A,B,C,兴使。。照激。发魄。
代理類持有被代理類的引用,在代理類的方法X中調用被代理類.X()俩垃;
動態(tài)代理(示例):
//1.定義接口
/**
* 作者:wl on 2017/11/27 17:15
* 郵箱:wangl@ixinyongjia.com
*/
//基本需求
public interface BaseNeed {
String rentHouse();
String rentCar();
void learnKnowledge();
}
//2.定義接口實現類
public class Worker implements BaseNeed {
@Override
public String rentHouse() {
Log.d("test","打工者需要租房");
return "打工者需要租房";
}
@Override
public String rentCar() {
Log.d("test","打工者也要成為司機");
return "打工者也要成為司機";
}
@Override
public void learnKnowledge() {
Log.d("test","打工者也要充電");
}
}
//3.定義動態(tài)代理的'請求處理類' MyInvocationHandle
public class MyInvocationHandle<T extends BaseNeed> implements InvocationHandler {
private T target;
public MyInvocationHandle(Class clazz) {
super();
try {
target = (T) clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Log.d("test", "開始調用代理");
if(method.getName().equals("learnKnowledge")){
Log.d("test", "對于學習方法注入擴展");
}
Object test = method.invoke(target, args);
// Log.d("test", "結束調用代理");
return test;
}
public T getProxy() {
//return (T) Proxy.newProxyInstance(BaseNeed.class.getClassLoader(), new Class[]{BaseNeed.class}, this);
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//4.外部調用
MyInvocationHandle myInvocationHandle=new MyInvocationHandle(Worker.class);
BaseNeed proxy= myInvocationHandle.getProxy();
proxy.rentHouse();
proxy.rentCar();
proxy.learnKnowledge();
//日志輸出:
11-28 11:14:32.289 18103-18103/XXX D/test: 打工者需要租房
11-28 11:14:32.289 18103-18103/XXX D/test: 打工者也要成為司機
11-28 11:14:32.290 18103-18103/XXX D/test: 對于學習方法注入擴展
11-28 11:14:32.290 18103-18103/XXX D/test: 打工者也要充電
原理簡單描述:Proxy在運行時生成代理類$Proxy11
public final class $Proxy11 extends Proxy
這個類在靜態(tài)代碼塊中通過反射等技術初始化實現了 接口 中定義的所有方法:如
m3 = Class.forName("dynamic.proxy.BaseNeed").getMethod("rentHouse", new Class[0]);
如:
public final void rentHouse()
{
try
{
// 實際上就是調用
//MyInvocationHandler的
//public Object invoke(Object proxy, Method method, Object[] args)方法
//所以無論外部調用那個方法励幼,都會走MyInvocationHandle中的invoke
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
總結:動態(tài)代理能在運行時動態(tài)的生成代理類,當接口方法很多時口柳,使用靜態(tài)代理的話代碼量很大苹粟,擴展起來也比較繁瑣,而動態(tài)代理在內存中自動生成代理類跃闹,很好的解決了這個問題嵌削。缺點是代理的對象必須實現接口(cglib這好玩意兒解決了這個問題),retrofit就是采用動態(tài)代理來實現的(用動態(tài)代理望艺,將API接口類中的所有請求在invoke中統一轉化成okhttpcall或者observerable)
對比JDK的動態(tài)代理和cglib的動態(tài)代理:
JDK的動態(tài)代理
的原理是通過反射拿到接口中所有的方法苛秕,然后通過二進制流的方式生成代理類class,并且代理類的實現要繼承Proxy類找默,又不能多繼承艇劫,所以必須要有接口。
CGLib
采用了非常底層的字節(jié)碼技術惩激,其原理是通過字節(jié)碼技術為一個類創(chuàng)建子類港准,并在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯咧欣。(cglib部分參考的連接)
JDK動態(tài)代理與CGLib動態(tài)代理均是實現Spring AOP的基礎浅缸。
最后送上一篇風趣幽默的文章:Java帝國-動態(tài)代理
裝飾模式
1.定義:動態(tài)地給一個對象添加一些額外的職責,就增加功能來說魄咕,裝飾模式比生成子類更為靈活衩椒。
2.心得分析:裝飾模式在結構上幾乎和代理一樣,裝飾類跟被裝飾類實現同一個接口哮兰,然后裝飾類持有被裝飾類的引用毛萌,在內部去 增強
被裝飾類的同名方法。
注意這里我用了一個詞 增強
喝滞,這也正是裝飾和代理的最大區(qū)別:
1.裝飾模式的目的是去 增強阁将、擴大、修飾 被裝飾對象的操作右遭,而代理模式的側重點是去控制
對 被代理類 的訪問做盅。
2.一般不會再弄個代理類來控制原先的代理缤削,而裝飾的話經常會存在嵌套的情況,即一層裝飾一層吹榴。
外觀模式
1.定義:為子系統中的一組接口提供一個一致的界面亭敢,此模式定義了一個高層接口,這個接口使得這一子系統更加容易使用图筹。
2.優(yōu)缺點:對客戶程序隱藏子系統細節(jié)帅刀,解耦客戶和子系統,同時使得整個系統封裝的更易于調用远剩。另一方面外觀類本身需要提供不同api接口扣溺,并且外觀類無法遵循開閉原則,需求變化-可能需要直接修改外觀類瓜晤。
3.總結:以前在做'風跑app'的時候地圖定位等模塊完全依賴于高德地圖锥余,高德地圖api就相當于是子系統,我自己弄了
個MapHelper類活鹰,里面就是封裝了用高德api畫線,畫點等方法~~當時都不知道有這個設計模式只估,但確實就是這么用的志群,可見這個 外觀 模式的使用很廣泛,很常見蛔钙!
適配器模式
1.定義:將一個類的接口轉換成客戶希望的另外一個接口锌云。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
2.總結:
對象適配器
:跟靜態(tài)代理很像吁脱,需要適配的類Adaptee,適配器類Adapter桑涎,期望的接口Target內部要實現的方法為A。
使用過程就是 適配器類Adapter實現Target接口中的方法A兼贡,具體的實現方式就是自己持有需要適配的類Adaptee的引用攻冷,然后調用Adaptee的那個不兼容的方法。 這TM跟代理類持有被代理類的引用遍希,然后調用被代理類的方法去實現一模一樣等曼,區(qū)別就是適配器模式要求松一些,Adaptee無需繼承Target凿蒜。
類適配器
:與對象適配器不同禁谦,適配器類Adapter直接繼承需要適配的類Adaptee。相比較于對象適配器
模式而言缺點是會暴露出Adaptee中的方法废封,對使用者并不友好(再次證明:合成/聚合>繼承)
3.優(yōu)缺點:
擴展性不錯州泊,能復用系統現有的輪子。
過多的使用適配器會使系統凌亂漂洋,不易整體把握遥皂。如命名調用的是A力喷,內部確是適配成調用B
所以說這玩意兒不是很必要就不要用,優(yōu)先重構渴肉!
組合模式
1.定義:將對象組合成樹形結構以表示‘部分’-‘整體’的層次結構冗懦。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
2.總結:
透明方式:抽象的接口中包含管理子類的方法仇祭,這樣葉子類和枝節(jié)類都會實現對應的方法披蕉,只不過葉子類沒有下線,所以方法實現都為空乌奇。
安全方式:相反没讲,管理子類的方法放到了枝節(jié)類,葉子類更干凈
這個模式的理解和應用自我感覺還不到位礁苗,不寫了爬凑,暫時放放
享元模式
1.定義:運用共享技術有效地支持大量細粒度的對象。
2.示例:俄羅斯方塊的生產
public abstract class Shape{ //形狀
public void doRotate(int degree); //名為‘旋轉 ’的方法
}
public class Tetris extend Shape{ //俄羅斯方塊
private String shapeType;
public Tertis (String type){ //構造器傳入 形狀的類型 如:7型试伙,I型嘁信,田型,山型疏叨。
shapeType=type;
}
public void doRotate(int degree){
Toast("將 shapeType 形狀的方塊旋轉 degree 度)
}
}
public class TetrisFactory { //俄羅斯方塊制造工廠
private map<String,Tetris > realTetris=new HashMap<>(); //緩存不同類型的俄羅斯方塊實體
public Tetris getTetrisInstance(String type) {
if(realTetris.get(type)==null){
realTetris.put(type,new Tetris(type));
}
return realTetris.get(type);
}
}
//模擬俄羅斯方塊的產生過程:
int[ ] degrees=new int[]{90,180,270};
String[ ] types=new String[]{"7","山","口","田"};
TetrisFactory factory=new TetrisFactory ();
Tetris item=null;
for(int i=0;i<10000;i++){
int degree=degrees[new Random().nextInt(2)];
String type=types[new Random().nextInt(3)];
item=factory.getTetrisInstance();
item.doRotate(degree);
}
代碼簡單明了潘靖,循環(huán)體中產生10000個俄羅斯方塊,并且方塊的形狀和角度隨機蚤蔓,如果不用享元卦溢,則實例化10000次,利用享元模式秀又,實例化僅僅4次单寂,并且將 角度 這一變化的因素抽離出去,作為外部參數傳入吐辙。
這個模式的核心就是將狀態(tài)外部化宣决,通過讀取外部化參數來復用緩存的實例。
橋接模式
1.定義:將抽象部分與它的實現部分分離昏苏,使它們可以獨立的變化疲扎。
(定義不太好理解,實際上核心就是:一個類存在兩個獨立變化的維度捷雕,且這兩個維度都要擴展椒丧,那么將其中一個維度抽象出來,通過合成或者聚合的方式關聯到另一維度)
2.舉例:電腦的品牌和電腦上的USB插口
品牌有 聯想救巷、惠普壶熏、方正等
USB接口有 2.0 3.0 等
如果這兩個變量都通過
繼承
的方式來實現的話:
類的繼承關系如圖,看似很清晰浦译,可是如果以后科技發(fā)展棒假,USB出了4.0 5.0 溯职,同時又有更多的電腦品牌誕生時,通過這種繼承的方式實現的代碼改起來可以說灰常痛苦帽哑,每增加一種USB谜酒,需要為所有品牌去添加一個類,顯然是不科學的妻枕,問題的關鍵就在處理這兩個變化維度
的方式上僻族!即:對 品牌 和接口類型 都是通過 繼承 去實現的
如果我們使用橋接模式,通過聚合或者合成
的方式 將變化維度中的 USB接口隔離屡谐, 實現如圖:
此時將變化的維度USB接口類型隔離開述么,跟電腦形成"合成"的關系,而不再是繼承愕掏,好處在哪里呢度秘?
新增品牌,OK饵撑,和接口沒半毛錢關系剑梳,new 新品牌類(Usb接口)的時候傳入USB接口類型就行。
同樣 新增USB4.0滑潘, 制造新電腦的時候傳入 new USB4.0 這個構造就行垢乙,不用去動 具體電腦品牌類的代碼,完全符合開放閉合原則众羡!
3.總結:考慮類與類之間關系的構建時:聚合和合成
是優(yōu)先于繼承
的侨赡,對于橋接模式蓖租,強調的就是 一個類粱侣,當他的變化是多維度的時候,將其他維度抽離出去蓖宦,讓它們單獨演變齐婴,然后通過聚合或者合成的方式關聯回這個類。