學習設計模式《二十三》——橋接模式

一、基礎概念

????????橋接模式的本質是【分離抽象和實現】。

????????橋接模式的定義:將抽象部分與它的實現部分分離,使它們都可以獨立地變化。? ??

認識橋接模式
序號認識橋接模式說明
1什么是橋接通俗點說就是在不同的東西之間搭一個橋,讓它們能夠連接起來,可以相互通訊和使用。在橋接模式中是給什么東西搭橋呢?【是為被分離了的抽象部分和實現部分來搭橋】
?????????注意:在橋接模式中橋接是單向的,也就是只能是抽象部分的對象去使用具體實現部分的對象,而不能反過來,這就是單向橋。
2為何需要橋接為了達到讓抽象部分和實現部分都可以獨立變化的目的,在橋接模式中,是把抽象部分和實現部分分離開來。
????????雖然從程序結構上是分開了,但是抽象部分實現的時候,還是需要使用具體的實現,這可怎么辦?【抽象部分如何才能調用到具體實現部分的功能呢?】搭個橋就可以了,讓抽象部分通過這個橋就可以調用到實現部分的功能了,因此需要橋接。
3如何橋接只要讓抽象部分擁有實現部分的接口對象,就橋接上了,在抽象部分即可通過這個接口來調用具體實現部分的功能(即:橋接在程序上體現了在抽象部分擁有實現部分的接口對象,維護橋接就是維護這個關系)。
4獨立變化橋接模式的意圖是使得抽象和實現可以獨立變化,都可以分別擴充。也就是說抽象部分和實現部分是一種非常松散的關系。從某個角度來講,抽象部分和實現部分是可以完全分開的,獨立的,抽象部分不過是一個使用實現部分對外接口的程序罷了。
?????????如果這么看橋接模式的話,就類似于策略模式了。抽象部分需要根據某個策略,來選擇真實的實現,也就是說橋接模式的抽象部分相當于策略模式的上下文,更原始的就直接類似于面向接口編程,通過接口分離的兩個部分而已。但是別忘了,橋接模式的抽象部分,是可以繼續擴展和變化的,而策略模式只有上下文,是不存在所謂抽象部分的。
????????抽象和實現為什么還要組合在一起呢?原因是在抽象部分和實現部分還是存在內部聯系的,抽象部分的實現通常是需要調用實現部分的功能來實現的。
5動態變換功能由于橋接模式中的抽象部分和實現部分是完全分離的,因此可以在運行時動態組合具體的真實實現,從而達到動態變換功能的目的。
????????從另外一個角度看,抽象部分和實現部分沒有固定的綁定關系,因此同一個真實實現可以被不同的抽象對象使用;反過來,同一個抽象也可以有多個不同的實現。
6退化的橋接模式如果接口僅有一個實現,那么就沒有必要創建接口了,這是一種橋接模式退化的情況(即:抽象類和接口是一對一的關系,雖然如此,但還是要保持它們的分離狀態,這樣,它們才不會相互影響,才可以分別擴展)
7橋接模式和繼承繼承是擴展對象功能的一種常見手段,通常情況下,繼承擴展的功能變化緯度都是一緯的,也就是變化的因素只有一類。
????????對于出現變化因素有兩類:也就是有兩個變化緯度的情況,繼承實現就會比較痛苦。從理論上來說,如果用繼承的方式來實現這種有兩個變化緯度的情況,最后實際的實現類應該是兩個維度上可變數量的乘積那么多個。如果要在任何一個緯度上進行擴展,都需要實現另外一個緯度上的可變數量那么多個實現類,這也是為何會感覺擴展起來很困難;且隨著程序規模的加大,會越來越難以擴展和維護。
????????【橋接模式】就是用來解決這種有兩個變化緯度的情況下,如果靈活地擴展功能的一個很好的方案,其實,橋接模式主要是把繼承改成了使用對象組合,從而把兩個維度分開,讓每一個緯度單獨去變化,最后通過對象組合的方式,把兩個維度組合起來,每一種組合的方式就相當于原來繼承中的一種實現,這樣就有效地減少了實際實現的類的個數【理論上,如果使用橋接模式的方式來實現這種有兩種變化緯度的情況,最后實際的實現類應該是兩個緯度上可變數量的和】。? ? ? ??
誰來橋接
序號誰來橋接說明
1由客戶端來負責創建接口對象,并在創建抽象類對象的時候,把它設置到抽象部分的對象中去。
2可以在抽象部分對象構建的時候,由抽象部分的對象自己來創建相應的接口對象,也可以給它傳遞一些參數,根據參數來選擇并創建具體的接口對象。
3可以在抽象類中選擇并創建一個默認的接口對象,然后子類可以根據需要改變這個實現。
4也可以使用抽象工廠或者簡單工廠來選擇并創建具體的接口對象,抽象部分的類可以通過調用工廠的方法來獲取接口對象。
5如果使用IOC/DI容器的話,還可以通過IOC/DI容器來創建具體的接口對象,并注入會到抽象類中。
橋接模式的優點
序號橋接模式的優點
1分離抽象和實現部分:橋接模式分離了抽象部分和實現部分,從而及大地提高了系統的靈活性。讓抽象部分和實現部分獨立開來,分別定義接口,這有助于對系統進行分層,從而產生更好的結構化的系統。對于系統的高層部分,只需要知道抽象部分和實現部分的接口就可以了。
2更好的擴展性:由于橋接模式把抽象部分和實現部分分離開了,而且分別定義接口,這就使得抽象部分和實現部分可以分別獨立地擴展,而不會相互影響,從而大大提高了系統的可擴展性。
3可動態地切換實現:由于橋接模式把抽象部分和實現部分分離開了,所以在實現橋接的時候,就可以實現動態的選擇和使用具體的實現。也就是說一個實現不再是固定的綁定在一個抽象接口上了,可以實現在運行期間動態地切換。
4可以減少子類的個數:對于兩個變化緯度的情況,如果采用繼承的實現方式,大約需要在兩個緯度上的可變化數量的乘積個子類;而采用橋接模式來實現,大約需要兩個緯度上的可變化數量的和個子類。可以明顯的減少子類的個數。
思考橋接模式
序號說明
1橋接模式的本質是:分離抽象和實現;橋接模式最重要的工作就是分離抽象部分和實現部分,這是解決問題的關鍵。只有把抽象部分和實現部分分離開了,才能夠讓它們獨立地變化;只有抽象部分和實現部分可以獨立地變化,系統才會有更好的可擴展性和可維護性(還有其他好處如:可以動態地切換實現、可以減少子類個數等)。
2

