結(jié)構(gòu)類模式(讀書筆記)

最近在讀秦小波寫的設(shè)計(jì)模式之禪這本書,結(jié)構(gòu)類模式讀完了,寫下一篇文章做下總結(jié)。結(jié)構(gòu)類模式包括適配器模式、橋梁模式、組合模式、裝飾模式、門面模式、享元模式和代理模式。為什么叫結(jié)構(gòu)類模式呢?因?yàn)樗鼈兌际峭ㄟ^組合類或?qū)ο螽a(chǎn)生更大結(jié)構(gòu)以適應(yīng)更高層次的邏輯需求。

適配器模式(Adapter Pattern)

定義:將一個(gè)類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個(gè)類能夠在一起工作。通用類圖如下:

1-1 adpater.png

如何理解呢?

從通用類圖中可以看到有三個(gè)角色,Adaptee源角色、Target目標(biāo)角色、Adapter適配器角色,Target就是客戶端所期待的接口,Adaptee就是要被轉(zhuǎn)換的角色,通常它是已經(jīng)存在的、運(yùn)行良好的類或?qū)ο?,Adapter的作用就是把Adaptee轉(zhuǎn)換成為Target。簡(jiǎn)單來說就是有A和B兩個(gè)不同的類,如果想讓A類像B類一樣使用,這個(gè)時(shí)候就可以通過一個(gè)適配器C類,適配器C類負(fù)責(zé)把A類轉(zhuǎn)換成B類,這樣使用A類就可以像使用B類那樣。

適配器模式的使用場(chǎng)景有哪些?只要記住一點(diǎn)就可以了:你有動(dòng)機(jī)修改一個(gè)已經(jīng)投產(chǎn)的接口時(shí),就可以考慮適配器模式了。比如系統(tǒng)擴(kuò)展了,需要使用一個(gè)已有或新建立的類,但這個(gè)類又不符合系統(tǒng)的接口,可以使用適配器模式解決這個(gè)問題。

適配器模式通用源碼

public interface Target {
    // 目標(biāo)角色有自己的方法
    public void request();
}

public class ConcreteTarget implements Target {
    @Override
    public void request() {
        System.out.println("If you need any help, please call me.");
    }
}

public class Adaptee {
    // 原有的業(yè)務(wù)邏輯
    public void doSomething() {
        System.out.println("我非常忙");
    }
}

public class Adapter extends Adaptee implements Target {
    @Override
    public void request() {
        super.doSomething();
    }
}

// 在Client場(chǎng)景類中使用它
public class Client {
    public static void main(String[] args) {
        // 原有業(yè)務(wù)邏輯
        Target target = new ConcreteTarget();
        target.request();

        // 增加了適配器后的角色
        Target target1 = new Adapter();
        target1.request();
    }
}

適配模式的擴(kuò)展

前面所說的可以被稱為類適配器,下面介紹一種對(duì)象適配器,是適配器模式的一種擴(kuò)展,通用類圖如下:

1-2 adaptee2.png

比較兩個(gè)通用類圖,發(fā)現(xiàn)一種是Adapter繼承于Adaptee,一種是Adapter關(guān)聯(lián)Adaptee1和Adaptee2,所以通過繼承關(guān)系的叫做類適配器,通過關(guān)聯(lián)關(guān)系的叫做對(duì)象適配器。類適配器與對(duì)象適配器的區(qū)別:類適配器是類間繼承,對(duì)象適配器是對(duì)象的合成關(guān)系,也可以說是類的關(guān)聯(lián)關(guān)系。

注意點(diǎn):Adapter適配器不要加入自己的實(shí)現(xiàn),它所有的實(shí)現(xiàn)都是委托給Adaptee源角色來實(shí)現(xiàn)的。

代理模式(Proxy Pattern)

定義:為其他對(duì)象提供一種代理以控制這個(gè)對(duì)象的訪問。通用類圖如下:

1-3 proxy.png

如何理解代理模式呢?

