設計模式(三)~結構型模式(1)

代理模式

定義

代理模式:由於某些原因訪問對象不適合或者不能直接引用目標對象,這時候需要給目標對象提供一個代理對象以控制對該對象的訪問,代理對象作爲訪問對象和目標對象之間的中介。

分類
  • 靜態代理

    代理類與真實主題(被代理類)一一對應,且真實主題必須事先存在。

  • 動態代理

    被代理對象與代理類之前沒有耦合關係且無需事先存在。

特點

優點

  • 代理模式在訪問者與目標者之間起到中介作用和保護目標對象的作用。
  • 代理對象可以擴展目標對象的功能。
  • 代理模式將訪問對象與目標對象分離,在一定程度上降低了系統的耦合度。

缺點

  • 代理模式在訪問對象與目標對象之間建立一個代理對象,會造成請求處理速度變慢。
  • 增加系統複雜度。
結構

代理模式結構比較簡單,主要是通過定義一個抽象主題的代理來包含真實主題,從而實現對真實主題的訪問。

代理模式主要角色如下:

  • 抽象主題(Subject)類:通過接口或抽象類聲明真實主題的功能方法(真實主題和代理類都要實現此抽象主題)
  • 真實主題(Real Suject)類:實現了抽象主題中的具體方法,是代理對象所代表的真實對象,是最終要引用的對象。
  • 代理(Proxy)類:實現了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制和擴展真實主題的功能。
    在這裏插入圖片描述
實現

案例:上海房產是一家經營許多房源的公司,提供了許多房源它是真實的主題,提供房源展示方法,而上海無愛無家上海房產公司的代理,通過調用上海房產的顯示方法,顯示代理的產品,當然可以增加一些額外的處理,如:清掃或者加價

/**
 *1、抽象主題,定義了具體主題(被代理類)的功能(具體主題和代理類都要實現)
 */
public interface IHouse {
    //房源展示方法
    void show();
}
/**
 *2、具體主題-上海房產公司,實現抽象主題定義的方法-房源展示
 */
public class SHHouse implements IHouse {
    @Override
    public void show() {
        System.out.println("我是真實主題(被代理類)-上海房產公司,只提供顧客看房子");
    }
}
/**
 *3、代理類-無愛無家,實現了抽象主題定義的方法,內部含有對真實主題的引用,可以對被代理類功能進行擴展
 */
public class House5i5jProxy implements IHouse {
    //真實主題的引用
    SHHouse shHouse;
    public House5i5jProxy() {
        this.shHouse = new SHHouse();
    }

    @Override
    public void show() {
        cleanUPHouse();
        if (shHouse != null) {
            //調用真實主題的方法
            shHouse.show();
        }
        resetHouse();
    }

    //代理類擴展方法-清掃房子
    private void cleanUPHouse() {
        System.out.println("我是代理類-顧客看房子之前,先去打掃房子");
    }

    //代理類的擴展方法-恢復房子
    private void resetHouse() {
        System.out.println("我是代理類-顧客看房子之後,重新整理房子");
    }
}
//測試
public class Test {
    public static void main(String[] args) {
        //通過代理對象訪問目標對象的方法
        House5i5jProxy house5i5jProxy = new House5i5jProxy();
        //代理對象內部對目標對象的方法進行了擴展
        house5i5jProxy.show();
    }
}

//輸出
我是代理類-顧客看房子之前,我先去打掃房子
我是被代理類-上海房產公司,只提供顧客看房子
我是代理類-顧客看房子之後,我重新整理房子
應用場景
  • 遠程代理:這種方式通常是爲了隱藏目標對象存在於不同地址空間的事實,方便客戶端訪問。
  • 虛擬代理:這種方式通常用於要創建的目標對象開銷很大時。
  • 安全代理:這種方式通常用於控制不同種類客戶對真實對象訪問。
  • 智能指引:主要用於調用目標對象時代理附加一些額外的處理功能
  • 延遲加載:餵了提供系統的性能,延遲對目標對象的加載。
擴展(動態代理)

前面介紹的代理模式中,代理類中包含了對真實主題的引用,這種方式有兩個缺陷

  • 真實主題與代理類必須要一一對應,增加真實主題也要增加代理。
  • 設計代理以前真實主題必須事先存在,不太靈活。採用動態代理可以解決上面問題。
/**
 * 抽象主題(定義真實主題(被代理類的功能方法))
 */
public interface ICar {
    void show();
}
/**
 *真實主題(實現抽象主題接口定義的方法)
 */
//真實主題-公共汽車
public class BusCar implements ICar {
    String name;
    public BusCar(String name) {this.name = name;}

    @Override
    public void show() {
        System.out.println("我是被代理類-" + name);
    }
}

//真實主題-出租車
public class TaxiCar implements ICar {
    String name;
    public TaxiCar(String name) {this.name = name;}

    @Override
    public void show() {
        System.out.println("我是被代理類-" + name);
    }
}
/**
 * 動態代理類,不與具體的真實主題(被代理類綁定)
 */