對設計原則的體現:

????????《1》橋接模式很好地實現了開閉原則(通常應用橋接模式的地方,抽象部分和實現部分都是可變化的,也就是應用會有兩個變化緯度,橋接模式就是找到這兩個變化,并分別封裝起來,從而合理地實現OCP)。在使用橋接模式的時候,通常情況下,頂層的抽象類和接口是不變的,而繼承抽象類的具體類是可變的。由于抽象類是通過接口來操作具體的實現類,因此具體的實現類是可以擴展的,并根據需要可以有多個具體的實現。
????????《2》橋接模式還很好地體現了:多用對象組合,少用對象繼承(如果使用繼承來擴展功能,不但讓對象之間有很強的耦合性,而且會需要很多的子類才能夠完成相應的功能,需要兩個緯度上的可變化數量的乘積個子類。而采用對象的組合,松散了對象之間的耦合性,不但使每個對象變得簡單和可維護,還極大減少了子類的個數,大約要兩個緯度上可變化數量的和個子類)。

何時選用橋接模式?
?????????1、如果不希望在抽象部分和實現部分采用固定的綁定關系,可以采用橋接模式,來把抽象部分和實現部分分開,然后在程序運行期間來動態地設置抽象部分需要用到的具體的實現,還可以動態地切換具體的實現。
?????????2、如果出現抽象部分和實現部分都能夠擴展的情況,可以采用橋接模式,讓抽象部分和實現部分獨立地變化,從而靈活地進行單獨擴展,而不是攪在一起,擴展一邊會影響到另一邊。
????????3、如果希望實現部分的修改不會對客戶產生影響,可以采用橋接模式。由于客戶是面向抽象的接口在運行,實現部分的修改可以獨立于抽象部分,并不會對客戶產生影響,也可以說對客戶是透明的。
????????4、如果采用繼承的方案,會導致產生很多子類,對于這種情況,可以考慮采用橋接模式,分析功能變化的原因,看看能否分離不同的緯度,然后通過橋接模式來分離它們,從而減少子類的數目。

