設計模式——結構性設計模式

来源:https://www.cnblogs.com/buchizicai/archive/2022/08/17/16580751.html
-Advertisement-
Play Games

結構型模式所描述的是如何將類和對象結合在一起來形成一個更大的結構,它描述兩種不同的事物:類和對象,根據這一點,可分為類結構型和對象結構型模式。類結構型模式關心類的組合,由多個類可以組合成一個更大的系統,在類結構型模式中一般只存在繼承關係和實現關係;對象結構型模式關心類與對象的組合,通過關聯關係使得在... ...


結構性設計模式

針對類與對象的組織結構。(白話:類與對象之間的交互的多種模式

類/對象適配器模式

當需要傳入一個A類型參數,但只有B類型類時,就需要一個A類型的適配器裝入B類的數據,來將B數據轉成A類型,然後作為參數傳入

適配器,在生活中又稱轉換器。現在的手機基本都割去了3.5mm的耳機介面,此時只有有線耳機,要聽歌就需要一個轉換器將3.5mm介面轉成手機有的type-c的介面

類適配器(不建議)

繼承需要轉變的類

//主方法
public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	test( ? );   //我們沒有35MM類型的手機介面,只有type-c的手機介面,那這裡該填個type-c。所以需要一個轉介面將35MM轉為type-c介面
    }

    public static void test(typeC typec){   //現在我們需要調用test方法,但是test方法需要類型是typeC
        System.out.println("成功得到:"+typec.listen());
    }
}

//介面
public interface typeC {    //typeC介面也想聽歌
    String listen();
}

//父類
public class 35MM{	
    public String listenMusic(){
		return "有線耳機聽歌!"	//因為只有有線耳機,所以只有35MM才能聽歌
    }
}

//子類作適配器 繼承35MM,實現type-C介面
public class Adapter extends 35MM implements typeC{
    
    @Override
    public String listen() {  //現在不再繼承35MM,僅實現typeC介面
        return super.listenMusic();
    }
}

對象適配器

將需要轉變的類實例化,並用作與適配器類的構造方法

因為類適配器會占用一個繼承位,而java又是單繼承的。如果typeC不是介面而是抽象類的話就用不了了。所以提出對象適配器:

//主方法
public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	test( ? );   //我們沒有35MM類型的手機介面,只有type-c的手機介面,那這裡該填個type-c。所以需要一個轉介面將35MM轉為type-c介面
    }

    public static void test(typeC typec){   //現在我們需要調用test方法,但是test方法需要類型是typeC
        System.out.println("成功得到:"+typec.listen());
    }
}

//介面
public interface typeC {    //typeC介面也想聽歌
    String listen();
}

//父類
public class 35MM{	
    public String listenMusic(){
		return "有線耳機聽歌!"	//因為只有有線耳機,所以只有35MM才能聽歌
    }
}

//子類作適配器 繼承35MM,實現type-C介面
public class Adapter implements typeC{	//現在不再繼承35MM,僅實現typeC介面
    35MM 35mm;	//實例化需要轉變的類
    public Adapter(35MM 35mm){	//將實例化的對象用於構造對象
		this.35mm = 35mm;
    }
    
    @Override
    public String listen() {   //接著實現listen方法,直接使用typeC提供的實現
        return 35mm.listenMusic();
    }
}

橋接模式

配置自定義

同一種產品有著不同的配置,就像手機有:運行記憶體 4 6 8g,存儲記憶體:64 128 256g,晶元:驍龍 A系列 麒麟 聯發科 獵戶座。不能每一種配置都寫一個類就太麻煩了,所以有了橋接模式,可以通過多個類橋接成一個產品類。

優勢:可以通過多個維度來自由設定配置

這裡以華為手機舉例:(小知識——華為手機是用自家的麒麟晶元)

//第一層類:繼承該類可以自定義晶元類型
public abstract class AbstractPhone {
	private Size size; //這裡是描述存儲記憶體。由於舉例簡單點方便看得懂就不寫運行記憶體了
    
    public AbstractPhone(Size size){
        this.size = size;
    }
    
    public abstract String getType();	//這裡的類型是指晶元類型
}

//介面及實現類
public interface Size{
    String getSize();
}
public class 256G implements Size{
    @Override
    public String getSize() {
        return "256g記憶體";
    }
}

//第二層類:繼承該類可以自定義晶元類型和存儲記憶體的尺度大小
public abstract class RefinedAbstractPhone extends AbstractPhone{
    protected RefinedAbstractPhone(Size size) {
        super(size);
    }
    
    public String getSize(){   //添加尺寸維度獲取方式
        return size.getSize();
    }
}

//產品類:繼承第二層類,然後自定義存儲記憶體大小和晶元種類
public class HUAWEI extends RefinedAbstractPhone{
    protected HUAWEI(Size size){	//構造方法指定具體存儲記憶體大小
        super(size);
    }
    