通過一個(gè)例子來理解,比如一家公司請(qǐng)明星為自己的產(chǎn)品代言,這家公司如果直接聯(lián)系該明星,明星就會(huì)說這件事請(qǐng)和我的經(jīng)紀(jì)人聯(lián)系,換句話說就是想要請(qǐng)明星代言,先要找到其經(jīng)紀(jì)人,通過經(jīng)紀(jì)人在去和明星進(jìn)行商討。在這里經(jīng)紀(jì)人就可以理解為一個(gè)代理,通過經(jīng)紀(jì)人來對(duì)明星進(jìn)行訪問,這就是代理模式。

代理模式的使用場(chǎng)景?比如Spring AOP,非常典型的動(dòng)態(tài)代理。

代理模式通用源碼

public interface Subject {
    public void request();
}

public class RealSubject implements Subject {
    @Override
    public void request() {
        // 業(yè)務(wù)邏輯處理
    }
}

public class Proxy implements Subject {
    // 要代理哪個(gè)實(shí)現(xiàn)類
    private Subject subject = null;

    public Proxy(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }

    // 預(yù)處理
    private void before() {
        // do something
    }

    // 善后處理
    private void after() {
        // do something
    }
}

// Client場(chǎng)景類
public class Client {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Proxy proxy = new Proxy(subject);
        proxy.request();
    }
}

代理模式的擴(kuò)展

擴(kuò)展一:普通代理

它要求客戶端只能訪問代理角色,而不能訪問真實(shí)角色。

擴(kuò)展二:強(qiáng)制代理

要求客戶端必須通過真實(shí)角色找到代理角色,否則不能訪問真實(shí)角色。

擴(kuò)展三:代理是有個(gè)性的

一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,完成不同任務(wù)的整合。也就是說代理類不僅僅可以實(shí)現(xiàn)主題接口,也可以實(shí)現(xiàn)其他接口完成不同的任務(wù),而且代理的目的是在目標(biāo)對(duì)象的基礎(chǔ)上作增強(qiáng),這種增強(qiáng)的本質(zhì)通常就是對(duì)目標(biāo)對(duì)象的方法進(jìn)行攔截和過濾。

擴(kuò)展四:動(dòng)態(tài)代理

動(dòng)態(tài)代理通用類圖如下:

1-4 dynamic-proxy.png

動(dòng)態(tài)代理是在實(shí)現(xiàn)階段不用關(guān)心代理誰,而在運(yùn)行階段才指定代理哪一個(gè)對(duì)象。

裝飾模式(Decorator Pattern)

定義:動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,裝飾模式比生成子類更為靈活。通用類圖如下:

1-5 decorator.png

如何理解裝飾呢?

裝飾類比較簡(jiǎn)單,看下其使用場(chǎng)景:需要擴(kuò)展一個(gè)類的功能,或給一個(gè)類增加附加功能;需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)地撤銷;需要為一批兄弟類進(jìn)行改裝或加裝功能。

裝飾模式通用源碼

public abstract class Component {
    public abstract void operate();
}

public class ConcreteComponent extends Component {
    @Override
    public void operate() {
        System.out.println("do something");
    }
}

public class Decorator extends Component {
    private Component component = null;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        this.component.operate();
    }
}

public class ConcreteDecorator1 extends Decorator {
    public ConcreteDecorator1(Component component) {
        super(component);
    }

    public void method1() {
        System.out.println("method1 修飾");
    }

    @Override
    public void operate() {
        this.method1();
        super.operate();
    }
}

public class ConcreteDecorator2 extends Decorator {
    public ConcreteDecorator2(Component component) {
        super(component);
    }

    public void method2() {
        System.out.println("method2 修飾");
    }

    @Override
    public void operate() {
        this.method2();
        super.operate();
    }
}

public class Client {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        // 修飾1
        component = new ConcreteDecorator1(component);
        // 修飾2
        component = new ConcreteDecorator2(component);
        component.operate();
    }
}

門面模式(Facade Pattern)

