值得永久收藏的 C# 设计模式套路

时间:2022-04-02 13:15:50

值得永久收藏的 C# 设计模式套路

关于设计模式的说法,网上一搜一大堆,咱就不再去说了。

我的理解,设计模式就是很多NB的大佬们总结出来的,用来处理特定情况的标准解决方案。

那既然是标准方案,就一定会有套路,会有标准的样子。

几种常见的设计模式,都有各自的套路模板。下面进入主题,咱们一个一个来说。

一、抽象工厂模式

抽象工厂主要的场景是创建一系列相关或相似的对象。注意这个相关或相似。因为有这个特性,我们就可以抽取出共同点,来形成接口或抽象类,然后通过这个接口或抽象类,来派生出各个对象。

为什么叫抽象工厂?不知道,而且不重要。在我看来,这只是个名字,记下就好。一个东西出来,总要有个名字的,有个人跑出来说,就叫抽象工厂吧。于是就叫抽象工厂了。就像一个人,做了一个网站,让别人在上面卖东西,后来做大了,需要吹NB了,得给这个经营方式起个名字,于是,B2C 就出现了。现在人们创业做电商,头一件事先讨论做 B2B 还是 B2C。做反了。先做吧,挣钱要紧。做大了,你说叫什么,都会有人听、有人信。

说跑题了,:P

?

先写几个类型,包括接口和实体类,后面会用到:

  1. // 接口定义
  2. public interface ILaptop
  3. {
  4. void GetInfo();
  5. }
  6. public interface IMobile
  7. {
  8. void GetInfo();
  9. }
  10. // 实体类一
  11. public class MateBook : ILaptop
  12. {
  13. public void GetInfo()
  14. {
  15. Console.WriteLine("I am MateBook");
  16. }
  17. }
  18. public class Mac : ILaptop
  19. {
  20. public void GetInfo()
  21. {
  22. Console.WriteLine("I am Mac");
  23. }
  24. }
  25. // 实体类二
  26. public class Mate : IMobile
  27. {
  28. public void GetInfo()
  29. {
  30. Console.WriteLine("I am Mate");
  31. }
  32. }
  33. public class IPhone : IMobile
  34. {
  35. public void GetInfo()
  36. {
  37. Console.WriteLine("I am IPhone");
  38. }
  39. }

有了上面的类型,我们来看看抽象工厂的套路。

定义:

  1. public interface IFactory
  2. {
  3. ILaptop CreateLaptop();
  4. IMobile CreateMobile();
  5. }
  6. public class FactoryA : IFactory
  7. {
  8. public ILaptop CreateLaptop()
  9. {
  10. return new MateBook();
  11. }
  12. public IMobile CreateMobile()
  13. {
  14. return new Mate();
  15. }
  16. }
  17. public class FactoryB : IFactory
  18. {
  19. public ILaptop CreateLaptop()
  20. {
  21. return new Mac();
  22. }
  23. public IMobile CreateMobile()
  24. {
  25. return new IPhone();
  26. }
  27. }

调用:

  1. public static class Example
  2. {
  3. public static void ExampleTest()
  4. {
  5. var factoryA = new FactoryA();
  6. var laptopA = factoryA.CreateLaptop();
  7. var mobileA = factoryA.CreateMobile();
  8. laptopA.GetName();
  9. mobileA.GetName();
  10. var factoryB = new FactoryB();
  11. var laptopB = factoryB.CreateLaptop();
  12. var mobileB = factoryB.CreateMobile();
  13. laptopB.GetName();
  14. mobileB.GetName();
  15. }
  16. //result :
  17. //I am MateBook
  18. //I am Mate
  19. //I am Mac
  20. //I am IPhone
  21. }

这个模式里面,核心的部分就是工厂接口的定义:

  1. public interface IFactory
  2. {
  3. ILaptop CreateLaptop();
  4. IMobile CreateMobile();
  5. }

