Mr.Raindrop

Mr.Raindrop

一切都在无可挽回的走向庸俗。
twitter
github

設計模式初探 --結構型模式

结构型模式是為解決怎樣組裝現有的類,設計它們的交互方式,從而達到實現一定的功能目的。結構型模式包容了對很多問題的解決。例如:擴展性(外觀、組成、代理、裝飾)、封裝(適配器、橋接)。

在解決了對象的創建問題之後,對象的組成以及對象之間的依賴關係就成了開發人員關注的焦點,因為如何設計對象的結構、繼承和依賴關係會影響到後續程序的維護性、代碼的健壯性、耦合性等。

適配器模式#

適配器模式(Adapter Pattern)是一種結構型設計模式,其核心作用是將一個類的接口轉換為客戶端期望的另一種接口,從而解決因接口不兼容而無法協同工作的類之間的協作問題。它類似於現實中的 “電源轉換器”,通過中間層接口轉換,讓原本不匹配的組件能夠協同工作。

主要組成部分#
  • 目標接口(Target):客戶端期望的接口,定義了需要調用的方法。
  • 被適配者(Adaptee):需要適配的接口,已存在的類或接口,但其接口與目標不兼容。
  • 適配器(Adapter):連接目標與被適配者的中間層,通過繼承或組合實現接口轉換。
實現#
  • 類適配器(繼承方式):適配器繼承被適配者類,並實現目標接口。靈活性較低

    // 目標接口
    interface Target { void request(); }
    // 被適配者
    class Adaptee { void specificRequest() { /*...*/ } }
    // 適配器
    class Adapter extends Adaptee implements Target {
        @Override void request() { specificRequest(); }
    }
    
  • 對象適配器(組合方式):適配器持有被適配者的對象實例,通過委託調用方法。

    class Adapter implements Target {
        private Adaptee adaptee;
        public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }
        @Override void request() { adaptee.specificRequest(); }
    }
    
  • 接口適配器(缺省適配器):解決目標接口方法過多而客戶端僅需部分實現的問題。它通過抽象類作為中間層,為接口方法提供默認實現(通常為空或簡單邏輯),允許子類按需重寫特定方法,從而避免強制實現所有接口方法的冗餘。

    // 目標接口
    public interface MultiFunction {
        void print();
        void scan();
        void copy();
    }
    
    // 抽象適配器類
    public abstract class DefaultAdapter implements MultiFunction {
        @Override
        public void print() {}  // 默認空實現
        @Override
        public void scan() {}
        @Override
        public void copy() {}
    }
    
    // 具體適配器類
    public class PrinterAdapter extends DefaultAdapter {
        @Override
        public void print() {
            System.out.println("打印功能已啟用");
        }
        // scan()和copy()無需重寫,直接使用默認空邏輯
    }
    
    // 調用
    public class Client {
        public static void main(String[] args) {
            MultiFunction device = new PrinterAdapter();
            device.print();  // 輸出:打印功能已啟用
        }
    }
    

外觀模式#

外觀模式(Facade Pattern)是一種結構型設計模式,其核心目標是為複雜的子系統提供一個統一的、簡化的接口,從而隱藏系統內部的複雜性,降低客戶端與子系統之間的耦合度,提升系統的易用性和可維護性。

主要組成部分#

通過一個門面類(Facade) 封裝多個子系統的交互邏輯,對外提供高層次的接口。客戶端只需與門面類交互,無需直接調用複雜的子系統組件。

  • 門面角色(Facade):統一協調子系統的調用,對外提供簡化接口。
  • 子系統角色(Subsystem Classes):實現具體功能的模塊。
  • 客戶端(Client)

image

橋接模式#

2. 橋接模式 — Graphic Design Patterns

設想如果要繪製矩形、圓形、橢圓、正方形,我們至少需要 4 個形狀類,但是如果繪製的圖形需要具有不同的顏色,如紅色、綠色、藍色等,此時至少有如下兩種設計方案:

  • 第一種設計方案是為每一種形狀都提供一套各種顏色的版本。
  • 第二種設計方案是根據實際需要對形狀和顏色進行組合

橋接模式將繼承關係轉換為關聯關係,從而降低了類與類之間的耦合,減少了代碼編寫量。將抽象部分與它的實現部分分離,使它們都可以獨立地變化。

橋接模式的構造#

橋接設計模式

image

實現案例#

橋接模式(Bridge Pattern)-(最通俗易懂的案例)_橋接器模式實例 - CSDN 博客

組合模式#

將這棵樹理解成一個大的容器,容器裡面包含很多的成員對象,這些對象即可以是容器對象也可以是葉子對象。但是由於容器對象和葉子對象在功能上的區別,使得我們在使用的過程中必須要區分容器對象和葉子對象,但是這樣就會給客戶帶來不必要的麻煩,作為客戶,它希望能夠一致的對待容器對象和葉子對象。這就是組合模式的設計動機:組合模式定義了如何將容器對象和葉子對象進行遞歸組合,使得客戶在使用的過程中無需區分,可以對它們進行一致的處理