定義:要求一個(gè)子系統(tǒng)的外部與其內(nèi)部的通訊必須通過一個(gè)統(tǒng)一的對(duì)象進(jìn)行。門面模式提供一個(gè)高層次的接口,使得子系統(tǒng)更易于使用。通用類圖如下:

1-6 facade.png

如何理解門面模式呢?

簡(jiǎn)單來說,門面對(duì)象是外界訪問子系統(tǒng)內(nèi)部的唯一通道。比如有A、B、C三個(gè)子系統(tǒng),要想訪問這三個(gè)子系統(tǒng),必須通過一個(gè)統(tǒng)一的門面對(duì)象來訪問。

門面模式的使用場(chǎng)景:為一個(gè)復(fù)雜的模塊或子系統(tǒng)提供一個(gè)供外界訪問的接口;子系統(tǒng)相對(duì)獨(dú)立(外界對(duì)系統(tǒng)的訪問只要黑箱操作即可)。

注意點(diǎn):門面不參與子系統(tǒng)內(nèi)的業(yè)務(wù)邏輯。

門面模式通用源碼

public class ClassA {
    // 子系統(tǒng)A
    public void doSomethingA() {
        System.out.println("do something A");
    }
}

public class ClassB {
    // 子系統(tǒng)B
    public void doSomethingB() {
        System.out.println("do something B");
    }
}

public class ClassC {
    // 子系統(tǒng)C
    public void doSomethingC() {
        System.out.println("do something C");
    }
}

public class Facade {
    // 被委托的對(duì)象
    ClassA classA = new ClassA();
    ClassB classB = new ClassB();
    ClassC classC = new ClassC();
    // 提供給外部的訪問方法
    public void methodA() {
        this.classA.doSomethingA();
    }

    public void methodB() {
        this.classB.doSomethingB();
    }

    public void methodC() {
        this.classC.doSomethingC();
    }
}

組合模式(Composite Pattern)

定義:將對(duì)象組合成樹形結(jié)構(gòu)以表示部分-整體的層次結(jié)構(gòu),使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性。通用類圖如下:

1-7 composite.png

如何理解組合模式呢?

Component抽象構(gòu)件角色:定義參加組合對(duì)象的共有方法和屬性,可以定義一些默認(rèn)的行為或?qū)傩?;Leaf葉子構(gòu)件:葉子對(duì)象,其下再也沒有其他的分支,也就是遍歷的最小單位;Composite樹枝構(gòu)件:樹枝對(duì)象,它的作用是組合樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)形成一個(gè)樹形結(jié)構(gòu)。

組合模式的使用場(chǎng)景:維護(hù)和展示部分-整體關(guān)系的場(chǎng)景,如樹形菜單、文件和文件夾管理;從一個(gè)整體中能夠獨(dú)立出部分模塊或功能的場(chǎng)景。

組合模式通用源碼

public abstract class Component {
    // 個(gè)體和整體都具有的共享 抽象構(gòu)件
    public void doSomething() {
        // 編寫業(yè)務(wù)邏輯
        System.out.println("個(gè)體和整體都具有的共享");
    }
}

public class Composite extends Component {
    // 構(gòu)件容器
    private ArrayList<Component> componentArrayList = new ArrayList<Component>();
    // 增加一個(gè)葉子構(gòu)件或樹枝構(gòu)件
    public void add(Component component) {
        this.componentArrayList.add(component);
    }
    // 刪除一個(gè)葉子構(gòu)件或樹枝構(gòu)件
    public void remove(Component component) {
        this.componentArrayList.remove(component);
    }
    // 獲得分支下的所有葉子構(gòu)件和樹枝構(gòu)件
    public ArrayList<Component> getChildren() {
        return this.componentArrayList;
    }
}

public class Leaf extends Component {
    // 樹葉構(gòu)件
}

