关于设计模式的说法,网上一搜一大堆,咱就不再去说了。
我的理解,设计模式就是很多NB的大佬们总结出来的,用来处理特定情况的标准解决方案。
那既然是标准方案,就一定会有套路,会有标准的样子。
几种常见的设计模式,都有各自的套路模板。下面进入主题,咱们一个一个来说。
一、抽象工厂模式
抽象工厂主要的场景是创建一系列相关或相似的对象。注意这个相关或相似。因为有这个特性,我们就可以抽取出共同点,来形成接口或抽象类,然后通过这个接口或抽象类,来派生出各个对象。
为什么叫抽象工厂?不知道,而且不重要。在我看来,这只是个名字,记下就好。一个东西出来,总要有个名字的,有个人跑出来说,就叫抽象工厂吧。于是就叫抽象工厂了。就像一个人,做了一个网站,让别人在上面卖东西,后来做大了,需要吹NB了,得给这个经营方式起个名字,于是,B2C 就出现了。现在人们创业做电商,头一件事先讨论做 B2B 还是 B2C。做反了。先做吧,挣钱要紧。做大了,你说叫什么,都会有人听、有人信。
说跑题了,:P
?
先写几个类型,包括接口和实体类,后面会用到:
- // 接口定义
- public interface ILaptop
- {
- void GetInfo();
- }
- public interface IMobile
- {
- void GetInfo();
- }
- // 实体类一
- public class MateBook : ILaptop
- {
- public void GetInfo()
- {
- Console.WriteLine("I am MateBook");
- }
- }
- public class Mac : ILaptop
- {
- public void GetInfo()
- {
- Console.WriteLine("I am Mac");
- }
- }
- // 实体类二
- 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();
- }
- }
调用:
- 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
- }
这个模式里面,核心的部分就是工厂接口的定义:
- public interface IFactory
- {
- ILaptop CreateLaptop();
- IMobile CreateMobile();
- }
在这个工厂接口中,加入了多个相似的接口。于是,调用端可以很简单的以类似的方式去调用,而工厂实体中,对内部引用的实体进行区分。一个典型的场景是:一个程序,对着很多种数据库。这样的情况,可以在工厂实体中区分具体连接哪种数据库,而内部的引用实体,则各自对应不同的数据库。外部调用时,只需要在初始化时确认使用哪种数据库,后面的 CRUD 操作,就直接使用就成,调用端不需要考虑数据库的区别。事实上,这也是抽象工厂用的最多的场景。
二、工厂模式
去掉了抽象两个字,居然还是一个模式,而且是一个不同的模式。这个名字起的够混乱。
不过老实说,工厂模式跟抽象工厂模式很像,区别是:抽象工厂模式是在工厂模式的基础上,又做了一层抽象。
看套路:
- 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
- }
看到了吧,跟抽象工厂确实很相似。不过在使用上,工厂模式用得会更多。任何类都可以按工厂模式来写。这个模式最大的作用,是把定义和实体做了分层。开发时,可以一部分人去定义接口,而另一部分人去实现这个接口。而且,工作模式可以随时扩展为抽象工厂。比方一开始只是可能有多种数据库,但具体是哪些数据库还没确定,就可以先按工厂模式写,等数据库定下来,随时就很容易转为抽象工厂了。
三、建造者模式
这个名称起的更不知所云了,就因为一个 Builder?
其实他说的是这么个事。我们经常看到这样的代码:
- var mobile = new MobileBuilder()
- .WithBrand("Apple")
- .WithModel("13Pro")
- .WithMemory("512G")
- .Build();
看着是不是很洋气?
来看看这个套路:
- // 这就是个数据模型
- 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;
- }
- }
然后就可以这样调用了:
- 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"}
- }
个人而言,我很喜欢这个套路,没有别的,就是洋气,非常的洋气。应用场景也非常多,所有数据的 DTO,都可以么写。
四、原型模式
这个模式听着会有点陌生。看过一些文章,也把它归为是创建型模式。实际上,我更倾向于把它看作是一种代码结构,而不是模式。这种结构最大的作用,是复制 - 通过复制一个存在的实例来创建新实例。
代码是这样的:
- 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;
- }
- }
看看,其实就是一段复制代码。
但是要注意,对于深拷贝和浅拷贝,涉及到指针和引用,如果你不熟悉,了解后再用。看一下上面的结果:
- 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"}}
- }
结果和你理解的是不是一样?如果不一样,去研究一下值和引用的区别。另外,C# 10 里新出来的 Record,就是一个典型的原型模式的类型,也可以了解一下。
五、单例模式
单例模式也是一个用处非常大的模式,而且这个名字起得挺直白。
单例模式,简单点说就是不管你 new 多少回,实际应用全局内存中只会有一份实例。
套路代码特别简单:
- 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;
- }
- }
这里有两个注意点:
类声明用到 sealed 关键字,以确保这个类不会被派生。
类构造函数用了 private,以确保这个类不会被 new。这本身与单例无关,只是通过这种方式来表明这是一个单例。控制单例的最核心的代码,其实是下面的 GetInstance() 方法。
调用时,就是下面一行代码:
- Singleton singleton = Singleton.GetInstance();
就OK了。
设计模式有很多种,对应的套路也有很多。其中,有一些是简单无脑的套路,像上面的单例,而另一些就会比较复杂。
不过,既然是套路,总是有固定的代码或结构可循的。
我这个主题,打算分几篇来写。这是第一篇。
最后做个小注解:套路虽简单,也要吃透了再用。而且,有时候简单的代码就能很好地完成任务,一定不要过度使用。