在这个工厂接口中,加入了多个相似的接口。于是,调用端可以很简单的以类似的方式去调用,而工厂实体中,对内部引用的实体进行区分。一个典型的场景是:一个程序,对着很多种数据库。这样的情况,可以在工厂实体中区分具体连接哪种数据库,而内部的引用实体,则各自对应不同的数据库。外部调用时,只需要在初始化时确认使用哪种数据库,后面的 CRUD 操作,就直接使用就成,调用端不需要考虑数据库的区别。事实上,这也是抽象工厂用的最多的场景。

二、工厂模式

去掉了抽象两个字,居然还是一个模式,而且是一个不同的模式。这个名字起的够混乱。

不过老实说,工厂模式跟抽象工厂模式很像,区别是:抽象工厂模式是在工厂模式的基础上,又做了一层抽象。

看套路:

  1. public interface IFactory
  2. {
  3. ILaptop CreateLaptop();
  4. }
  5. public class FactoryA : IFactory
  6. {
  7. public ILaptop CreateLaptop()
  8. {
  9. return new MateBook();
  10. }
  11. }
  12. public class FactoryB : IFactory
  13. {
  14. public ILaptop CreateLaptop()
  15. {
  16. return new Mac();
  17. }
  18. }
  19. public static class Example
  20. {
  21. public static void Test()
  22. {
  23. var factoryA = new FactoryA();
  24. var laptopA = factoryA.CreateLaptop();
  25. laptopA.GetName();
  26. var factoryB = new FactoryA();
  27. var laptopB = factoryB.CreateLaptop();
  28. laptopB.GetName();
  29. }
  30. //result :
  31. //I am MateBook
  32. //I am Mac
  33. }

看到了吧,跟抽象工厂确实很相似。不过在使用上,工厂模式用得会更多。任何类都可以按工厂模式来写。这个模式最大的作用,是把定义和实体做了分层。开发时,可以一部分人去定义接口,而另一部分人去实现这个接口。而且,工作模式可以随时扩展为抽象工厂。比方一开始只是可能有多种数据库,但具体是哪些数据库还没确定,就可以先按工厂模式写,等数据库定下来,随时就很容易转为抽象工厂了。

三、建造者模式

这个名称起的更不知所云了,就因为一个 Builder?

其实他说的是这么个事。我们经常看到这样的代码:

  1. var mobile = new MobileBuilder()
  2. .WithBrand("Apple")
  3. .WithModel("13Pro")
  4. .WithMemory("512G")
  5. .Build();

看着是不是很洋气?

来看看这个套路:

  1. // 这就是个数据模型
  2. public class Mobile
  3. {
  4. public string Brand { get; set; }
  5. public string Model { get; set; }
  6. public string Memory { get; set; }
  7. }
  8. // 这才是 Builder 的定义
  9. public class MobileBuilder
  10. {
  11. private readonly Mobile _mobile = new Mobile();
  12. public MobileBuilder WithBrand(string brand)
  13. {
  14. _mobile.Brand = brand;
  15. return this;
  16. }
  17. public MobileBuilder WithModel(string model)
  18. {
  19. _mobile.Model = model;
  20. return this;
  21. }
  22. public MobileBuilder WithMemory(string memory)
  23. {
  24. _mobile.Memory = memory;
  25. return this;
  26. }
  27. public Mobile Build()
  28. {
  29. return _mobile;
  30. }
  31. }

然后就可以这样调用了:

  1. public static class Example
  2. {
  3. public static void Test()
  4. {
  5. var mobile = new MobileBuilder()
  6. .WithBrand("Apple")
  7. .WithModel("13Pro")
  8. .WithMemory("512G")
  9. .Build();
  10. Console.WriteLine(mobile.ToJson());
  11. }
  12. //result :
  13. //{"Brand":"Apple","Model":"13Pro","Memory":"512G"}
  14. }

个人而言,我很喜欢这个套路,没有别的,就是洋气,非常的洋气。应用场景也非常多,所有数据的 DTO,都可以么写。

四、原型模式

这个模式听着会有点陌生。看过一些文章,也把它归为是创建型模式。实际上,我更倾向于把它看作是一种代码结构,而不是模式。这种结构最大的作用,是复制 - 通过复制一个存在的实例来创建新实例。

