面向對象23種設計模式系列(二)- 結構型設計模式

来源:https://www.cnblogs.com/xyh9039/archive/2020/07/17/13301890.html
-Advertisement-
Play Games

本系列將和大家分享面向對象23種設計模式中常用的幾種設計模式,本章主要簡單介紹下結構型設計模式。 ...


結構型設計模式:關註類與類之間的關係。結構型設計模式的核心:組合優於繼承(橫向關係優於縱向關係)。

  縱向關係:繼承≈≈實現  超強關聯

  橫向關係:>組合>聚合>關聯>依賴

    依賴是出現在方法內部(在類A的方法內部對類B進行聲明和初始化)

    另外三個是用語義區分的,可能都是一個屬性

      Person類 有個大腦Header屬性  組合(同生共死)

      Person類 有個手/腳屬性   聚合(成人)

      Person類 有個iPhone屬性  關聯(非必須)   

多種結構型設計模式其實都是用組合包一層,然後加功能,解決不同的問題,然後有不同的側重點,也有不同的規範。

結構型設計模式(7個):適配器模式、代理模式、裝飾器模式、外觀模式、組合模式、橋接模式、享元模式。

下麵我們結合幾種具體場景來分析幾種結構性設計模式。

 1、適配器模式(Adapter Pattern)

解決重構的問題,新東西和舊系統不吻合,通過繼承/組合進行適配。

場景:程式中已經確定好數據訪問規範IDBHelper,我們的MySqlHelper、SqlServerHelper和OracleHelper都遵循了這個規範。隨著業務的變化,某一天我們的資料庫可能需要遷移到MongoDB,而它的數據訪問使用的是第三方的MongoDBHelper,其數據訪問規範和我們現有的IDBHelper規範不一致(沒有實現IDBHelper介面),但是呢我們又無法修改第三方的代碼。這時候我們就需要做相應的適配了。

程式原有代碼:

using System;

namespace AdapterPattern
{
    /// <summary>
    /// 數據訪問介面
    /// </summary>
    public interface IDBHelper
    {
        void Add<T>();
        void Delete<T>();
        void Update<T>();
        void Query<T>();
    }
}
using System;

namespace AdapterPattern
{
    /// <summary>
    /// MySql數據訪問
    /// </summary>
    public class MySqlHelper : IDBHelper
    {
        public void Add<T>()
        {
            Console.WriteLine("This is {0} Add", this.GetType().Name);
        }
        public void Delete<T>()
        {
            Console.WriteLine("This is {0} Delete", this.GetType().Name);
        }
        public void Update<T>()
        {
            Console.WriteLine("This is {0} Update", this.GetType().Name);
        }
        public void Query<T>()
        {
            Console.WriteLine("This is {0} Query", this.GetType().Name);
        }
    }
}
using System;

namespace AdapterPattern
{
    /// <summary>
    /// SqlServer數據訪問
    /// </summary>
    public class SqlServerHelper : IDBHelper
    {
        public void Add<T>()
        {
            Console.WriteLine("This is {0} Add", this.GetType().Name);
        }
        public void Delete<T>()
        {
            Console.WriteLine("This is {0} Delete", this.GetType().Name);
        }
        public void Update<T>()
        {
            Console.WriteLine("This is {0} Update", this.GetType().Name);
        }
        public void Query<T>()
        {
            Console.WriteLine("This is {0} Query", this.GetType().Name);
        }
    }
}
using System;

namespace AdapterPattern
{
    /// <summary>
    /// Oracle數據訪問
    /// </summary>
    public class OracleHelper : IDBHelper
    {
        public void Add<T>()
        {
            Console.WriteLine("This is {0} Add", this.GetType().Name);
        }
        public void Delete<T>()
        {
            Console.WriteLine("This is {0} Delete", this.GetType().Name);
        }
        public void Update<T>()
        {
            Console.WriteLine("This is {0} Update", this.GetType().Name);
        }
        public void Query<T>()
        {
            Console.WriteLine("This is {0} Query", this.GetType().Name);
        }
    }
}