    @Override
    public String getType() {
        return "華為手機";   //返回手機品牌類型
    }
}

//主方法
public static void main(String[] args) {
	HUAWEI huawei = new HUAWEI(new 256G());
    System.out.println(huawei.getType());
    System.out.println(huawei.getSize());
}

組合模式

對多個組件進行統一一樣的操作

組合模式實際上就是將多個組件進行組合,讓用戶可以對它們進行一致性處理。比如我們的文件夾,一個文件夾中可以有很多個子文件夾或是文件。

它就像是一個樹形結構一樣,有分支有葉子,而組合模式則是可以對整個樹形結構上的所有節點進行遞歸處理,比如我們現在希望將所有文件夾中的文件的名稱前面都添加一個首碼,那麼就可以使用組合模式。

image

組合模式的示例如下,這裡我們就用文件和文件夾的例子來講解:

/**
 * 首先創建一個組件抽象,組件可以包含組件,組件有自己的業務方法
 */
public abstract class Component {
    public abstract void addComponent(Component component);    //添加子組件
    public abstract void removeComponent(Component component);   //刪除子組件
    public abstract Component getChild(int index);   //獲取子組件
    public abstract void test();   //執行對應的業務方法,比如修改文件名稱
}

接著我們來編寫兩種實現類:文件夾實現類,文件實現類

public class Directory extends Component{   //目錄可以包含多個文件或目錄

    List<Component> child = new ArrayList<>();   //這裡我們使用List來存放目錄中的子組件

    @Override
    public void addComponent(Component component) {
        child.add(component);
    }

    @Override
    public void removeComponent(Component component) {
        child.remove(component);
    }

    @Override
    public Component getChild(int index) {
        return child.get(index);
    }

    @Override
    public void test() {
        child.forEach(Component::test);   //將繼續調用所有子組件的test方法執行業務
    }
}
public class File extends Component{   //文件就相當於是樹葉,無法再繼續添加子組件了

    @Override
    public void addComponent(Component component) {
        throw new UnsupportedOperationException();   //不支持這些操作了
    }

    @Override
    public void removeComponent(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Component getChild(int index) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void test() {
        System.out.println("文件名稱修改成功!"+this);   //具體的名稱修改操作
    }
}

最後,我們來測試一下:可以看到我們對最外層目錄進行操作後,會遞歸向下處理當前目錄和子目錄中所有的文件

public static void main(String[] args) {
    Directory outer = new Directory();   //新建一個外層目錄
    Directory inner = new Directory();   //新建一個內層目錄
    outer.addComponent(inner);
    outer.addComponent(new File());   //在內層目錄和外層目錄都添加點文件,註意別導錯包了
    inner.addComponent(new File());
    inner.addComponent(new File());
    outer.test();    //開始執行文件名稱修改操作
}

裝飾模式

通過B類 實現對A類方法執行前後,分別多執行一些操作。類似於AOP

適用:業務功能前後實現一些操作。如:在支付前提醒是否需要支付xxx元。

//頂層抽象類
public abstract class Base {   //頂層抽象類,定義了一個test方法執行業務
    public abstract void test();
}

//業務實現類
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是業務方法");   //具體的業務方法
    }
}

//裝飾業務類(這裡的構造方法參數是需要傳入實現業務類對象)
public class Decorator extends Base{   //裝飾者需要將裝飾目標組合到類中

    protected Base base;

    public Decorator(Base base) {
        this.base = base;
    }

    @Override
    public void test() {
        base.test();    //這裡暫時還是使用目標的原本方法實現
    }
}

//具體實現裝飾業務類
public class DecoratorImpl extends Decorator{   //裝飾實現

    public DecoratorImpl(Base base) {
        super(base);
    }

    @Override
    public void test() {    //對原本的方法進行裝飾,我們可以在前後都去添加額外操作
        System.out.println("裝飾方法:我是操作前邏輯");
        super.test();
        System.out.println("裝飾方法:我是操作後邏輯");
    }
}

//主方法
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //將Base實現裝飾一下
    Decorator outer = new DecoratorImpl(decorator);  //裝飾者還可以嵌套,此時是裝飾兩次

    decorator.test();	//裝飾一次:裝飾前——業務方法——裝飾後

    outer.test();	//裝飾兩次:裝飾前——裝飾前——業務方法——裝飾後——裝飾後
}

代理模式

和裝飾模式代碼一模一樣,但核心是思想不同

裝飾模式和代理模式:

  1. 結構相同:都實現同一個介面/抽象類
  2. 作用不同:
    • 裝飾器模式強調的是增強自身,在被裝飾之後你能夠在被增強的類上使用增強後的功能,增強後你還是你,只不過被強化了而已;
    • 代理模式強調要讓別人幫你去做事情,以及添加一些本身與你業務沒有太多關係的事情(記錄日誌、設置緩存等)重點在於讓別人幫你做。

