- A+
前言:相信作为程序开发,或多或少都接触甚至使用过设计模式,甚至对于有些设计模式的概念都已经很熟悉了,但是在实际开发项目的时候是否有使用过这些模式呢,可能比较少甚至没有。有些设计模式确实在架构中更实用一些,这也是部分原因。但不管怎样,最起码常用的几种设计模式还是需要了解的,本文介绍几种常见的设计模式,希望读者能从中有所收获并学以致用。
什么是设计模式
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码 可复用性 、 可维护性 、可读性、稳健性以及安全性的解决方案。
假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式。
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
- 对接口编程而不是对实现编程——依赖倒置原则
- 优先使用对象组合而不是继承——合成复用原则。
设计模式的六大原则:
- 单一职责原则(Single Responsibility Principle)。职责清晰
- 里氏替换原则(Liskov Substitution Principle)——任何使用基类的地方,都可以透明的使用其子类
- 迪米特法则 (Law Of Demeter)—— 一个对象应该对其他对象保持最少的了解,即高聚合低耦合
- 依赖倒置原则(Dependence Inversion Principle)—— 依赖抽象,而不是依赖细节
- 接口隔离原则(Interface Segregation Principle)—— 客户端不应该依赖它不需要的接口; 一个类对另一个类的依赖应该建立在最小的接口上;
- 开闭原则 (Open Closed Principle) —— 对扩展开发,对修改关闭
创建型模式
单例模式(Singleton Pattern)
单例模式想必大家都已经耳熟能详了,这是很常见的一种设计模式,也是最简单的一种设计模式。它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
使用单例模式的实例:.NET Core依赖注入生命周期中的Singleton。生命周期为Singleton的服务全局唯一,每次调用都是调用的同一个服务.
单例模式有两种写法,懒汉式和饿汉式。
懒汉式,顾名思义,比较懒,一开始的时候不会创建对象,要用到了才会想起来去创建对象,写法如下:
//懒汉式单例写法 public class SingletonPattern { private static SingletonPattern _singletonInstance; //构造函数私有化是关键 private SingletonPattern() { } //双重检验,提升性能 public static SingletonPattern GetInstance(bool useLock = true) { if (_singletonInstance is null) { lock (singleton_lock) { if (_singletonInstance is null) { _singletonInstance = new SingletonPattern(); } } } return _singletonInstance; } }
注意:懒汉式要考虑多线程安全问题,这里使用双重检验锁以确保线程安全并提升性能
饿汉式比较简单,只需要做个是否已经创建的判断即可,写法如下:
//饿汉式单例写法 public class SingletonPattern { private static SingletonPattern _singletonInstance = new SingletonPattern(); //构造函数私有化是关键 private SingletonPattern() { } public static SingletonPattern GetInstance(bool useLock = true) { if (_singletonInstance is null) { _singletonInstance = new SingletonPattern(); } return _singletonInstance; } }
该模式的适用场景:
- 一个全局类,频繁创建和销毁,如服务类、工具类等
- 需要控制实例数量,节约系统资源的时候
原型模式(Prototype Pattern)
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
原型模式的难点在于对对象的克隆,如果对象比较复杂,嵌套的属性对象比较多,自己实现克隆方法会比较麻烦。不过还好目前主流语言都支持深度克隆对象,如Java的Serializable, javascript的cloneDeep等等,深度克隆也可以通过序列化和反序列化来实现,因此支持序列化和反序列化的语言都可以用这种方式实现深度克隆。
原型的实现方式与单例相差不大,重点是返回的实例是克隆对象
public class PrototypePattern { private static PrototypePattern _protetypeInstance = new PrototypePattern(); private PrototypePattern() { } public static PrototypePattern GetInstance() { //重点在于返回克隆的对象 PrototypePattern clone = GetDeepCloneObj(); return clone; } }
原型适用场景:
- 对象创建复杂,消耗资源大,又需要重复创建类似对象
工厂模式(Factory Pattern)
工厂模式在GOF的设计模式中分为工厂方法和抽象工厂,实际上简单工厂、工厂方法和抽象工厂的分类更为普遍一些。
简单工厂模式(Sinple Factory Pattern)
简单工厂主要隔离了使用者和产品,使用者需要使用产品时,直接向工厂请求,而不用知道具体产品的实现。也就是前面说的依赖倒置原则!
上图可以看出,用户要创建一个产品,不需要知道产品的具体实现,只需要知道创建产品的工厂即可。实现代码如下:
public class SimpleFactoryPattern { public static IRunner CreateRunner(PatternEnum pattern) { switch (pattern) { case PatternEnum.Singleton: return new SingletonRunner(); case PatternEnum.Prototype: return new PrototypeRunner(); case PatternEnum.Factory_Method: return new FactoryMethodRunner(); case PatternEnum.Abstract_Factory: return new AbstractFactoryRunner(); default: return null; } } } //使用 class Program { static void Main(string[] args) { var pattern = PatternEnum.Abstract_Factory; var prototype = SimpleFactoryPattern.CreateRunner(pattern); prototype.Run(); Console.ReadKey(); } }
简单工厂适用于比较简单的情况下,可以屏蔽对象创建细节,对于对象来说符合开闭原则。但是对于工厂来说并不符合开闭原则,因为当需要新增一个对象时,需要修改工厂的内容。另外当需要生成的对象过多时或者经常需要修改时,该模式就显得不够用了。
这时候就需要使用工厂方法了
工厂方法模式(Factory Method Pattern)
如上图,工厂方法相比于简单工厂,改变在于将具体的工厂也屏蔽了,用户不需要知道需要用什么工厂,只要调用抽象工厂的方法即可。实现如下:
//抽象工厂 public abstract class MounseFactoryMethod { public abstract IMouse CreateMouse(); } //具体实现工厂 public class DellMouseFactory : MounseFactoryMethod { public override IMouse CreateMouse() { return new DellMouse(); } } //具体实现工厂 public class HpMouseFactory : MounseFactoryMethod { public override IMouse CreateMouse() { return new HpMouse(); } } //当具体的实现工厂太多时,可以结合简单工厂,利用简单工厂创建具体工厂 //也可以利用反射创建工厂 public class MouseFactory { public static MounseFactoryMethod CreateMouseFactory(BrandEnum brand) { switch (brand) { case BrandEnum.Dell: return new DellMouseFactory(); case BrandEnum.Hp: return new HpMouseFactory(); default: return null; } } } //使用 class Program { static void Main(string[] args) { var mouseFactory = MouseFactory.CreateMouseFactory(BrandEnum.Dell); var mouse = mouseFactory.CreateMouse(); mouse.Click(); } }
工厂方法的工厂是符合开闭原则的,当有新产品时,只需要添加新的工厂即可,不需要改动已有工厂代码。
在上面的代码中,我们抽象了一个鼠标生产工厂MounseFactoryMethod,两个具体生产工厂DellMouseFactory 和HpMouseFactory 。另外还额外使用了一个简单工厂MouseFactory来选择要使用的具体工厂。
工厂方法在实际使用中比较常见,当需要创建的对象种类比较多且新增或删除比较频繁时,工厂方法是不错的选择。
抽象工厂(Abstract Factory)
首先了解下产品族的概念:
如上图,拥有相同特性的产品称为一个产品等级,同一产品平台的不同产品称为一个产品族。
抽象工厂用于处理比较复杂的产品。举个例子,上述工厂方法中的MounseFactoryMethod专门用于生产鼠标,而DellMouseFactory 和HpMouseFactory 则分别用于生产戴尔鼠标和惠普鼠标,它们都是一个产品等级的产品。当我们不仅需要生产鼠标,还要生成键盘时,光是一个MounseFactoryMethod已经不能满足生产需要,这时候工厂生产的就不只是单一产品,而是一个产品族。类图如下:
上图中,我们定义了一个ComputerFactory,这个工厂能够生产更丰富的产品(鼠标和键盘),戴尔和惠普分别有独立的工厂生产自己的鼠标和键盘。
像上图这种,生产产品族的工厂称为抽象工厂。
抽象工厂和工厂方法的区别在于,工厂方法只能生产单一产品,也就是产品接口只有一个,而抽象工厂的产品可能是来自不同的接口。
实现代码如下:
public abstract class ComputerAbstractFactory { public abstract IMouse CreateMouse(); public abstract IKeyboard CreateKeyboard(); } public class DellAbstractFactory : ComputerAbstractFactory { public override IKeyboard CreateKeyboard() { return new DellKeyboard(); } public override IMouse CreateMouse() { return new DellMouse(); } } public class HpAbstractFactory : ComputerAbstractFactory { public override IKeyboard CreateKeyboard() { return new HpKeyboard(); } public override IMouse CreateMouse() { return new HpMouse(); } } //使用 class Program { static void Main(string[] args) { Console.WriteLine("使用抽象工厂 DellAbstractFactory 创建产品"); var dellFactory = new DellAbstractFactory(); dellFactory.CreateKeyboard().Click(); dellFactory.CreateMouse().Click(); Console.WriteLine("使用抽象工厂 HpAbstractFactory 创建产品"); var hpFactory = new HpAbstractFactory(); hpFactory.CreateKeyboard().Click(); hpFactory.CreateMouse().Click(); } }
工厂模式总结
- 简单工厂不符合开闭原则,仅使用于产品种类少、修改不频繁的情况
- 工厂方法符合开闭原则,但只适用单一产品等级的情况
- 抽象工厂符合开闭原则,适用于生产产品族的情况