设计模式详解之创建型模式——单例、原型和工厂

  • 设计模式详解之创建型模式——单例、原型和工厂已关闭评论
  • 173 次浏览
  • A+
所属分类:.NET技术
摘要

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码 可复用性 、 可维护性 、可读性、稳健性以及安全性的解决方案。

前言:相信作为程序开发,或多或少都接触甚至使用过设计模式,甚至对于有些设计模式的概念都已经很熟悉了,但是在实际开发项目的时候是否有使用过这些模式呢,可能比较少甚至没有。有些设计模式确实在架构中更实用一些,这也是部分原因。但不管怎样,最起码常用的几种设计模式还是需要了解的,本文介绍几种常见的设计模式,希望读者能从中有所收获并学以致用。

什么是设计模式

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码 可复用性 、 可维护性 、可读性、稳健性以及安全性的解决方案。

假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式。

在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。

  • 对接口编程而不是对实现编程——依赖倒置原则
  • 优先使用对象组合而不是继承——合成复用原则。

设计模式的六大原则:

  1. 单一职责原则(Single Responsibility Principle)。职责清晰
  2. 里氏替换原则(Liskov Substitution Principle)——任何使用基类的地方,都可以透明的使用其子类
  3. 迪米特法则 (Law Of Demeter)—— 一个对象应该对其他对象保持最少的了解,即高聚合低耦合
  4. 依赖倒置原则(Dependence Inversion Principle)—— 依赖抽象,而不是依赖细节
  5. 接口隔离原则(Interface Segregation Principle)—— 客户端不应该依赖它不需要的接口; 一个类对另一个类的依赖应该建立在最小的接口上;
  6. 开闭原则 (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();                   } } 

工厂模式总结

  1. 简单工厂不符合开闭原则,仅使用于产品种类少、修改不频繁的情况
  2. 工厂方法符合开闭原则,但只适用单一产品等级的情况
  3. 抽象工厂符合开闭原则,适用于生产产品族的情况