代理模式一般代碼:

//頂層抽象類
public abstract class Base {   //頂層抽象類,定義了一個test方法執行業務
    public abstract void test();
}

//業務實現類
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是業務方法");   //具體的業務方法
    }
}

//代理業務類(這裡的構造方法參數是需要傳入實現業務類對象)
public class Decorator extends Base{   //代理者需要將代理目標組合到類中

    protected Base base;

    public Decorator(Base base) {
        this.base = base;
    }

    @Override
    public void test() {
        base.test();    //這裡暫時還是使用目標的原本方法實現
    }
}

//具體實現代理業務類
public class DecoratorImpl extends Decorator{   //代理實現

    public DecoratorImpl(Base base) {
        super(base);
    }

    @Override
    public void test() {    //對原本的方法進行代理,我們可以在前後都去添加額外操作
        System.out.println("裝飾方法:我是操作前邏輯");
        super.test();
        System.out.println("裝飾方法:我是操作後邏輯");
    }
}

//主方法
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //將Base實現代理一下
    Decorator outer = new DecoratorImpl(decorator);  //代理者還可以嵌套,此時是代理兩次

    decorator.test();	//代理一次:代理前——業務方法——代理後

    outer.test();	//代理兩次:代理前——代理前——業務方法——代理後——代理後
}

實現代理模式除了和裝飾模式一樣的代碼情況外還有兩種實現方式:【因為都是動態代理所以生成的代理類是看不到的】

  1. JDK提供的動態代理:我們不再需要手動編寫繼承關係創建代理類,它能夠在運行時通過反射機製為我們自動生成代理類:【只能代理介面】

    //介面
    public interface Subject {  //JDK提供的動態代理只支持介面
        void test();
    }
    
    //介面實現類
    public class SubjectImpl implements Subject{
    
        @Override
        public void test() {
            System.out.println("我是測試方法!");
        }
    }
    
    //創建動態代理的處理邏輯(就是執行業務前後的方法編寫在裡面)
    public class TestProxy implements InvocationHandler {    //代理類,需要實現InvocationHandler介面
    
        private final Object object;   //這裡需要保存一下被代理的對象,下麵需要用到
    
        public TestProxy(Object object) {
            this.object = object;
        }
    
        @Override   //此方法就是調用代理對象的對應方法時會進入,這裡我們就需要編寫如何進行代理了
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         		//method就是調用的代理對象的哪一個方法,args是實參數組
            System.out.println("代理的對象:"+proxy.getClass());   //proxy就是生成的代理對象了,我們看看是什麼類型的
            Object res = method.invoke(object, args);   //在代理中調用被代理對象原本的方法,因為你是代理,還是得執行一下別人的業務,當然也可以不執行,但是這樣就失去代理的意義了,註意要用上面的object
            System.out.println("方法調用完成,返回值為:"+res);   //看看返回值是什麼
            return res;   //返回返回值
        }
    }
    
  2. Spring在使用的CGLib框架代理。

    maven依賴:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>
    

    代碼實現:

    //介面
    public interface Subject {  //JDK提供的動態代理只支持介面
        void test();
    }
    
    //介面實現類
    public class SubjectImpl implements Subject{
    
        @Override
        public void test() {
            System.out.println("我是測試方法!");
        }
    }
    
    //創建動態代理的處理邏輯(就是執行業務前後的方法編寫在裡面)
    public class TestProxy implements MethodInterceptor {  //首先還是編寫我們的代理邏輯
    
        private final Object target;   //這些和之前JDK動態代理寫法是一樣的
    
        public TestProxy(Object target) {
            this.target = target;
        }
    
        @Override   //我們也是需要在這裡去編寫我們的代理邏輯
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("現在是由CGLib進行代理操作!"+o.getClass());
            return method.invoke(target, objects);   //也是直接調用代理對象的方法即可
        }
    }
    
    //主方法
    public static void main(String[] args) {
        SubjectImpl subject = new SubjectImpl();
    
        Enhancer enhancer = new Enhancer();   //增強器,一會就需要依靠增強器來為我們生成動態代理對象
        enhancer.setSuperclass(SubjectImpl.class);    //直接選擇我們需要代理的類型,直接不需要介面或是抽象類,SuperClass作為代理類的父類存在,這樣我們就可以按照指定類型的方式去操作代理類了
        enhancer.setCallback(new TestProxy(subject));  //設定我們剛剛編寫好的代理邏輯
    
        SubjectImpl proxy = (SubjectImpl) enhancer.create();   //直接創建代理類
    
        proxy.test();   //調用代理類的test方法
    }
    

外觀模式

可以理解為門面模式,將需要通過操作多個類實現的一個功能封裝到一個類中,便於使用