二、橋接模式示例

????????業務需求:發送提示消息(如某人有新的工作了,需要發送一條消息提示他)。從業務上看,消息由分為普通消息、加急消息和特急消息多種,不同的消息類型,業務功能處理是不一樣的。如:加急消息是在消息上添加加急,而特急消息除了添加特急外,還會做一條催促的記錄,多久不完成就會繼續催促;從發送消息的手段上看:有系統內消息、手機短信消息、郵件消息等。

?2.1、不使用模式的示例

? 2.1.1、實現簡化版本

????????我們先實現一個簡單版本(如:消息只是實現發送普通消息,發送方式只是實現系統內消息和郵件)由于發送普通消息會有兩種不同的實現方式,為了讓外部能夠統一操作,因此,把消息設計為接口,然后由兩個不同的實現類分別實現系統內消息方式和郵件發送消息方式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 消息統一接口/// </summary>internal interface IMessage{/// <summary>/// 發送消息/// </summary>/// <param name="message">需要發生的消息</param>/// <param name="toUser">消息要發送給的人員</param>void Send(string message,string toUser);}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 站內消息的方式發送普通消息/// </summary>internal class CommonMsgSMS : IMessage{public void Send(string message, string toUser){Console.WriteLine($"現在使用【站內消息方式】發送消息【{message}】給【{toUser}】");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 使用Email的方式發送普通消息/// </summary>internal class CommonMsgEmail : IMessage{public void Send(string message, string toUser){Console.WriteLine($"現在使用【Email方式】發送消息【{message}】給【{toUser}】");}}//Class_end
}

? 2.1.2、實現加急發送消息

? ? ? ? 加急發送消息的實現不同于普通消息,需要在消息前加上加息,然后在發送消息;另外加急消息會提供監控的方法,讓客戶端可以隨時通過這個方法來了解對于加急消息的處理進度(如:相應的人員是否接收到這個消息,相應的工作是否已經開展等)因此加急消息需要擴展新的接口,除了實現基本的發送消息功能外,還需要添加監控功能:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 加急消息接口/// </summary>internal interface IUrgencyMessage:IMessage{/// <summary>/// 監控某消息的處理/// </summary>/// <param name="messageId">被監控消息的編號</param>/// <returns>返回監控到的數據對象</returns>object Watch(string messageId);}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 站內消息的方式發送加急消息/// </summary>internal class UrgencyMsgSMS : IUrgencyMessage{public void Send(string message, string toUser){message = $"[加急] {message}";Console.WriteLine($"現在使用【站內消息方式】發送消息【{message}】給【{toUser}】");}public object Watch(string messageId){//獲取相應的數據,組織成為監控的數據對象,然后返回return null;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 使用Email的方式發送加急消息/// </summary>internal class UrgencyMsgEmail : IUrgencyMessage{public void Send(string message, string toUser){message = $"[加急] {message}";Console.WriteLine($"現在使用【Email方式】發送消息【{message}】給【{toUser}】");}public object Watch(string messageId){//獲取相應的數據,組織成為監控的數據對象,然后返回return null;}}//Class_end
}