第三方的MongoDBHelper

using System;

namespace AdapterPattern
{
    /// <summary>
    /// MongoDB數據訪問
    /// </summary>
    public class MongoDBHelper
    {
        public MongoDBHelper()
        {
            Console.WriteLine($"構造MongoDBHelper");
        }

        public void AddMongoDB<T>()
        {
            Console.WriteLine("This is {0} Add", this.GetType().Name);
        }
        public void DeleteMongoDB<T>()
        {
            Console.WriteLine("This is {0} Delete", this.GetType().Name);
        }
        public void UpdateMongoDB<T>()
        {
            Console.WriteLine("This is {0} Update", this.GetType().Name);
        }
        public void QueryMongoDB<T>()
        {
            Console.WriteLine("This is {0} Query", this.GetType().Name);
        }
    }
}

可以看出程式原有的規範和第三方的規範不一致,那我們如何去做適配呢?

方法1:類適配器模式

繼承:既滿足現有的規範調用,又沒有修改MongoDBHelper。

using System;

namespace AdapterPattern
{
    /// <summary>
    /// 類適配器模式
    /// 繼承 既滿足現有的規範調用,又沒有修改MongoDBHelper
    /// </summary>
    public class MongoDBHelperInherit : MongoDBHelper, IDBHelper
    {
        public MongoDBHelperInherit()
        {
            Console.WriteLine($"構造{this.GetType().Name}");
        }

        public void Add<T>()
        {
            base.AddMongoDB<T>();
        }
        public void Delete<T>()
        {
            base.DeleteMongoDB<T>();
        }
        public void Update<T>()
        {
            base.UpdateMongoDB<T>();
        }
        public void Query<T>()
        {
            base.QueryMongoDB<T>();
        }
    }
}

方法2:對象適配器模式

組合:既滿足現有的規範調用,又沒有修改MongoDBHelper。

using System;

namespace AdapterPattern
{
    /// <summary>
    /// 對象適配器模式
    /// 組合 既滿足現有的規範調用,又沒有修改MongoDBHelper
    /// </summary>
    public class MongoDBHelperCompose : IDBHelper
    {
        private MongoDBHelper _mongoDBHelper;
        private readonly MongoDBHelper _mongoDBHelper1 = new MongoDBHelper(); //1、屬性註入  聲明寫死  一定有

        /// <summary>
        /// 2、構造函數註入
        /// 可以替換(需要抽象)  _mongoDBHelper一定有值(不考慮其他構造函數)
        /// </summary>
        public MongoDBHelperCompose(MongoDBHelper mongoDBHelper)
        {
            _mongoDBHelper = mongoDBHelper;
            Console.WriteLine($"構造{this.GetType().Name}");
        }

        /// <summary>
        /// 3、方法註入 可以替換(需要抽象)  _mongoDBHelper不一定有值
        /// </summary>
        public void SetObject(MongoDBHelper mongoDBHelper)
        {
            _mongoDBHelper = mongoDBHelper;
        }

        public void Add<T>()
        {
            _mongoDBHelper.AddMongoDB<T>();
        }
        public void Delete<T>()
        {
            _mongoDBHelper.DeleteMongoDB<T>();
        }
        public void Update<T>()
        {
            _mongoDBHelper.UpdateMongoDB<T>();
        }
        public void Query<T>()
        {
            _mongoDBHelper.QueryMongoDB<T>();
        }
    }
}

那為什麼說組合優於繼承呢?

  二者都會先構造一個MongoDBHelper,但是繼承是強侵入,父類的東西子類必須有

  靈活性,繼承只為一個類服務;組合可以面向抽象為多個類型服務

2、代理模式(Proxy Pattern)