public class DynamicProxy implements InvocationHandler {
    //真實主題(被代理類Object類型,任何實現了接口的類都可以通過此動態代理實現代理需求)
    Object object;
    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        System.out.println("我是動態代理類-在被代理方法執行之前插入代碼邏輯");
        Object result = method.invoke(object, args);
        System.out.println("我是動態代理類-在被代理方法執行之後插入代碼邏輯");
        return result;
    }
}
//動態代理測試
public class Test {
    public static void main(String[] args) {
        //被代理對象的類加載器
        ClassLoader classLoader = ICar.class.getClassLoader();
        //被代理對象實現的接口
        Class mInterface = ICar.class;
        //InvocationHandler對象
        InvocationHandler dynamicHandler = new DynamicProxy(new BusCar("公共汽車"));
        //返回被代理對象
        ICar busProxy = (ICar) Proxy.newProxyInstance(classLoader, 
                        new Class[]{mInterface}, dynamicHandler);
        busProxy.show();

        ICar taxiProxy = (ICar) Proxy.newProxyInstance(ICar.class.getClassLoader(),
                         new Class[]{ICar.class}, new DynamicProxy(new TaxiCar("出租車")));
        taxiProxy.show();
    }
}

//輸出
我是動態代理類-在被代理方法執行之前插入代碼邏輯
我是被代理類-公共汽車
我是動態代理類-在被代理方法執行之後插入代碼邏輯

我是動態代理類-在被代理方法執行之前插入代碼邏輯
我是被代理類-出租車
我是動態代理類-在被代理方法執行之後插入代碼邏輯

總結
  • 動態代理在靜態代理基礎上擴展,不在侷限於真實主題(被代理對象)與代理類一一對應關係,

    靜態代理中代理類內部包含對真實主題的引用,因此需要一一對應。

    動態代理中被代理的是Object類型,符合一定規則的類都可以被代理。

  • 動態代理解決了靜態代理真實主題(被代理類)必須事先存在的缺陷

    靜態代理中包含了對真實主題的引用,因此如果想要代理某個類,真實主題必須事先存在

適配器模式

定義

將一個類的接口轉換成客戶希望的另一個接口,適配器模式使得原本由於接口不兼容而不能一起動作的那些類可以一起工作。

比如:老虎和飛禽,現在目標是飛虎,在不增加實體需求下,增加一個適配器,在裏面包容一個虎對象,實現飛的接口。

分類
  • 類結構模式

    通過類之間的繼承實現,耦合度較高,所以用的較少

  • 對象結構模式

    通過依賴方式實現(推薦)

類適配器模式的結構
在這裏插入圖片描述
對象適配器模式的結構
在這裏插入圖片描述

特點

優點

  • 提高類的複用性

    系統需要使用現有的類,而此類的接口不符合系統的需要。那麼通過適配器模式就可以讓這些功能得到更好的複用。

  • 透明、簡單

    客戶端可以調用同一接口,因而對客戶端來說是透明的。這樣做更簡單&更直接。

  • 更好的擴展型

    在實現適配器功能的時候,可以調用自己開發的功能,從而自然地擴展系統功能。

  • 解耦性

    將目標類和適配者類解耦,通過引入一個適配器類重用現有的適配者類,而無需修改原來的代碼。

  • 符合開閉原則

    同一個適配器可以把適配者類和它的子類都適配到目標接口;可以爲不同的目標接口實現不同的適配器,而不需要修改適配者類。

缺點

  • 過多使用適配器,會讓系統非常凌亂,不易整體進行把控。
結構

適配器模式包含一下角色

  • 目標(Target)接口:當前系統所期待的接口,可以是抽象類或者接口。
  • 適配者(Adaptee)類:指被訪問和適配的已存在組件中的類。
  • 適配器(Adapter)類:是一個轉換器,通過繼承或者依賴適配者對象,把適配者接口轉換成目標接口,讓客戶按目標接口的格式訪問適配者。
實現

案例

背景:小明買了一臺進口電視

衝突:但是進口電視要求電壓(110V),與國內輸出電壓(220V)不兼容

解決方案:通過設置一個適配器將插頭輸出的220V電壓轉換成110V

  • 類結構模式實現(繼承)

    適配器類**繼承**已有對象,實現目標接口

/**
 *1、創建目標接口(期望得到的插頭)能輸出110V(將220V轉換爲110V)
 */
public interface ITargetPower {
    void convert110V();
}
/**
 *2、創建適配者類-輸出220V電壓(已經存在的類)
 */
public class PowerPort220V {

    //輸出220V電壓
    public void outPut220V() {
       //TODO...
    }
}
/**
 *3、創建電源適配器類,繼承適配者實現目標接口
 * 目標是要實現輸出110的功能convert110V()方法,
 * 但是原來插頭沒有,因此適配器需要補充上這個功能,
 * 實際上convert110V()只是調用了原來插頭的outPut220V()方法,
 * 所以適配器只是將outPut220V()方法作了一層封裝,
 * 封裝成目標接口ITargetPower可以調用的convert110V()方法而已。
 */