? 2.1.3、客戶端測試

namespace BridgePattern
{internal class Program{static void Main(string[] args){NoPatternTest();Console.ReadLine();}/// <summary>/// 不使用模式的示例/// </summary>private static void NoPatternTest(){Console.WriteLine("------不使用模式的示例------");//使用站內消息方式發送【普通】消息NoPattern.IMessage message = new NoPattern.CommonMsgSMS();message.Send("請你吃飯", "張三");//使用站內消息方式發送【加急】消息message = new NoPattern.UrgencyMsgSMS();message.Send("請你吃飯", "張三");Console.WriteLine();//使用郵件的方式發送【普通】消息message = new NoPattern.CommonMsgEmail();message.Send("請你吃飯", "張三");//使用郵件的方式發送【加急】消息message = new NoPattern.UrgencyMsgEmail();message.Send("請你吃飯", "張三");}}//Class_end
}

? 2.1.4、運行結果

這個示例是滿足了基本的功能要求,可是這么實現好不好呢?有沒有什么問題呢?

????????通過繼承來擴展的實現方式,有個明顯的缺點:擴展消息的種類不太容易;不同類型的消息具有不同的業務,也就是有不同的實現,在這種情況下,每個種類的消息,需要實現所有不同的消息發送方式。更可怕的是,如果要新加入一種消息的發送方式,那么會要求所有的消息種類都要加入這種新的發送方式的實現。要是考慮業務功能上再擴展一下呢?(如:群發消息,也就是一次可以發送多條消息)就意味著很多地方都要修改,這樣的實現很明顯是不靈活的

?2.2、橋接模式示例

????????橋接模式就是用來解決上述問題的,將抽象部分與它的實現部分分離,使得它們都可以獨立的變化。仔細分析上面的示例要求,示例的變化具有兩個緯度,一個緯度是抽象的消息(包含普通消息、加急消息、特急消息);另一個緯度是具體的消息發送方式(包含:站內消息、Email消息、手機短信消息)這幾個方式是平等的,可被切換方式。這兩個緯度一共組合出9種可能性,如下圖所示:

????????出現問題的根本原因是:在與消息的抽象和實現是混合在一起的,這就導致了一個緯度的變化會引起另一個緯度進行相應的變化,從而使得程序擴展起來非常困難。要想解決這個問題,就必須把這兩個緯度分開(即:將抽象部分和實現部分分開,讓它們相互獨立,這樣就可以實現獨立的變化,使擴展變得簡單)。

? ? ? ? 橋接模式通過引入實現的接口,把實現部分從系統中分離出去。那么,抽象這邊如何使用具體的實現呢?肯定是用面向實現的接口來編程,為了讓抽象這邊能夠很方便地與實現結合起來,把頂層的抽象接口改成抽象類,在其中持有一個具體的實現部分的實例。這樣一來,對于需要發送消息的客戶端來說,只需要創建相應的消息對象,然后調用這個消息對象的方法就可以了,這個消息對象會調用持有的真正消息發送法師來把消息發送出去(也就是說:客戶端只是想要發送消息而已,并不想關心具體如何發送)。

? 2.2.1、實現簡單功能

我們先從簡單的功能開始,實現普通消息和加急消息功能。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 發送消息的統一接口/// </summary>internal interface IMessage{/// <summary>/// 發送消息/// </summary>/// <param name="message">要發送的消息內容</param>/// <param name="toUser">消息發送給的人員</param>void Send(string message,string toUser);}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 抽象的消息對象/// </summary>internal class AbstractMessage{//持有一個實現消息的對象protected IMessage message;/// <summary>/// 構造函數/// </summary>/// <param name="message">實現消息的對象</param>public AbstractMessage(IMessage message){this.message = message;}/// <summary>/// 發送消息/// </summary>/// <param name="message">需要發送的消息內容</param>/// <param name="toUser">消息發送的給的人員</param>public virtual void SendMsg(string message,string toUser){this.message.Send(message, toUser);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 站內消息/// </summary>internal class MsgSMS : IMessage{public void Send(string message, string toUser){Console.WriteLine($"使用站內消息的方式,發送消息【{message}】給【{toUser}】");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 使用Email方式發送消息/// </summary>internal class MsgEmail : IMessage{public void Send(string message, string toUser){Console.WriteLine($"使用Email消息的方式,發送消息【{message}】給【{toUser}】");}}//Class_end
}