代理模式:

  包一層:沒有什麼技術問題是包一層不能解決的,如果有,就再包一層。

  vpn代理  翻牆代理  火車票代理。。。

  通過代理業務類去完成對真實業務類的調用,代理類不能增加業務功能。

  通過代理,能夠為對象擴展功能(不是增加業務)而不去修改原始業務類,也就是包了一層,我的地盤聽我的。比如來個日誌記錄,異常處理,可以避免修改業務類,只需要修改代理類。

場景:我們都知道如果要真實的去火車站買票則去火車站的路上需要發費一段時間,到了火車站可能查詢火車票也要發費一點時間,查詢到有票了這時候又需要排隊買票,又要發掉一段時間。為瞭解決這個問題,這個時候就有可能出現很多火車票代理點,這些代理點就可以代理查詢火車票和售票業務。代理點只負責代理相關業務,不進行增加業務,不會出現比如說火車站沒有提供選座業務,但是代理點卻增加了這個業務。下麵我們通過代碼來看下。

真實業務:

using System;

namespace ProxyPattern
{
    /// <summary>
    /// 售票業務介面
    /// </summary>
    public interface ISellTickets
    {
        bool GetSomething();
        void DoSomething();
    }
}
using System;
using System.Threading;

namespace ProxyPattern
{
    /// <summary>
    /// 真實售票業務類
    /// 一個耗時耗資源的對象方法
    /// 一個第三方封裝的類和方法
    /// </summary>
    public class RealSellTickets : ISellTickets
    {
        public RealSellTickets()
        {
            Thread.Sleep(2000);
            long lResult = 0;
            for (int i = 0; i < 100000000; i++)
            {
                lResult += i;
            }
            Console.WriteLine("RealSellTickets被構造。。。");
        }

        /// <summary>
        /// 火車站查詢火車票
        /// </summary>
        public bool GetSomething()
        {
            Console.WriteLine("坐車去火車站看看餘票信息。。。");
            Thread.Sleep(3000);
            Console.WriteLine("到火車站,看到是有票的");
            return true;
        }

        /// <summary>
        /// 火車站買票
        /// </summary>
        public void DoSomething()
        {
            Console.WriteLine("開始排隊。。。");
            Thread.Sleep(2000);
            Console.WriteLine("終於買到票了。。。");
        }
    }
}

代理類:

using System;
using System.Collections.Generic;

namespace ProxyPattern
{
    /// <summary>
    /// 售票業務代理類
    /// </summary>
    public class ProxySellTickets : ISellTickets
    {
        private readonly ISellTickets _sellTickets = new RealSellTickets();
        //private static ISellTickets _sellTickets = new RealSellTickets(); //構造函數耗時耗資源,可改成單例

        public void DoSomething()
        {
            try
            {
                Console.WriteLine("prepare DoSomething..."); //此處可進行擴展功能,比如來個日誌記錄,可以避免修改業務類,只需要修改代理類。
                _sellTickets.DoSomething(); //代理業務
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message); //此處可進行異常處理的擴展
                throw ex;
            }
        }

        private static Dictionary<string, bool> _proxyDictionary = new Dictionary<string, bool>(); //緩存處理
        public bool GetSomething()
        {
            try
            {
                Console.WriteLine("prepare GetSomething...");
                string key = "Proxy_GetSomething";
                bool bResult = false;
                if (!_proxyDictionary.ContainsKey(key))
                {
                    bResult = _sellTickets.GetSomething(); //代理業務
                    _proxyDictionary.Add(key, bResult);
                }
                else
                {
                    bResult = _proxyDictionary[key];
                }

                return bResult;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw ex;
            }
        }
    }
}

可以看出ProxySellTickets完成了對RealSellTickets的代理,其實就是包了一層。通過代理業務類去完成對真實業務類的調用,代理類不能增加業務功能,但是可以擴展一些功能,比如日誌記錄、緩存處理、異常處理等。

3、裝飾器模式(Decorator Pattern)