當每個功能是一個系統,完成一個業務需要多個功能時就需要分別調用多個系統,此時就可以將一個業務需要使用的多個系統封裝成一個門面系統,只要調用該門面系統即可完成該業務。

image

舉例:比如現在我們設計了三個子系統,分別是排隊、結婚、領證,正常情況下我們是需要分別去找這三個部門去完成的,但是現在我們通過門面統一來完成

//系統一
public class SubSystemA {
    public void test1(){
        System.out.println("排隊");
    }
}

//系統二
public class SubSystemB {
    public void test2(){
        System.out.println("結婚");
    }
}

//系統三
public class SubSystemC {
    public void test3(){
        System.out.println("領證");
    }
}

//門面
public class Facade {

    SubSystemA a = new SubSystemA();
    SubSystemB b = new SubSystemB();
    SubSystemC c = new SubSystemC();

    public void marry(){   //紅白喜事一條龍服務
        a.test1();
        b.test2();
        c.test3();
    }
}

//主方法
public static void main(String[] args) {
    Facade facade = new Facade();
    facade.marry();
}

享元模式

核心是共用。當A類方法里寫了一個方法,B類中需要同樣的方法就可以直接創建A類對象來調用方法或者通過一個方法工廠類收集各個方法,然後B類通過工廠類調用A類方法

舉例:通過享元工廠類實現共用方法

//A類
public class DBUtil {
    public void selectDB(){
        System.out.println("我是資料庫操作...");
    }
}

//享元工廠
public class DBUtilFactory {
    private static final DBUtil UTIL = new DBUtil();   //享元對象被存放在工廠中

    public static DBUtil getFlyweight(){   //獲取享元對象
        return UTIL;
    }
}

//B類
public class UserService {   //用戶服務

    public void service(){
        DBUtil util = DBUtilFactory.getFlyweight();   //通過享元工廠拿到DBUtil對象
        util.selectDB();    //該幹嘛幹嘛
    }
}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2 基本語法 2.1 JavaScript簡介 JavaScript 是一門解釋型語言,其代碼在客戶端中執行前不需經過編譯,而是直接由瀏覽器解釋執行。主要用作客戶端腳本語言,在瀏覽器中執行。但隨著Node.js的問世,JavaScript 也逐漸開始被用來編寫伺服器端程式。 JavaScript 不 ...
  • display , visibility 僅會改變元素顯示,不會改變元素種類。可以配合 js 使用使元素可以動態的顯示隱藏。 可以使用 display 或 visibility 實現html元素的隱藏功能,但是這兩種方式有不一樣的地方。display 中的 none 值隱藏,元素後不會在占據頁面空間 ...
  • 高階組件(HOC) 高階組件(Heigher Order Component)也被稱之為高階函數,容器組件,高階組件是類組件編程中的一種重要代碼邏輯復用技巧。 高階組件的語法 接收一個React組件作為入參,經過修飾,最後返回一個新的React組件,所以這個入參的React組件被稱之為“UI組件”; ...
  • 1 HTML定義 HTML(英文Hyper Text Markup Language的縮寫)中文譯為“超文本標簽語言”,主要是通過HTML標簽對網頁中的文本、圖片、聲音等內容進行描述。 <strong> 加粗字體 </strong> 2 HTML的骨架格式 <HTML> <head> <title> ...
  • 在React中上下文是一種通信方案。 上下文的特點 在組件樹中,是一種自上而下的單向數據流通信方案,數據只能從父組件註入,在子組件中訪問。 組件關係只要滿足“父組件-後代組件”這種關係時,都可以使用上下文通信。 在父組件中provide提供數據,在後代組件中註入並使用,這種通信不具有響應式,有點像v ...
  • 使用three.js(webgl)搭建智慧樓宇、設備檢測、數字孿生、物聯網3D、物業3D監控、物業基礎設施可視化運維、3D定位、三維室內定位、3d建築,3d消防,消防演習模擬,3d庫房,webGL,threejs,3d機房,bim管理系統 ...
  • HTTP 起源 HTTP 是由蒂姆·伯納斯-李(TimBerners—Lee)於1989年在歐洲核子研究組織(CERN)所發起 其中最著名的是 1999 年 6 月公佈的 RFC 2616,定義了 HTTP 協議中現今廣泛使用的一個版本——HTTP 1.1 HTTP 是什麼 全稱:超文本傳輸協議(H ...
  • 本文將探討一下,在多行文本情形下的一些有意思的文字動效。 多行文本,相對於單行文本,場景會複雜一些,但是在實際業務中,多行文本也是非常之多的,但是其效果處理比起單行文本會更困難。 單行與多行文本的漸隱 首先,我們來看這樣一個例子,我們要實現這樣一個單行文本的漸隱: 使用 mask,可以輕鬆實現這樣的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...