代码是这样的:

  1. public class MobilePackage
  2. {
  3. public string Color { get; set; }
  4. public Mobile Mobile { get; set; }
  5. // 下面才是模式代码
  6. public MobilePackage ShallowCopy()
  7. {
  8. return (MobilePackage)this.MemberwiseClone();
  9. }
  10. public MobilePackage DeepCopy()
  11. {
  12. var clone = (MobilePackage)this.MemberwiseClone();
  13. clone.Color = new string(Color);
  14. clone.Mobile = new Mobile
  15. {
  16. Brand = new string(Mobile.Brand),
  17. Model = new string(Mobile.Model),
  18. Memory = new string(Mobile.Memory),
  19. };
  20. return clone;
  21. }
  22. }

看看,其实就是一段复制代码。

但是要注意,对于深拷贝和浅拷贝,涉及到指针和引用,如果你不熟悉,了解后再用。看一下上面的结果:

  1. public static class Example
  2. {
  3. public static void Test()
  4. {
  5. var mobilePackage = new MobilePackage
  6. {
  7. Color = "White",
  8. Mobile = new Mobile
  9. {
  10. Brand = "Apple",
  11. Model = "13Pro",
  12. Memory = "512G",
  13. }
  14. };
  15. var shallowCopied = mobilePackage.ShallowCopy();
  16. var deepCopied = mobilePackage.DeepCopy();
  17. mobilePackage.Color = "Black";
  18. mobilePackage.Mobile.Brand = "Huawei";
  19. mobilePackage.Mobile.Model = "Mate";
  20. Console.WriteLine(mobilePackage.ToJson());
  21. Console.WriteLine(shallowCopied.ToJson());
  22. Console.WriteLine(deepCopied.ToJson());
  23. }
  24. //result:
  25. //{"Color":"Black","Mobile":{"Brand":"Huawei","Model":"Mate","Memory":"512G"}}
  26. //{"Color":"White","Mobile":{"Brand":"Huawei","Model":"Mate","Memory":"512G"}}
  27. //{"Color":"White","Mobile":{"Brand":"Apple","Model":"13Pro","Memory":"512G"}}
  28. }

结果和你理解的是不是一样?如果不一样,去研究一下值和引用的区别。另外,C# 10 里新出来的 Record,就是一个典型的原型模式的类型,也可以了解一下。

五、单例模式

单例模式也是一个用处非常大的模式,而且这个名字起得挺直白。

单例模式,简单点说就是不管你 new 多少回,实际应用全局内存中只会有一份实例。

套路代码特别简单:

  1. public sealed class Singleton
  2. {
  3. private static Singleton _instance;
  4. private static readonly object _locker = new object();
  5. private Singleton()
  6. {
  7. }
  8. public static Singleton GetInstance()
  9. {
  10. if (_instance == null)
  11. {
  12. lock (_locker)
  13. {
  14. if (_instance == null)
  15. {
  16. _instance = new Singleton();
  17. }
  18. }
  19. }
  20. return _instance;
  21. }
  22. }

这里有两个注意点:

类声明用到 sealed 关键字,以确保这个类不会被派生。

类构造函数用了 private,以确保这个类不会被 new。这本身与单例无关,只是通过这种方式来表明这是一个单例。控制单例的最核心的代码,其实是下面的 GetInstance() 方法。

调用时,就是下面一行代码:

  1. Singleton singleton = Singleton.GetInstance();

就OK了。

设计模式有很多种,对应的套路也有很多。其中,有一些是简单无脑的套路,像上面的单例,而另一些就会比较复杂。

不过,既然是套路,总是有固定的代码或结构可循的。

我这个主题,打算分几篇来写。这是第一篇。

最后做个小注解:套路虽简单,也要吃透了再用。而且,有时候简单的代码就能很好地完成任务,一定不要过度使用。

原文链接:https://mp.weixin.qq.com/s/2l29xFsf5l2_OwomEEv9Xw