組合模式(Composite Pattern) 也稱為 整體 - 部分(Part-Whole)模式,它的宗旨是通過將單個對象(葉子節點)和組合對象(樹枝節點)用相同的接口進行表示,使得客戶對單個對象和組合對象的使用具有一致性。

組合模式結構#

image
組合模式分為透明式的組合模式和安全式的組合模式。在該方式中,由於抽象構件聲明了所有子類中的全部方法,所以客戶端無須區別樹葉對象和樹枝對象,對客戶端來說是透明的。但其缺點是:樹葉構件本來沒有 Add ()、Remove () 及 GetChild () 方法,卻要實現它們(空實現或拋異常),這樣會帶來一些安全性問題。

透明模式的實現#

27 設計模式 —— 組合模式(詳解版) - 知乎

public class CompositePattern {
    public static void main(String[] args) {
        Component c0 = new Composite();
        Component c1 = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        Component leaf3 = new Leaf("3");
        c0.add(leaf1);
        c0.add(c1);
        c1.add(leaf2);
        c1.add(leaf3);
        c0.operation();
    }
}
//抽象構件
interface Component {
    public void add(Component c);
    public void remove(Component c);
    public Component getChild(int i);
    public void operation();
}
//樹葉構件
class Leaf implements Component {
    private String name;
    public Leaf(String name) {
        this.name = name;
    }
    public void add(Component c) {
    }
    public void remove(Component c) {
    }
    public Component getChild(int i) {
        return null;
    }
    public void operation() {
        System.out.println("樹葉" + name + ":被訪問!");
    }
}
//樹枝構件
class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<Component>();
    public void add(Component c) {
        children.add(c);
    }
    public void remove(Component c) {
        children.remove(c);
    }
    public Component getChild(int i) {
        return children.get(i);
    }
    public void operation() {
        for (Object obj : children) {
            ((Component) obj).operation();
        }
    }
}
安全模式實現#

由於葉子和分支有不同的接口,客戶端在調用時要知道樹葉對象和樹枝對象的存在,所以失去了透明性。
動圖

Leaf 類不需要實現那些對於它來說無用的方法,如 add、remove 和 getChild,這使得設計更加合理且易於理解。同時,這樣的設計也避免了潛在的錯誤

代理模式#

控制對一個對象的訪問,通過引入一個代理對象來管理這個訪問。代理模式允許在不改變原對象接口的情況下,增加額外的功能或控制訪問方式。

主要組成部分#
  1. Subject(目標接口)
    • 定義了代理類和真實主題類的共同接口。
    • 這使得客戶端可以在不改變代碼的情況下,使用代理或者真實主題對象。
  2. Real Subject(真實主題)
    • 實現了 Subject 接口的具體業務邏輯。
    • 這是客戶端真正需要訪問的對象。
  3. Proxy(代理)
    • 也實現了 Subject 接口,並且維護一個對 Real Subject 的引用。
    • 代理類負責在客戶端和真實主題之間進行協調,可能在請求前後執行一些額外的操作。

代理模式的結構圖

靜態代理#

一個代理只能服務於一個特定的業務實現類

1. 定義目標接口

首先,定義一個接口,該接口聲明了需要被代理的方法。

JAVApublic interface Subject {
    void request();
}

2. 實現真實主題類

創建一個實現了上述接口的具體類,該類包含實際的業務邏輯。

JAVApublic class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: 處理請求");
    }
}

3. 創建代理類

代理類也實現相同的接口,並持有一個對真實主題對象的引用。代理類可以在調用真實主題的方法之前或之後添加額外的功能。

JAVApublic class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        // 執行預處理操作
        System.out.println("Proxy: 執行預處理操作");
        realSubject.request();
        // 執行後處理操作
        System.out.println("Proxy: 執行後處理操作");
    }
}

4. 編寫客戶端代碼

客戶端通過接口與代理類或真實主題對象交互,無需知道是直接調用還是通過代理。

JAVApublic class Client {
    public static void main(String[] args) {
        // 創建真實主題對象
        Subject realSubject = new RealSubject();
        
        // 創建代理對象,並將真實主題傳遞給代理
        Subject proxy = new Proxy(realSubject);
        
        // 調用代理的方法
        proxy.request();
    }
}
動態代理#

動態代理採用反射的機制,在運行時創建一個接口類的實例。

//業務接口
interface DateService {
    void add();
    void del();
}
// 具體業務類
class DateServiceImplA implements DateService {
    @Override
    public void add() {
        System.out.println("成功添加!");
    }