裝飾器模式:

  結構型設計模式巔峰之作,組合+繼承。

  通過組合+繼承,完成對象功能動態擴展。

場景:很多人應該都上過網課,知道不同的VIP學員可能有不同的學習許可權,比如:具有觀看視頻直播許可權的普通VIP學員、具有視頻回看許可權的VIP學員、具有課程免費升級的VIP學員,另外可能還有點評、鞏固練習等許可權。現在我們希望為不同的學員去定製不同的許可權,這些許可權的個數能隨意定製順序也能隨意定製,並且隨著業務的變化許可權類型可能還在不斷的增加,這時候我們又希望在不改變原有代碼的情況下還能去動態的擴展新的功能。這種場景就可以考慮使用裝飾器模式了。

下麵我們來重點看下代碼:

using System;

namespace DecoratorPattern
{
    /// <summary>
    /// 抽象學員
    /// </summary>
    public abstract class AbstractStudent
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public abstract void Study();
    }
}
using System;

namespace DecoratorPattern
{
    /// <summary>
    /// 一個普通的vip學員,學習vip課程
    /// </summary>
    public class StudentVip : AbstractStudent
    {
        public override void Study()
        {
            Console.WriteLine("{0} is a vip student studying .net Vip", base.Name);
        }
    }
}
using System;

namespace DecoratorPattern.Decorator
{
    /// <summary>
    /// 裝飾器的基類
    /// 繼承+組合
    /// 也是一個學員,繼承了抽象類
    /// </summary>
    public class BaseStudentDecorator : AbstractStudent
    {
        private readonly AbstractStudent _student; //用了組合加override
        public BaseStudentDecorator(AbstractStudent student)
        {
            this._student = student;
        }

        public override void Study()
        {
            this._student.Study();
            //Console.WriteLine("******************************");
            //基類裝飾器必須是個空的行為  會重覆
        }
    }
}
using System;

namespace DecoratorPattern.Decorator
{
    /// <summary>
    /// 鞏固練習
    /// </summary>
    public class StudentHomeworkDecorator : BaseStudentDecorator
    {
        public StudentHomeworkDecorator(AbstractStudent student)
           : base(student) //調用父類的構造函數
        {

        }

        public override void Study()
        {
            base.Study();
            Console.WriteLine("鞏固練習");
        }
    }
}
using System;

namespace DecoratorPattern.Decorator
{
    /// <summary>
    /// 點評
    /// 父類是BaseStudentDecorator,爺爺類是AbstractStudent
    /// </summary>
    public class StudentCommentDecorator : BaseStudentDecorator
    {
        public StudentCommentDecorator(AbstractStudent student)
           : base(student) //調用父類的構造函數
        {

        }

        public override void Study()
        {
            base.Study();
            Console.WriteLine("點評");
        }
    }
}
using System;

namespace DecoratorPattern.Decorator
{
    /// <summary>
    /// 視頻代碼回看
    /// </summary>
    public class StudentVideoDecorator : BaseStudentDecorator
    {
        public StudentVideoDecorator(AbstractStudent student)
           : base(student) //調用父類的構造函數
        {

        }

        public override void Study()
        {
            base.Study();
            Console.WriteLine("視頻代碼回看");
        }
    }
}
using System;

namespace DecoratorPattern.Decorator
{
    /// <summary>
    /// 課程免費升級
    /// </summary>
    public class StudentUpdateDecorator : BaseStudentDecorator
    {
        public StudentUpdateDecorator(AbstractStudent student)
           : base(student) //調用父類的構造函數
        {

        }

        public override void Study()
        {
            base.Study();
            Console.WriteLine("課程免費升級");
        }
    }
}

使用(核心):

using System;

using DecoratorPattern.Decorator;