// 場(chǎng)景類
public class Client {
    public static void main(String[] args) {
        // 創(chuàng)建一個(gè)根節(jié)點(diǎn)
        Composite root = new Composite();
        root.doSomething();
        // 創(chuàng)建一個(gè)樹枝構(gòu)件
        Composite branch = new Composite();
        // 創(chuàng)建一個(gè)葉子節(jié)點(diǎn)
        Leaf leaf = new Leaf();
        // 建立整體
        root.add(branch);
        branch.add(leaf);

        display(root);
    }

    // 通過遞歸遍歷樹
    public static void display(Composite root) {
        for (Component c : root.getChildren()) {
            if (c instanceof Leaf) {
                // 葉子結(jié)點(diǎn)
                c.doSomething();
            }else {
                // 樹枝結(jié)點(diǎn)
                display((Composite) c);
            }
        }
    }
}

橋梁模式(Bridge Pattern)

定義:將抽象和實(shí)現(xiàn)解耦,使得兩者可以獨(dú)立地變化。通用類圖如下:

1-8 bridge.png

如何理解橋梁模式呢?

簡(jiǎn)單來理解就是把經(jīng)常變化的部分封裝到Implementor中,不經(jīng)常變化的留在抽象中。如果抽閑的實(shí)現(xiàn)類想要調(diào)用Implementor中的方法怎么辦?搭個(gè)橋過去,也就是在實(shí)現(xiàn)類中保留一個(gè)Implementor的引用,這也是為什么叫橋梁模式的原因。最終的目的就是把抽象和實(shí)現(xiàn)分離開來。

使用場(chǎng)景有哪些呢?不希望或不適用使用繼承的場(chǎng)景,例如繼承層次過渡、無法更細(xì)化設(shè)計(jì)顆粒等場(chǎng)景,需要考慮使用橋梁模式;接口或抽象類不穩(wěn)定的場(chǎng)景;重用性要求較高的場(chǎng)景,設(shè)計(jì)的顆粒度越細(xì),則被重用的可能性就越大;

橋梁模式通用源碼

public interface Implementor {
    // 基本方法
    public void doSomething();
    public void doAnything();
}

public class ConcreteImplementor1 implements Implementor {
    @Override
    public void doSomething() {
        // 業(yè)務(wù)邏輯處理
    }

    @Override
    public void doAnything() {
        // 業(yè)務(wù)邏輯處理
    }
}

public class ConcreteImplementor2 implements Implementor {
    @Override
    public void doSomething() {
        // 業(yè)務(wù)邏輯處理
    }

    @Override
    public void doAnything() {
        // 業(yè)務(wù)邏輯處理
    }
}

public abstract class Abstraction {
    private Implementor implementor;
    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }

    // 自身的行為和屬性
    public void request() {
        this.implementor.doSomething();
    }

    // 獲得實(shí)現(xiàn)化角色
    public Implementor getImplementor() {
        return implementor;
    }
}

public class RefinedAbstraction extends Abstraction {
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    @Override
    public void request() {
        super.request();
        super.getImplementor().doAnything();
    }
}

public class Client {
    public static void main(String[] args) {
        // 定義一個(gè)實(shí)現(xiàn)化角色
        Implementor implementor = new ConcreteImplementor1();
        // 定義一個(gè)抽象化角色
        Abstraction abstraction = new RefinedAbstraction(implementor);
        // 執(zhí)行行文
        abstraction.request();
    }
}

享元模式(Flyweight Pattern)

定義:使用共享對(duì)象可有效地支持大量的細(xì)粒度的對(duì)象。通用類圖如下:

1-9 flyweight.png

如何理解享元模式呢?

享元模式比較簡(jiǎn)單,可以大大減少應(yīng)用程序創(chuàng)建的對(duì)象,降低程序內(nèi)存占用,增強(qiáng)程序的性能,但它同時(shí)也提高了系統(tǒng)的復(fù)雜性,需要分離出外部狀態(tài)和內(nèi)部狀態(tài),而且外部狀態(tài)具有固化特性,不應(yīng)該隨內(nèi)部狀態(tài)改變而改變,否則導(dǎo)致系統(tǒng)的邏輯混亂。