接下來就是擴展抽象消息接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 普通消息/// </summary>internal class CommonMsg : AbstractMessage{public CommonMsg(IMessage message) : base(message){}public override void SendMsg(string message, string toUser){//【普通消息】直接調用父類方法,把消息發送出去就可以了base.SendMsg(message, toUser);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 加急消息/// </summary>internal class UrgencyMsg : AbstractMessage{public UrgencyMsg(IMessage message) : base(message){}public override void SendMsg(string message, string toUser){message = $"[加急] {message}";base.SendMsg(message, toUser);}/// <summary>/// 擴展新功能(監控消息的處理過程)/// </summary>/// <param name="messageId">被監控的消息的編號</param>/// <returns>返回被監控到的數據對象</returns>public object Watch(string messageId){//獲取相應的數據,組織成監控的數據對象,然后返回return null;}}//Class_end
}

? 2.2.2、添加新功能

????????上面已經使用橋接模式實現了2種消息發送方式和2種消息類型消息;現在來看一下能夠解決前面提出的問題,我們通過新添加還未實現的功能來看看(即:新添加特急消息處理;新增加使用手機發送消息的方式)該如何實現?

我們只需要在抽象部分新添加一個特急消息類,擴展抽象消息就可以把特急消息的處理功能加入系統中了;對于新增手機發送消息的方式也簡單,只需要在新增一個類實現手機發送消息的方式即可。

《1》新增特急消息處理類

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 特急消息/// </summary>internal class SpecialUrgencyMsg : AbstractMessage{public SpecialUrgencyMsg(IMessage message) : base(message){}public override void SendMsg(string message, string toUser){message = $"[特急] {message}";base.SendMsg(message, toUser);}public void Hurry(string messageId){//執行催促的業務,發出催促消息}}//Class_end
}

《2》新增的手機發送消息方式功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 手機短信的方式發送消息/// </summary>internal class MsgMobile : IMessage{public void Send(string message, string toUser){Console.WriteLine($"使用手機短信的方式,發送消息【{message}】給【{toUser}】");}}//Class_end
}

? 2.2.3、客戶端測試

namespace BridgePattern
{internal class Program{static void Main(string[] args){BridgeDemoOneTest();Console.ReadLine();}/// <summary>/// 橋接模式示例1/// </summary>private static void BridgeDemoOneTest(){Console.WriteLine("------橋接模式示例1------");/*把發送消息實現方式切換為站內信*///創建具體的實現對象BridgeDemoOne.IMessage messageStyle = new BridgeDemoOne.MsgSMS();//創建一個普通的消息對象BridgeDemoOne.AbstractMessage abstarctMessage=new BridgeDemoOne.CommonMsg(messageStyle);abstarctMessage.SendMsg("請你吃飯","張三");//創建一個緊急消息對象abstarctMessage=new BridgeDemoOne.UrgencyMsg(messageStyle);abstarctMessage.SendMsg("請你吃飯","張三");//創建一個特急消息對象abstarctMessage = new BridgeDemoOne.SpecialUrgencyMsg(messageStyle);abstarctMessage.SendMsg("請你吃飯", "張三");Console.WriteLine();/*把發送消息實現方式切換為郵件*///創建具體的實現對象messageStyle=new BridgeDemoOne.MsgEmail();//創建一個普通的消息對象abstarctMessage = new BridgeDemoOne.CommonMsg(messageStyle);abstarctMessage.SendMsg("請你吃飯", "張三");//創建一個緊急消息對象abstarctMessage = new BridgeDemoOne.UrgencyMsg(messageStyle);abstarctMessage.SendMsg("請你吃飯", "張三");//創建一個特急消息對象abstarctMessage = new BridgeDemoOne.SpecialUrgencyMsg(messageStyle);abstarctMessage.SendMsg("請你吃飯", "張三");}}//Class_end
}

? 2.2.4、運行結果

? 2.2.5、誰來橋接

《1》由抽象部分的對象自己來創建相應的對象

這種情況又分為兩種實現:一種是需要外部傳入參數,另一種是不需要外部傳入參數:

①外部傳入參數:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 抽象的消息對象/// </summary>internal class AbstractMessage2{//持有一個實現部分的對象protected IMessage message;public AbstractMessage2(int type){switch (type){case 1:message = new MsgSMS();break;case 2:message = new MsgEmail();break;case 3:message = new MsgMobile();break;default:break;}}/// <summary>/// 發送消息/// </summary>/// <param name="message">需要發送的消息內容</param>/// <param name="toUser">消息發送的給的人員</param>public virtual void SendMsg(string message, string toUser){this.message.Send(message, toUser);}}//Class_end
}