namespace DecoratorPattern
{
    /// <summary>
    /// 裝飾器模式:結構型設計模式巔峰之作,組合+繼承。
    /// 通過組合+繼承,完成對象功能動態擴展。
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            {
                AbstractStudent student = new StudentVip()
                {
                    Id = 381,
                    Name = "隔壁老王"
                };
                student = new StudentHomeworkDecorator(student); //鞏固練習
                student = new StudentCommentDecorator(student); //點評
                student = new StudentVideoDecorator(student); //視頻代碼回看
                student = new StudentUpdateDecorator(student); //課程免費升級
                student.Study(); //先調用基類Study方法,再處理自己的邏輯
            }

            Console.ReadKey();
        }
    }
}

運行結果:

PS:裝飾器基類的Study方法中只能有代理業務,不能有別的,不然會重覆。

 

 Demo源碼:

鏈接:https://pan.baidu.com/s/1EACr1x3VkiNL-tMaqknDnw 
提取碼:ud7f

此文由博主精心撰寫轉載請保留此原文鏈接https://www.cnblogs.com/xyh9039/p/13301890.html

版權聲明:如有雷同純屬巧合,如有侵權請及時聯繫本人修改,謝謝!!!


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

-Advertisement-
Play Games
更多相關文章
  • 前言 每一個孩子都像星空中的一顆星星,散髮著自己所特有的光芒照亮著整個夜空。今天就帶大家用27行Python代碼繪製一幅滿天星吧。 全局設置 在繪製滿天星的過程中要運用到turtle工具,它是Python的標準庫,也可以形象的稱它為海龜庫,它可以描繪繪圖的軌跡,操作簡單、快捷。首先,我們要做一些有關 ...
  • 話說Laravel7便捷的字元串操作 用過Laravel的朋友都知道,Laravel內置的字元串處理函數有瞭解,Illuminate\Support\Str類。 Laravel 7 現在基於這些函數提供了一個更加面向對象的、更加流暢的字元串操作庫。你可以使用 String::of 創建一個 Illu ...
  • 一、新建項目示例使用IDEA快速創建基於SpringBoot的工程。springboot 2.3.1java 8WebFlux 必須選用Reactive的庫POM 依賴 org.springframework.boot spring-boot-starter-webflux 二、Controller... ...
  • 前言 Dubbo是一個分散式服務框架,致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。簡單的說,dubbo就是個服務框架,如果沒有分散式的需求,其實是不需要用的,只有在分散式的時候,才有dubbo這樣的分散式服務框架的需求,並且本質上是個服務調用的東東,說白了就是個遠程服務 ...
  • 條件語句 Go的條件語句和其它語言類似,主要是不支持三目運算符所以?:這種條件判斷是不支持的。Go提供的條件判斷語句主要有 if 還有 switch這兩種形式下麵是 if條件語句 if的幾種寫法,基本上和其它語言是一致的 if 條件 { } else { } if 條件 { } else if 條件 ...
  • 上次我們說到 微服務架構的前世今生(七):微服務架構生態體系 ,這次我們在說說微服務架構的技術支持。作者 哈嘍沃德先生,歡迎關註。 一、Spring Cloud Spring Cloud Netflix Eureka:服務註冊中心。 Spring Cloud Zookeeper:服務註冊中心。 Sp ...
  • 讀寫鎖實現邏輯相對比較複雜,但是卻是一個經常使用到的功能,希望將我對ReentrantReadWriteLock的源碼的理解記錄下來,可以對大家有幫助 前提條件 在理解ReentrantReadWriteLock時需要具備一些基本的知識 理解AQS的實現原理 之前有寫過一篇《深入淺出AQS源碼解析》 ...
  • 警告消息框主要是用來向用戶戶展示諸如警告、異常、完成和提示消息。一般實現的效果就是從系統視窗右下角彈出,然後加上些簡單的顯示和消失的動畫。 ###創建警告框視窗 首先我們創建一個警告框視窗(Form),將視窗設置為無邊框(FormBoderStyle=None),添加上圖片和內容顯示控制項 創建好警告 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...