    @Override
    public void del() {
        System.out.println("成功刪除!");
    }
}
// 代理
class ProxyInvocationHandler implements InvocationHandler {
    private DateService service;

    public ProxyInvocationHandler(DateService service) {
        this.service = service;
    }
    
    
	// this.getClass().getClassLoader() 第一个是类加载器,用来加载代理类。
    // service.getClass().getInterfaces()  被代理类实现的所有接口,使得代理对象能够提供与目标对象相同的接口视图。
    // this 当前实例(即实现了 InvocationHandler 接口的对象),用于处理对代理对象的方法调用。
    public Object getDateServiceProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
    }

    /* invoke 方法:
	重寫的 invoke 方法會在代理對象上調用任何方法時觸發。它接收三個參數:
	Object proxy:代表代理對象本身。
	Method method:表示正在被調用的目標方法。
	Object[] args:傳遞給目標方法的參數列表。
	在方法體內,首先通過 method.invoke(service, args); 調用了目標對象的實際方法,並將結果保存到 result 變量中。
	然後打印出一條日志信息,表明代理對象執行了某個方法,並記錄了方法名和返回值。
	最後返回目標方法的執行結果。
	*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        var result = method.invoke(service, args); // 讓service調用方法,方法返回值
        System.out.println(proxy.getClass().getName() + "代理類執行" + method.getName() + "方法,返回" + result +  ",記錄日志!");
        return result;
    }
}

//客戶端
public class Test {
    public static void main(String[] args) {
        DateService serviceA = new DateServiceImplA();
        DateService serviceProxy = (DateService) new ProxyInvocationHandler(serviceA).getDateServiceProxy();
        serviceProxy.add();
        serviceProxy.del();
    }
}

裝飾模式#

裝飾模式(Decorator Pattern)-(最通俗易懂的案例)_裝飾模式使用例子 - CSDN 博客

裝飾模式(Decorator),動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活。裝飾模式屬於結構型模式,它是作為現有的類的一個包裝。

Decorator 類圖

裝飾模式的主要角色包括:

  • Component(抽象組件):定義了被裝飾對象和裝飾器共同的接口。定義一個對象接口,可以給這些對象動態地添加職責。

  • ConcreteComponent(具體組件):實現了 Component 接口,是被裝飾的對象。定義了一個具體的對象,也可以給這個對象添加一些職責。

  • Decorator(抽象裝飾器):持有一個 Component 對象的引用,並定義了一個與 Component 具有相同接口的方法,以保持一致性。

  • ConcreteDecorator(具體裝飾器):實現了 Decorator 定義的方法,在調用被裝飾對象的方法前後可以執行一些額外的操作。

具體實現#
// 抽象組件
public interface INoodles {
    public void cook();
}

// 具體組件
public class Noodles implements INoodles {

    @Override
    public void cook() {
        System.out.println("的面條");
    }
}

// 抽象裝飾類
public abstract class NoodlesDecorator implements INoodles {

    private INoodles noodles;    //添加一個INoodles 的引用

    public NoodlesDecorator(INoodles noodles){     //通過構造器來設置INoodles 
        this.noodles = noodles;
    }

    @Override
    public void cook() {
        if (noodles!=null){
            noodles.cook();
        }
    }

}

// 具體裝飾類
public class EggDecorator extends NoodlesDecorator {

    public EggDecorator(INoodles noodles) {
        super(noodles);
    }

    /**
     * 重寫父類的cook方法,並添加自己的實現,調用父類的cook方法,此cook方法是通過本類的構造器
     * EggDecorator(INoodles noodles)傳入的noodles的cook操作
     */
    @Override
    public void cook() {
        System.out.println("加了一个荷包蛋");
        super.cook();
    }
}

// 具體裝飾類
public class BeefDecorator extends NoodlesDecorator {

    public BeefDecorator(INoodles noodles) {
        super(noodles);
    }

    @Override
    public void cook() {
        System.out.println("加了一斤牛肉");
        super.cook();
    }
}

public class Client {

    public static void main(String[] args) {

        INoodles noodles1 = new Noodles();
        INoodles noodles1WithEgg = new EggDecorator(noodles1);
        noodles1WithEgg.cook();
    }
}
裝飾模式和代理模式的區別#
  1. 意圖不同:裝飾模式關注的是增強對象的功能,而代理模式更注重於控制對對象的訪問。
  2. 實現細節:在裝飾模式中,裝飾器和被裝飾的對象必須實現相同的接口,這樣才能保證裝飾鏈中的對象能夠相互替換。而在代理模式中,代理類和目標對象也常常實現相同的接口,但這主要是為了保持一致性,並不是必須的條件。
  3. 設計考量:使用裝飾模式時,設計者更多考慮的是如何在不修改現有代碼的情況下擴展功能;而使用代理模式時,則更多地考慮如何保護、管理對資源的訪問。
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。