public class PowerAdapter extends PowerPort220V implements ITargetPower {
    @Override
    public void convert110V() {
        this.outPut220V();
    }
}
  • 對象結構模式實現(依賴)

    適配器類**依賴**適配者類,實現目標接口

//目標接口1、適配者類2同上
/**
 *3、創建適配器,內部依賴適配者
 */
public class PowerAdapter2 implements ITargetPower {
    //關聯適配者類
    PowerPort220V powerPort220V;
    
    //通過構造函數傳入具體需要適配的適配者類對象。
    public PowerAdapter2(PowerPort220V powerPort220V) {
        this.powerPort220V = powerPort220V;
    }

    @Override
    public void convert110V() {
        //使用委託方式完成特殊功能
        powerPort220V.outPut220V();
    }
}
應用場景
  • 系統需要複用現有類,而該類的接口不符合系統的要求,可以使用適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。
  • 多個組件功能類似,但接口不統一且可能會經常切換,可使用適配器模式,是客戶端可以以統一的接口使用它們。
擴展

雙向適配器模式

橋接模式

定義

將抽象和實現分離,使它們可獨立變化,它是用組合關係代替繼承關係來實現,從而降低抽象和實現這兩個可變維度的耦合度。

特點

橋接(Bridge)模式的特點:

優點

  • 抽象和實現相分離,擴展能力強。
  • 實現細節對客戶透明。

缺點

  • 增加系統理解和設計難度(由於聚合關係建立在抽象層,要求開發者對抽象化進行設計和編程)。
結構

橋接模式包含一下角色

  • 抽象化(Abstract)角色:定義抽象類,包含對實例化對象的引用
  • 擴展抽象化(Refined)角色:是抽象化角色的子類,實現父類的抽象方法,並通過關聯關係調用實現化對象中的相關業務方法。
  • 實現化(Implementor)角色:定義實現化接口,供擴展抽象化對象調用。
  • 具體實現化(Concrete Implementor)角色:實現了實現化接口,完成對實現化接口業務具體實現。
    在這裏插入圖片描述
實現
/**
 *1、定義實現化接口(生產不同顏色的汽車)
 */
 public interface IColorCar {
    String getColor();
}
/**
 *2、定義具體實現化類(實現實實現化接口,完成相關業務的實現)
 */
//生產黑色汽車
public class BlackColorCar implements IColorCar {
    @Override
    public String getColor() {
        return "黑色";
    }
}
//生產紅色汽車
public class ReadColorCar implements IColorCar {
    @Override
    public String getColor() {
        return "紅色";
    }
}
/**
 *3、定義抽象化角色,生產不同類型汽車
 */
public abstract class AbsTypeCar {
    //包含對實例化對象的引用
    IColorCar iColorCar;
    public AbsTypeCar(IColorCar iColorCar) {
        this.iColorCar = iColorCar;
    }
    abstract String getType();
    public abstract void show();
}
/**
 *4、定義擴展抽象化對象,是抽象化對象的子類(完成父類的抽象方法),通過關聯關係調用實例化對象方法
 */
 //擴展抽象化類-公共汽車 
 public class BusTypeCar extends AbsTypeCar {
    public BusTypeCar(IColorCar iColorCar) {super(iColorCar);}

    @Override
    String getType() {return "公共汽車";}

    @Override
    public void show() {
        System.out.println(iColorCar.getColor() + getType());
    }
}

//擴展抽象化類-出租車
public class TaxiTypeCar extends AbsTypeCar {
    public TaxiTypeCar(IColorCar iColorCar) {super(iColorCar);}
    
    @Override
    String getType() {return "出租車";}

    @Override
    public void show() {
        System.out.println(iColorCar.getColor() + getType());
    }
}
//測試
public class Test {
    public static void main(String[] args) {
        IColorCar readCar = new ReadColorCar();
        AbsTypeCar busCar = new BusTypeCar(readCar);
        busCar.show();
        
        AbsTypeCar busCar2 = new BusTypeCar(new BlackColorCar());
        busCar2.show();

        IColorCar blackCar = new BlackColorCar();
        AbsTypeCar taxiCar = new TaxiTypeCar(blackCar);
        taxiCar.show();
    }
}

//輸出
紅色公共汽車
黑色公共汽車
黑色出租車
應用場景
  • 當一個類存在兩個獨立變化的緯度,且這兩個維度都需要擴展時。
  • 當一個系統不希望使用繼承,或使用多層次繼承導致類個數急劇增加時。
  • 當一個系統需要在構建抽象化角色與實例化角色之間增加的靈活性時。
擴展

有時橋接模式可與適配器模式合用,當橋接模式的實例化接口與現有類的接口不一致時,可在兩者中間定義適配器將兩者連接起來。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章