②不需要外部傳入參數

????????這種不需要外部傳入參數的情況,那就說明在抽象類中,有可能在抽象類的構造函數中選擇;也有可能在具體的方法中選擇。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 抽象的消息對象/// </summary>internal class AbstractMessage3{//持有一個實現部分的對象protected IMessage message;public AbstractMessage3(){}/// <summary>/// 發送消息/// </summary>/// <param name="message">需要發送的消息內容</param>/// <param name="toUser">消息發送的給的人員</param>public virtual void SendMsg(string message, string toUser){this.message.Send(message, toUser);}//根據消息的長度來選擇合適的實現protected IMessage GetImpl(string message){IMessage msg = null;if (string.IsNullOrEmpty(message)){//若沒有任何消息則默認使用站內消息msg = new MsgSMS();}else if (message.Length < 100){//如消息長度在100以內,則使用手機短信msg = new MsgMobile();}else if (message.Length < 1000){//如消息長度在100-1000以內,則使用站內消息msg = new MsgSMS();}else{//如消息長度在1000以上,則使用Emailmsg = new MsgEmail();}return msg;}}//Class_end
}

《2》在抽象類的構造函數中創建默認實現對象

????????直接在抽象類的構造方法中,創建一個默認的實現對象,然后子類根據需要,可以選擇直接使用還是覆蓋掉。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{internal class AbstractMessage4{//持有一個實現消息的對象protected IMessage message;/// <summary>/// 構造函數/// </summary>public AbstractMessage4(){//創建一個默認的實現this.message = new MsgSMS();}/// <summary>/// 發送消息/// </summary>/// <param name="message">需要發送的消息內容</param>/// <param name="toUser">消息發送的給的人員</param>public virtual void SendMsg(string message, string toUser){this.message.Send(message, toUser);}}//Class_end
}

三、項目源碼工程

kafeiweimei/Learning_DesignPattern: 這是一個關于C#語言編寫的基礎設計模式項目工程,方便學習理解常見的26種設計模式https://github.com/kafeiweimei/Learning_DesignPattern

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/93977.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/93977.shtml
英文地址,請注明出處:http://en.pswp.cn/web/93977.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用Python 創建虛擬環境的兩種方式

使用Python 創建虛擬環境的兩種方式&#xff1a; 方式一&#xff1a;使用官方標準庫 venv (Python 3.3 推薦) 創建&#xff1a; # 語法&#xff1a;python -m venv <虛擬環境名稱> python -m venv my_project_env指定Python解釋器版本&#xff08;如果你的系統有多個Pyth…

Android 開發問題:android:marginTop=“20px“ 屬性不生效

android:marginTop"20px"在 Android 開發中&#xff0c;XML 布局文件中&#xff0c;上述屬性不生效 問題原因 margin 系列的屬性需要加上 layout_ 前綴layout_marginTop&#xff1a;頂部邊距layout_marginBottom&#xff1a;底部邊距layout_marginLeft&#xff1a;左…

【P18 3-10】OpenCV Python—— 鼠標控制,鼠標回調函數(鼠標移動、按下、。。。),鼠標繪制基本圖形(直線、圓、矩形)

P18 3-10 1 鼠標回調函數2 鼠標繪制基本圖形&#xff08;直線、圓、矩形&#xff09;2.1 圖形繪制教程2.2 鼠標繪制基本圖形&#xff08;直線、圓、矩形&#xff09;代碼實現1 鼠標回調函數 import cv2 import numpy as npdef mouse_callback(event,x,y,flage,userdata):print(…

微服務如何集成swagger3

文章目錄引言一、項目結構二、頂級pom依賴準備三、common-swagger模塊四、gateway模塊配置五、結果演示引言 我們在用springboot開發應用時&#xff0c;經常使用swagger來作為我們的接口文檔可視化工具&#xff0c;方便前端同事調用&#xff0c;集成也是比較簡單的&#xff0c…

特種行業許可證識別技術:通過圖像處理、OCR和結構化提取,實現高效、準確的許可證核驗與管理

在酒店、娛樂場所、典當行、危化品經營等特種行業管理中&#xff0c;許可證是合法經營的“生命線”。傳統人工核驗方式效率低下、易出錯&#xff0c;且難以應對海量數據和復雜偽造手段。特種行業許可證識別技術應運而生&#xff0c;成為智慧監管和優化服務的關鍵工具。特種行業…

零售行業新店網絡零接觸部署場景下,如何選擇SDWAN

一家連鎖超市在新疆偏遠地區的新店開業申請網絡專線&#xff0c;市政審批和架設電線桿的流程花了半個月&#xff0c;成本高企——而它的競爭對手在隔壁新店部署SD-WAN&#xff0c;從開箱到業務上線僅用了10分鐘。近年來&#xff0c;零售企業瘋狂擴張與下沉市場的趨勢愈演愈烈。…

python發布文章和同步文章到社區的工具小腳本

在開發過程中&#xff0c;開發者們往往需要頻繁地在社區中分享文章、解決方案以及技術文章來交流與成長。為了簡化這一過程&#xff0c;我將為你們介紹兩個基于Python腳本的自動化工具&#xff0c;可以幫助你發布文章到開發者社區&#xff0c;提高效率。一、從Markdown文件批量…

23.CNN系列網絡思考

為什么是卷積、池化的交替進行? 卷積做的是特征提取,池化做的是一種降采樣。 早期學習的主要是:低級特征(邊緣、角點、紋理、顏色)。這些特征分布相對局部且空間位置信息很重要。 卷積將這些特征學習出來,然后通過池化降采樣,突出其位置信息。然后再卷積進行學習池化后…

MySQL 8.x的性能優化文檔整理

一、內存與緩沖優化 # InnoDB緩沖池&#xff08;內存的60%-80%&#xff09; innodb_buffer_pool_size 12G # 核心參數 innodb_buffer_pool_instances 8 # 8核CPU建議分8個實例# 日志緩沖區與Redo日志 innodb_log_buffer_size 256M # 事務日志緩沖區 innodb_log_…

個人使用AI開發的《PlSqlRewrite4GaussDB(PLSQL自動轉換工具)1.0.1 BETA》發布

個人使用AI開發的《PlSqlRewrite4GaussDB(PLSQL自動轉換工具)1.0.1 BETA》發布 前言 基于語法樹的SQL自動改寫工具開發系列&#xff08;1&#xff09;-離線安裝語法樹解析工具antlr4 基于語法樹的SQL自動改寫工具開發系列&#xff08;2&#xff09;-使用PYTHON進行簡單SQL改寫…

python的校園研招網系統

前端開發框架:vue.js 數據庫 mysql 版本不限 后端語言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 數據庫工具&#xff1a;Navicat/SQLyog等都可以 摘要&…

如何高效撰寫AI領域學術論文——學習筆記

最開始寫的時候最好仿照著頂會來寫1. 標題(Title)?標題是論文的"門面"&#xff0c;需要同時具備簡潔性和信息量&#xff1a;采用"XX方法 for XXX任務"的標準格式&#xff0c;包含核心創新點和應用領域&#xff0c;避免使用模糊詞匯&#xff0c;力求精準&a…

elasticsearch8.12.0安裝分詞

上篇說到&#xff0c;安裝了es后正常運行es分詞下載地址從 GitHub Release 下載&#xff08;推薦&#xff09; &#x1f449; https://github.com/medcl/elasticsearch-analysis-ik/releases或https://release.infinilabs.com/analysis-ik/stable/安裝&#xff1a;選擇與你 ES …

強化學習算法分類與介紹(含權重更新公式)

強化學習算法種類豐富&#xff0c;可按學習目標&#xff08;基于價值 / 基于策略 / 演員 - 評論家&#xff09;、數據使用方式&#xff08;在線 / 離線&#xff09;、是否依賴環境模型&#xff08;無模型 / 有模型&#xff09;等維度分類。以下按核心邏輯梳理常見算法&#xff…

基于STM32F103單片機智能門禁熱釋人體感應報警設計

1 系統功能介紹 本設計基于 STM32F103C8T6 單片機&#xff0c;通過多種傳感器、執行器以及通信模塊實現智能門禁和安防報警功能。其主要功能是檢測門外人員情況&#xff0c;結合環境光照強度判斷是否需要照明&#xff0c;同時結合 GSM 模塊在異常情況下發送報警信息&#xff0c…

imx6ull-驅動開發篇33——platform 平臺驅動模型

目錄 Linux 驅動的分離與分層 驅動的分隔與分離 驅動的分層 platform 平臺驅動模型 platform 總線 bus_type 結構體 platform 總線 platform_match函數 platform 驅動 platform_driver 結構體 device_driver 結構體 platform_driver_register 函數 platform_drive…

Win/Linux筆記本合蓋不睡眠設置指南

在 筆記本電腦上&#xff0c;當你合上屏幕時&#xff0c;默認系統可能會進入“睡眠”或“休眠”狀態。如果你希望合上屏幕時系統繼續正常運行&#xff08;例如后臺下載、運行程序、遠程訪問等&#xff09;&#xff0c;需要修改系統的電源設置。 一、以下是 Windows 10 / Windo…

(棧)Leetcode155最小棧+739每日溫度

739. 每日溫度 - 力扣&#xff08;LeetCode&#xff09; while要把stack的判斷放在前面&#xff0c;否則stack[-1]可能報錯 class Solution(object):def dailyTemperatures(self, temperatures):""":type temperatures: List[int]:rtype: List[int]""…

【NLP(01)】NLP(自然語言處理)基礎

目錄NLP基礎一、基本概念1. 自然語言處理的基本介紹1.1 與語言相關的概念1.2 為什么使用NLP2. NLP的應用方向2.1 **自然語言理解**2.2 自然語言轉換2.3 自然語言生成3. NLP基礎概念4. NLP的發展歷史5. NLP的基本流程二、NLP中的特征工程0. 引入1. 詞向量2. 傳統NLP中的特征工程…

Python工程師進階學習道路分析

本文將分為以下幾個核心部分&#xff1a; 心態與基礎重塑&#xff1a;從“會用”到“精通”核心語言深度&#xff1a;窺探Python的奧秘編程范式與設計模式&#xff1a;寫出優雅的代碼并發與異步編程&#xff1a;釋放多核時代的威力性能分析與優化&#xff1a;讓代碼飛起來深入…