使用場(chǎng)景:系統(tǒng)中存在大量相似的對(duì)象;需要緩沖池的場(chǎng)景。

注意點(diǎn):使用享元模式可以實(shí)現(xiàn)對(duì)象池,但是這兩者還是有比較大的差異,對(duì)象池著重在對(duì)象的復(fù)用上,池中的每個(gè)對(duì)象是可替換的,從同一個(gè)池中獲得A對(duì)象和B對(duì)象對(duì)客戶端來說是完全相同的,它主要解決復(fù)用,而享元模式著重解決對(duì)象的共享問題,如何建立多個(gè)可共享的細(xì)粒度對(duì)象則是其關(guān)注的重點(diǎn)。

享元模式通用代碼

public abstract class Flyweight {
    // 內(nèi)部狀態(tài)
    private String intrinsic;
    // 外部狀態(tài)
    private final String extrinsic;
    // 要求享元角色必須接受外部狀態(tài),外部狀態(tài)可以理解為key,這樣一個(gè)東西.
    public Flyweight(String extrinsic) {
        this.extrinsic = extrinsic;
    }

    public String getIntrinsic() {
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }

    // 定義業(yè)務(wù)操作
    public abstract void operate();
}

public class ConcreteFlyweight1 extends Flyweight {
    public ConcreteFlyweight1(String extrinsic) {
        super(extrinsic);
    }

    // 根據(jù)外部狀態(tài)進(jìn)行邏輯處理
    @Override
    public void operate() {
        // 業(yè)務(wù)邏輯處理
    }
}

public class ConcreteFlyweight2 extends Flyweight {
    public ConcreteFlyweight2(String extrinsic) {
        super(extrinsic);
    }

    // 根據(jù)外部狀態(tài)進(jìn)行邏輯處理
    @Override
    public void operate() {
        // 業(yè)務(wù)邏輯處理
    }
}

public class FlyweightFactory {
    // 定義一個(gè)池容器
    private static HashMap<String, Flyweight> pool = new HashMap<String, Flyweight>();
    // 享元工廠
    public static Flyweight getFlyweight(String extrinsic) {
        Flyweight flyweight = null;
        if (pool.containsKey(extrinsic)) {
            flyweight = pool.get(extrinsic);
        }else {
            // 根據(jù)外部狀態(tài)創(chuàng)建享元對(duì)象
            flyweight = new ConcreteFlyweight1(extrinsic);
            // 放置到池中
            pool.put(extrinsic, flyweight);
        }
        return flyweight;
    }
}

代理模式VS裝飾模式

首先要說的是裝飾模式就是代理模式的一個(gè)特殊應(yīng)用,兩者的共同點(diǎn)是都具有相同的接口,不同點(diǎn)則是代理模式著重對(duì)代理過程的控制,而裝飾模式則是對(duì)類的功能進(jìn)行加強(qiáng)或減弱,它著重類的功能變化。

裝飾模式VS適配器模式

相似點(diǎn):都是包裝作用,都是通過委托方式實(shí)現(xiàn)其功能。不同點(diǎn)是:裝飾模式包裝的是自己的兄弟類,隸屬于同一個(gè)家族(相同接口或父類),適配器模式則修飾非血緣關(guān)系類,把一個(gè)非本家族的對(duì)象偽裝成本家族的對(duì)象,注意是偽裝,本質(zhì)上還是非相同接口的對(duì)象。

國士梅花

歡迎大家關(guān)注國士梅花,技術(shù)路上與你陪伴。

guoshimeihua.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,537評(píng)論 6 13
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,711評(píng)論 19 139
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,097評(píng)論 1 15
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,854評(píng)論 18 399
  • 我愿意用文字表達(dá)自己。 我始終相信文字的力量。 此生短暫也漫長、薄情也浩蕩,我真希望我們的柴米油鹽醬醋茶會(huì)因?yàn)榱硪?..
    美添閱讀 245評(píng)論 0 0

友情鏈接更多精彩內(nèi)容