值得永久收藏的 C# 設(shè)計(jì)模式套路
關(guān)于設(shè)計(jì)模式的說法,網(wǎng)上一搜一大堆,咱就不再去說了。
我的理解,設(shè)計(jì)模式就是很多NB的大佬們總結(jié)出來的,用來處理特定情況的標(biāo)準(zhǔn)解決方案。
那既然是標(biāo)準(zhǔn)方案,就一定會(huì)有套路,會(huì)有標(biāo)準(zhǔn)的樣子。
幾種常見的設(shè)計(jì)模式,都有各自的套路模板。下面進(jìn)入主題,咱們一個(gè)一個(gè)來說。
一、抽象工廠模式
抽象工廠主要的場(chǎng)景是創(chuàng)建一系列相關(guān)或相似的對(duì)象。注意這個(gè)相關(guān)或相似。因?yàn)橛羞@個(gè)特性,我們就可以抽取出共同點(diǎn),來形成接口或抽象類,然后通過這個(gè)接口或抽象類,來派生出各個(gè)對(duì)象。
為什么叫抽象工廠?不知道,而且不重要。在我看來,這只是個(gè)名字,記下就好。一個(gè)東西出來,總要有個(gè)名字的,有個(gè)人跑出來說,就叫抽象工廠吧。于是就叫抽象工廠了。就像一個(gè)人,做了一個(gè)網(wǎng)站,讓別人在上面賣東西,后來做大了,需要吹NB了,得給這個(gè)經(jīng)營方式起個(gè)名字,于是,B2C 就出現(xiàn)了?,F(xiàn)在人們創(chuàng)業(yè)做電商,頭一件事先討論做 B2B 還是 B2C。做反了。先做吧,掙錢要緊。做大了,你說叫什么,都會(huì)有人聽、有人信。
說跑題了,:P
?
先寫幾個(gè)類型,包括接口和實(shí)體類,后面會(huì)用到:
- // 接口定義
- public interface ILaptop
- {
- void GetInfo();
- }
- public interface IMobile
- {
- void GetInfo();
- }
- // 實(shí)體類一
- public class MateBook : ILaptop
- {
- public void GetInfo()
- {
- Console.WriteLine("I am MateBook");
- }
- }
- public class Mac : ILaptop
- {
- public void GetInfo()
- {
- Console.WriteLine("I am Mac");
- }
- }
- // 實(shí)體類二
- public class Mate : IMobile
- {
- public void GetInfo()
- {
- Console.WriteLine("I am Mate");
- }
- }
- public class IPhone : IMobile
- {
- public void GetInfo()
- {
- Console.WriteLine("I am IPhone");
- }
- }
有了上面的類型,我們來看看抽象工廠的套路。
定義:
- public interface IFactory
- {
- ILaptop CreateLaptop();
- IMobile CreateMobile();
- }
- public class FactoryA : IFactory
- {
- public ILaptop CreateLaptop()
- {
- return new MateBook();
- }
- public IMobile CreateMobile()
- {
- return new Mate();
- }
- }
- public class FactoryB : IFactory
- {
- public ILaptop CreateLaptop()
- {
- return new Mac();
- }
- public IMobile CreateMobile()
- {
- return new IPhone();
- }
- }
調(diào)用:
- public static class Example
- {
- public static void ExampleTest()
- {
- var factoryA = new FactoryA();
- var laptopA = factoryA.CreateLaptop();
- var mobileA = factoryA.CreateMobile();
- laptopA.GetName();
- mobileA.GetName();
- var factoryB = new FactoryB();
- var laptopB = factoryB.CreateLaptop();
- var mobileB = factoryB.CreateMobile();
- laptopB.GetName();
- mobileB.GetName();
- }
- //result :
- //I am MateBook
- //I am Mate
- //I am Mac
- //I am IPhone
- }
這個(gè)模式里面,核心的部分就是工廠接口的定義:
- public interface IFactory
- {
- ILaptop CreateLaptop();
- IMobile CreateMobile();
- }
在這個(gè)工廠接口中,加入了多個(gè)相似的接口。于是,調(diào)用端可以很簡(jiǎn)單的以類似的方式去調(diào)用,而工廠實(shí)體中,對(duì)內(nèi)部引用的實(shí)體進(jìn)行區(qū)分。一個(gè)典型的場(chǎng)景是:一個(gè)程序,對(duì)著很多種數(shù)據(jù)庫。這樣的情況,可以在工廠實(shí)體中區(qū)分具體連接哪種數(shù)據(jù)庫,而內(nèi)部的引用實(shí)體,則各自對(duì)應(yīng)不同的數(shù)據(jù)庫。外部調(diào)用時(shí),只需要在初始化時(shí)確認(rèn)使用哪種數(shù)據(jù)庫,后面的 CRUD 操作,就直接使用就成,調(diào)用端不需要考慮數(shù)據(jù)庫的區(qū)別。事實(shí)上,這也是抽象工廠用的最多的場(chǎng)景。
二、工廠模式
去掉了抽象兩個(gè)字,居然還是一個(gè)模式,而且是一個(gè)不同的模式。這個(gè)名字起的夠混亂。
不過老實(shí)說,工廠模式跟抽象工廠模式很像,區(qū)別是:抽象工廠模式是在工廠模式的基礎(chǔ)上,又做了一層抽象。
看套路:
- public interface IFactory
- {
- ILaptop CreateLaptop();
- }
- public class FactoryA : IFactory
- {
- public ILaptop CreateLaptop()
- {
- return new MateBook();
- }
- }
- public class FactoryB : IFactory
- {
- public ILaptop CreateLaptop()
- {
- return new Mac();
- }
- }
- public static class Example
- {
- public static void Test()
- {
- var factoryA = new FactoryA();
- var laptopA = factoryA.CreateLaptop();
- laptopA.GetName();
- var factoryB = new FactoryA();
- var laptopB = factoryB.CreateLaptop();
- laptopB.GetName();
- }
- //result :
- //I am MateBook
- //I am Mac
- }
看到了吧,跟抽象工廠確實(shí)很相似。不過在使用上,工廠模式用得會(huì)更多。任何類都可以按工廠模式來寫。這個(gè)模式最大的作用,是把定義和實(shí)體做了分層。開發(fā)時(shí),可以一部分人去定義接口,而另一部分人去實(shí)現(xiàn)這個(gè)接口。而且,工作模式可以隨時(shí)擴(kuò)展為抽象工廠。比方一開始只是可能有多種數(shù)據(jù)庫,但具體是哪些數(shù)據(jù)庫還沒確定,就可以先按工廠模式寫,等數(shù)據(jù)庫定下來,隨時(shí)就很容易轉(zhuǎn)為抽象工廠了。
三、建造者模式
這個(gè)名稱起的更不知所云了,就因?yàn)橐粋€(gè) Builder?
其實(shí)他說的是這么個(gè)事。我們經(jīng)??吹竭@樣的代碼:
- var mobile = new MobileBuilder()
- .WithBrand("Apple")
- .WithModel("13Pro")
- .WithMemory("512G")
- .Build();
看著是不是很洋氣?
來看看這個(gè)套路:
- // 這就是個(gè)數(shù)據(jù)模型
- public class Mobile
- {
- public string Brand { get; set; }
- public string Model { get; set; }
- public string Memory { get; set; }
- }
- // 這才是 Builder 的定義
- public class MobileBuilder
- {
- private readonly Mobile _mobile = new Mobile();
- public MobileBuilder WithBrand(string brand)
- {
- _mobile.Brand = brand;
- return this;
- }
- public MobileBuilder WithModel(string model)
- {
- _mobile.Model = model;
- return this;
- }
- public MobileBuilder WithMemory(string memory)
- {
- _mobile.Memory = memory;
- return this;
- }
- public Mobile Build()
- {
- return _mobile;
- }
- }
然后就可以這樣調(diào)用了:
- public static class Example
- {
- public static void Test()
- {
- var mobile = new MobileBuilder()
- .WithBrand("Apple")
- .WithModel("13Pro")
- .WithMemory("512G")
- .Build();
- Console.WriteLine(mobile.ToJson());
- }
- //result :
- //{"Brand":"Apple","Model":"13Pro","Memory":"512G"}
- }
個(gè)人而言,我很喜歡這個(gè)套路,沒有別的,就是洋氣,非常的洋氣。應(yīng)用場(chǎng)景也非常多,所有數(shù)據(jù)的 DTO,都可以么寫。
四、原型模式
這個(gè)模式聽著會(huì)有點(diǎn)陌生??催^一些文章,也把它歸為是創(chuàng)建型模式。實(shí)際上,我更傾向于把它看作是一種代碼結(jié)構(gòu),而不是模式。這種結(jié)構(gòu)最大的作用,是復(fù)制 - 通過復(fù)制一個(gè)存在的實(shí)例來創(chuàng)建新實(shí)例。
代碼是這樣的:
- public class MobilePackage
- {
- public string Color { get; set; }
- public Mobile Mobile { get; set; }
- // 下面才是模式代碼
- public MobilePackage ShallowCopy()
- {
- return (MobilePackage)this.MemberwiseClone();
- }
- public MobilePackage DeepCopy()
- {
- var clone = (MobilePackage)this.MemberwiseClone();
- clone.Color = new string(Color);
- clone.Mobile = new Mobile
- {
- Brand = new string(Mobile.Brand),
- Model = new string(Mobile.Model),
- Memory = new string(Mobile.Memory),
- };
- return clone;
- }
- }
看看,其實(shí)就是一段復(fù)制代碼。
但是要注意,對(duì)于深拷貝和淺拷貝,涉及到指針和引用,如果你不熟悉,了解后再用??匆幌律厦娴慕Y(jié)果:
- public static class Example
- {
- public static void Test()
- {
- var mobilePackage = new MobilePackage
- {
- Color = "White",
- Mobile = new Mobile
- {
- Brand = "Apple",
- Model = "13Pro",
- Memory = "512G",
- }
- };
- var shallowCopied = mobilePackage.ShallowCopy();
- var deepCopied = mobilePackage.DeepCopy();
- mobilePackage.Color = "Black";
- mobilePackage.Mobile.Brand = "Huawei";
- mobilePackage.Mobile.Model = "Mate";
- Console.WriteLine(mobilePackage.ToJson());
- Console.WriteLine(shallowCopied.ToJson());
- Console.WriteLine(deepCopied.ToJson());
- }
- //result:
- //{"Color":"Black","Mobile":{"Brand":"Huawei","Model":"Mate","Memory":"512G"}}
- //{"Color":"White","Mobile":{"Brand":"Huawei","Model":"Mate","Memory":"512G"}}
- //{"Color":"White","Mobile":{"Brand":"Apple","Model":"13Pro","Memory":"512G"}}
- }
結(jié)果和你理解的是不是一樣?如果不一樣,去研究一下值和引用的區(qū)別。另外,C# 10 里新出來的 Record,就是一個(gè)典型的原型模式的類型,也可以了解一下。
五、單例模式
單例模式也是一個(gè)用處非常大的模式,而且這個(gè)名字起得挺直白。
單例模式,簡(jiǎn)單點(diǎn)說就是不管你 new 多少回,實(shí)際應(yīng)用全局內(nèi)存中只會(huì)有一份實(shí)例。
套路代碼特別簡(jiǎn)單:
- public sealed class Singleton
- {
- private static Singleton _instance;
- private static readonly object _locker = new object();
- private Singleton()
- {
- }
- public static Singleton GetInstance()
- {
- if (_instance == null)
- {
- lock (_locker)
- {
- if (_instance == null)
- {
- _instance = new Singleton();
- }
- }
- }
- return _instance;
- }
- }
這里有兩個(gè)注意點(diǎn):
類聲明用到 sealed 關(guān)鍵字,以確保這個(gè)類不會(huì)被派生。
類構(gòu)造函數(shù)用了 private,以確保這個(gè)類不會(huì)被 new。這本身與單例無關(guān),只是通過這種方式來表明這是一個(gè)單例??刂茊卫淖詈诵牡拇a,其實(shí)是下面的 GetInstance() 方法。
調(diào)用時(shí),就是下面一行代碼:
- Singleton singleton = Singleton.GetInstance();
就OK了。
設(shè)計(jì)模式有很多種,對(duì)應(yīng)的套路也有很多。其中,有一些是簡(jiǎn)單無腦的套路,像上面的單例,而另一些就會(huì)比較復(fù)雜。
不過,既然是套路,總是有固定的代碼或結(jié)構(gòu)可循的。
我這個(gè)主題,打算分幾篇來寫。這是第一篇。
最后做個(gè)小注解:套路雖簡(jiǎn)單,也要吃透了再用。而且,有時(shí)候簡(jiǎn)單的代碼就能很好地完成任務(wù),一定不要過度使用。