之前我们有讲过六大设计原则,分别为【单一职责】【里氏替换】【 迪米特法则】【依赖倒置原则】【接口隔离原则】【开闭原则】,这些只能叫原则,叫建议,而并没有实际的招数。
那今天我们来讨论一下23种设计模式。设计模式就是:面向对象语言开发过程中,遇到的种种场景和问题,然后提出了解决方案和思路。
沉淀下来的就变成了设计模式,解决具体问题的具体招数,其实说白了就是套路,即是我们站在前辈的肩膀来解决问题。
设计模式分为三大类型:
1:创建型设计模式:关注对象的创建
2:结构型设计模式:关注类与类之间的关系
3:行为型设计模式:关注对象和行为的分离
接下来我们分别来讲讲一下这三类设计模式
一:创建性设计模式
看到这个很多人会想,对象的创建难道不就是new()一下,然后就能解决的吗?其实不然,这里面有很多套路,他包含:【单例模式】【原型】【简单工厂】【工厂方法】【抽象工厂】【建造者模式】等
1:单例模式:主要应用于构造对象耗时好资源,且很多地方都需要去new,想要避免重复构造的时候可以使用单例模式
A:怎么创建单例?
1:构造函数私有化,避免别人私有化
2:公开的静态方法提供对象的实例
3:全局唯一静态,重用这个变量,保证全局都是这一个
B:单例虽然限制了对象的创建,重用了对象,但是单例是不具有多线程安全性的,所以我们可以通过以下三种方式创建
1:静态变量初始化new
2:静态构造函数中new
3:使用lock加锁new
因为构造一个类的时候,首先先创建静态字段,然后再执行静态构造函数,所以使用静态变量或者静态构造函数进行创建时,只要你使用这个类就会创建这个对象,然后会常驻内存,这些称为饿汗单例,使用lock加锁的称为懒汉单例
第一种声明如下:懒汉式单例模式,使用lock加锁new
namespace SingletonPattern { /// <summary> /// 单例类:一个构造对象很耗时耗资源类型 /// 懒汉式单例模式 /// </summary> public class Singleton { /// <summary> /// 构造函数耗时耗资源 /// </summary> private Singleton() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine("{0}被构造一次", this.GetType().Name); } /// <summary> /// 3 全局唯一静态 重用这个变量 /// volatile 促进线程安全 让线程按顺序操作 /// </summary> private static volatile Singleton _Singleton = null; //因为单例会有线程安全问题,所以会加锁的操作 private static readonly object Singleton_Lock = new object(); /// <summary> /// 2 公开的静态方法提供对象实例 /// 双重if加锁会提高性能 /// </summary> /// <returns></returns> public static Singleton CreateInstance() { if (_Singleton == null)//是_Singleton已经被初始化之后,就不要进入锁等待了 { lock (Singleton_Lock) //保证任意时刻只有一个线程进入lock范围 //也限制了并发,尤其是_Singleton已经被初始化之后 { if (_Singleton == null)//保证只实例化一次 { _Singleton = new Singleton(); } } } return _Singleton; } //既然是单例,大家用的是同一个对象,用的是同一个方法, //如果并发还有线程安全问题,所以要保证线程安全必须lock加锁 public int iTotal = 0; } }
第二种声明如下:静态构造函数
namespace SingletonPattern { /// <summary> /// 单例类:一个构造对象很耗时耗资源类型 /// /// 饿汉式 /// </summary> public class SingletonSecond { private static SingletonSecond _SingletonSecond = null; /// <summary> /// 1 构造函数耗时耗资源 /// </summary> private SingletonSecond() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被构造一次", this.GetType().Name); } /// <summary> /// 静态构造函数:由CLR保证,程序第一次使用这个类型前被调用,且只调用一次 /// 检测,初始化 /// 写日志功能的文件夹检测 /// XML配置文件 /// </summary> static SingletonSecond() { _SingletonSecond = new SingletonSecond(); Console.WriteLine("SingletonSecond 被启动"); } public static SingletonSecond CreateInstance() { return _SingletonSecond; }//饿汉式 只要使用类就会被构造 } }
第三种声明如下:静态变量初始化时走私有构造函数
namespace SingletonPattern { /// <summary> /// 单例类:一个构造对象很耗时耗资源类型 /// 饿汉式 /// </summary> public class SingletonThird { /// <summary> /// 构造函数耗时耗资源 /// </summary> private SingletonThird() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(1000); Console.WriteLine("{0}被构造一次", this.GetType().Name); } /// <summary> /// 静态字段:在第一次使用这个类之前,由CLR保证,初始化且只初始化一次 /// 这个比构造函数还早 /// </summary> private static SingletonThird _SingletonThird = new SingletonThird(); public static SingletonThird CreateInstance() { return _SingletonThird; }//饿汉式 只要使用类就会被构造 } }
C:单例一般运用的场景
单例是保证全局唯一的一个实例,主要是应对一些特殊情况,比如数据库连接池(内置资源),再比如:流水号或者订单号生成器,使用同一个变量保证初始值是同一个!
2:原型设计模式
原型设计模式是在单例的基础上面升级了一下,然后把对象从内存的层面复制了一下,然后返回一个新的对象,但是又不走构造函数
注意:原型模式是一个新的对象,新的地址,但是copy仅仅是浅克隆,而不是深克隆,所以有时候需要注意一下
A:原型模式的声明如下:
namespace SingletonPattern { /// <summary> /// 原型模式:单例的基础上升级了一下,把对象从内存层面复制了一下, /// 然后返回是个新对象,但是又不是new出来的(不走构造函数) /// </summary> public class Prototype { /// <summary> /// 构造函数耗时耗资源 /// </summary> private Prototype() { long lResult = 0; for (int i = 0; i < 10000000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine("{0}被构造一次", this.GetType().Name); } /// <summary> /// 3 全局唯一静态 重用这个变量 /// </summary> private static volatile Prototype _Prototype = new Prototype(); /// <summary> /// 2 公开的静态方法提供对象实例 /// </summary> /// <returns></returns> public static Prototype CreateInstance() { Prototype prototype = (Prototype)_Prototype.MemberwiseClone(); return prototype; } } }
B:什么是深克隆,浅克隆
深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将被复制。如下图所示:
下面附上一张深克隆的代码以后需要的话,可以直接拿来用:
浅克隆:当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。如下图所示:
C:原型模式适用的场景
1:创建新对象成本较大(例如初始化时间长,占用CPU多或占太多网络资源),新对象可以通过复制已有对象来获得,如果相似对象,则可以对其成员变量稍作修改。
2:系统要保存对象的状态,而对象的状态很小。
3:需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的组合状态,通过复制原型对象得到新实例可以比使用构造函数创建一个新实例更加方便。
3:简单工厂(不属于23种设计模式)
不直接new,然后把对象的创建转移到工厂中,这种是细节没有消失,只是转移了矛盾,并没有消除矛盾,而是把矛盾集中在同一个工厂中
创建一个简单工厂的实例如下:
1 public interface IRace 2 { 3 /// <summary> 4 /// show出王者 5 /// </summary> 6 void ShowKing(); 7 } 8 9 /// <summary> 10 /// War3种族之一 11 /// </summary> 12 public class Human : IRace 13 { 14 public Human(int id, DateTime dateTime, string reamrk) 15 { } 16 public Human() 17 { } 18 19 public void ShowKing() 20 { 21 Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Sky"); 22 } 23 } 24 25 /// <summary> 26 /// War3种族之一 27 /// </summary> 28 public class Undead : IRace 29 { 30 public void ShowKing() 31 { 32 Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "GoStop"); 33 } 34 } 35 36 /// <summary> 37 /// War3种族之一 38 /// </summary> 39 public class ORC : IRace 40 { 41 public void ShowKing() 42 { 43 Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Grubby"); 44 } 45 } 46 /// <summary> 47 /// War3种族之一 48 /// </summary> 49 public class NE : IRace 50 { 51 public void ShowKing() 52 { 53 Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Moon"); 54 } 55 } 56 57 public enum RaceType 58 { 59 Human, 60 Undead, 61 ORC, 62 NE 63 } 64 65 /// <summary> 66 /// 细节没有消失,只是转移 67 /// 矛盾也没有消除,只是转移 68 /// 除此之外把所有的业务都写在这个里面,也集中了矛盾 69 /// </summary> 70 /// <param name="raceType"></param> 71 /// <returns></returns> 72 public static IRace CreateRace(RaceType raceType) 73 { 74 IRace iRace = null; 75 switch (raceType) 76 { 77 case RaceType.Human: 78 iRace = new Human(); 79 break; 80 case RaceType.Undead: 81 iRace = new Undead(); 82 break; 83 case RaceType.ORC: 84 iRace = new ORC(); 85 break; 86 case RaceType.NE: 87 iRace = new NE(); 88 break; 89 //增加一个分支 90 default: 91 throw new Exception("wrong raceType"); 92 } 93 return iRace; 94 }
然后调用如下:
{ Player player = new Player() { Id = 123, Name = "候鸟" }; } { IRace human = ObjectFactory.CreateRace(RaceType.Human); //new Human();没有细节 细节被转移 player.PlayWar3(human); } { IRace undead = ObjectFactory.CreateRace(RaceType.Undead); //new Undead();没有细节 细节被转移 player.PlayWar3(undead); }
A:简单工厂的优点
1:简单工厂模式解决了客户端直接依赖于具体对象的问题,客户端可以消除直接创建对象的责任,而仅仅是消费产品。简单工厂模式实现了对责任的分割
2:简单工厂模式也起到了代码复用的作用,把所有的创建都统一一起写,以后避免多人多次写重复的代码
B:简单工厂的缺点
1:工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响
2:系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,这样就会造成工厂逻辑过于复杂
C:简单工厂的应用场景
1:当工厂类负责创建的对象比较少时可以考虑使用简单工厂模式
2:客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时可以考虑使用简单工厂模式
D:.NET中简单工厂模式的实现
.NET中System.Text.Encoding类就实现了简单工厂模式,该类中的GetEncoding(int codepage)就是工厂方法,具体的代码可以通过Reflector反编译工具进行查看,具体可以看下图
4:工厂方法
由于简单工厂模式系统难以扩展,一旦添加新产品就不得不修改简单工厂的方法,这样就会造就了简单工厂的实现逻辑过于复杂,所以出现了工厂方法。
工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点
A:工厂方法的实现
创建工厂方法如下:
1 namespace FactoryMethod.Factory 2 { 3 public interface IFactory 4 { 5 IRace CreateRace(); 6 } 7 8 public class UndeadFactory : IFactory 9 { 10 public IRace CreateRace() 11 { 12 return new Undead(); 13 } 14 } 15 public class ORCFactory : IFactory 16 { 17 public IRace CreateRace() 18 { 19 return new ORC(); 20 } 21 } 22 public class NEFactory : IFactory 23 { 24 public IRace CreateRace() 25 { 26 return new NE(); 27 } 28 } 29 30 public class HumanFactory : IFactory 31 { 32 public virtual IRace CreateRace() 33 { 34 return new Human(); 35 } 36 } 37 public class HumanFactoryAdvanced : HumanFactory 38 { 39 public override IRace CreateRace() 40 { 41 Console.WriteLine("123"); 42 return new Human(); 43 } 44 } 45 }
调用如下:
{ IFactory factory = new HumanFactory();//包一层 IRace race = factory.CreateRace(); } { IFactory factory = new UndeadFactory(); IRace race = factory.CreateRace(); }
这样看来一个实体业务类包了一层工厂,看似和直接创建业务类一致,其实不然,这样做不仅仅是屏蔽了对象的创建,还留下了扩展空间(以后有需要扩展的直接修改factory类,而外界不影响),完美的遵循了开闭原则(对扩展开放,对修改封闭)
B:.NET中工厂方法模式的实现
.NET 类库中也有很多实现了工厂方法的类,例如Asp.net中,处理程序对象是具体用来处理请求,当我们请求一个*.aspx的文件时,此时会映射到System.Web.UI.PageHandlerFactory类上进行处理,而对*.ashx的请求将映射到System.Web.UI.SimpleHandlerFactory类中(这两个类都是继承于IHttpHandlerFactory接口的),关于这点说明我们可以在“C:WindowsMicrosoft.NETFrameworkv4.0.30319ConfigWeb.Config”文件中找到相关定义,具体定义如下:
1 2 3 4 5 |
|
5:抽象工厂
创建一组密不可分的对象,屏蔽对象的创建,约束强制保障产品簇,
A:抽象工厂创建的代码如下
/// <summary> /// 一个工厂负责一些产品的创建 /// 产品簇 /// 单一职责就是创建完整的产品簇 /// /// 继承抽象类后,必须显式的override父类的抽象方法 /// </summary> public abstract class FactoryAbstract { public abstract IRace CreateRace(); public abstract IArmy CreateArmy(); public abstract IHero CreateHero(); public abstract IResource CreateResource(); //public abstract ILuck CreateLuck(); } /// <summary> /// 一个工厂负责一些产品的创建 /// </summary> public class HumanFactory : FactoryAbstract { public override IRace CreateRace() { return new Human(); } public override IArmy CreateArmy() { return new HumanArmy(); } public override IHero CreateHero() { return new HumanHero(); } public override IResource CreateResource() { return new HumanResource(); } } /// <summary> /// 一个工厂负责一些产品的创建 /// </summary> public class ORCFactory : FactoryAbstract { public override IRace CreateRace() { return new ORC(); } public override IArmy CreateArmy() { return new ORCArmy(); } public override IHero CreateHero() { return new ORCHero(); } public override IResource CreateResource() { return new ORCResource(); } } /// <summary> /// 一个工厂负责一些产品的创建 /// </summary> public class UndeadFactory : FactoryAbstract { public override IRace CreateRace() { return new Undead(); } public override IArmy CreateArmy() { return new UndeadArmy(); } public override IHero CreateHero() { return new UndeadHero(); } public override IResource CreateResource() { return new UndeadResource(); } }
抽象工厂对扩展种族比较省事,直接继承抽象类,然后即可,但是如果想要在抽象类中扩展一个新的对象,则会影响到所有的子类,它也被称为倾斜的可扩展性设计
B:.NET中抽象工厂模式的实现
其中我们用到的操作数据库类DbProviderFactory就是一个抽象工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
6:建造者模式
这个主要针对于一些复杂的工厂方法来说的!
二:结构性设计模式
结构性设计模式总共包含七种,分别为【适配器设计模式】【代理模式】【装饰器模式】【组合模式】【享元模式】【外观模式】【桥接模式】,他们主要实现的核心是:使用组合包一层,然后增加功能,但是多种结构型模式为何又不相同,是因为他们解决不同的问题,然后有不同的侧重点,也有不同的规范!
下面主要介绍一下适配器设计模式,代理模式,装饰器模式 三种
1:适配器设计模式
主要的功能就是字面上面的意思,做适配转接的功能,他主要分为类适配器模式(继承) 和对象适配器模式(组合),一般组合是优于继承的,通过代码我们来加以说明!
我们先定义一个接口
1 /// <summary> 2 /// 数据访问接口 3 /// </summary> 4 public interface IHelper 5 { 6 void Add<T>(); 7 void Delete<T>(); 8 void Update<T>(); 9 void Query<T>(); 10 }
然后下面的类分别要实现这个接口
1 public class SqlserverHelper : IHelper 2 { 3 public void Add<T>() 4 { 5 Console.WriteLine("This is {0} Add", this.GetType().Name); 6 } 7 public void Delete<T>() 8 { 9 Console.WriteLine("This is {0} Delete", this.GetType().Name); 10 } 11 public void Update<T>() 12 { 13 Console.WriteLine("This is {0} Update", this.GetType().Name); 14 } 15 public void Query<T>() 16 { 17 Console.WriteLine("This is {0} Query", this.GetType().Name); 18 } 19 }
public class MysqlHelper : IHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } }
然后我们调用的时候如下:
Console.WriteLine("*****************************"); { IHelper helper = new SqlserverHelper(); helper.Add<Program>(); helper.Delete<Program>(); helper.Update<Program>(); helper.Query<Program>(); } Console.WriteLine("*****************************"); { IHelper helper = new MysqlHelper(); helper.Add<Program>(); helper.Delete<Program>(); helper.Update<Program>(); helper.Query<Program>(); }
程序已经确定好了规范都要实现IHelper,所以我们都可以使用IHelper来接收,但是现在我们新增一个RedisHelper第三方的接口如下:
/// <summary> /// 第三方提供的 openstack servicestack /// 不能修改 /// </summary> public class RedisHelper { public RedisHelper() { Console.WriteLine($"构造RedisHelper"); } public void AddRedis<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void DeleteRedis<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void UpdateRedis<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void QueryRedis<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } }
然后我们也想通过上面的方式调用,即用IHelper来接收,如果是直接 IHelper helper = new RedisHelper();这样是不被允许的,因为他们之间没有父子关系,所以我们现在要增加中间类,来转换一下,让RedisHelper适应于IHelper,可以通过以下两种方式改善
第一种通过继承的方式来改善,新增类如下:
public class RedisHelperInherit : RedisHelper, IHelper { public RedisHelperInherit() { Console.WriteLine($"构造{this.GetType().Name}"); } public void Add<T>() { base.AddRedis<T>(); } public void Delete<T>() { base.DeleteRedis<T>(); } public void Query<T>() { base.QueryRedis<T>(); } public void Update<T>() { base.UpdateRedis<T>(); } }
第二种通过组合(分为属性注入,构造函数注入,方法注入三种方式)的方式来改善,新增类如下:
public class RedisHelperObject : IHelper { public RedisHelperObject() { Console.WriteLine($"构造{this.GetType().Name}"); } //属性注入 声明写死 private RedisHelper _RedisHelper = new RedisHelper(); ////构造函数 可以替换(需要抽象) public RedisHelperObject(RedisHelper redisHelper) { this._RedisHelper = redisHelper; } ////方法注入 可以替换(需要抽象) public void SetObject(RedisHelper redisHelper) { this._RedisHelper = redisHelper; } public void Add<T>() { this._RedisHelper.AddRedis<T>(); } public void Delete<T>() { this._RedisHelper.DeleteRedis<T>(); } public void Query<T>() { this._RedisHelper.QueryRedis<T>(); } public void Update<T>() { this._RedisHelper.UpdateRedis<T>(); } }
这样的两种方式改善代码后,然后可以通过下面调用:
1 //继承 既满足现有的规范调用,又没有修改RedisHelper 2 //类适配器模式 3 Console.WriteLine("*****************************"); 4 { 5 IHelper helper = new RedisHelperInherit(); 6 helper.Add<Program>(); 7 helper.Delete<Program>(); 8 helper.Update<Program>(); 9 helper.Query<Program>(); 10 } 11 //组合 既满足现有的规范调用,又没有修改RedisHelper 12 //对象适配器 13 Console.WriteLine("*****************************"); 14 { 15 IHelper helper = new RedisHelperObject(); 16 helper.Add<Program>(); 17 helper.Delete<Program>(); 18 helper.Update<Program>(); 19 helper.Query<Program>(); 20 }
A:我们上面说了组合是优于继承的,具体分为以下两点来解释
1:侵入性:二者都会先构造一个redishelper,继承是强侵入的,父类的东西子类必须有
2:灵活性:继承只为一个类服务,结构可以为多个类型服务(属性注入,构造函数注入,方法注入 三种)
B:适配器主要是解决重构的问题,新东西和旧系统不吻合,通过继承/组合进行适配
2:代理模式
通过代理业务类去完成对真实业务类的调用,代理类不能扩展业务功能,比如我们常见的代理如:翻墙代理,火车票代理,VPN代理
下面我们还是通过代码来解释何为代理,比如我们现在有个实际业务的接口如下:
1 /// <summary> 2 /// 业务接口 3 /// </summary> 4 public interface ISubject 5 { 6 /// <summary> 7 /// get 8 /// </summary> 9 /// <returns></returns> 10 bool GetSomething(); 11 12 /// <summary> 13 /// do 14 /// </summary> 15 void DoSomething(); 16 }
然后我们有个实际的业务实现类如下:
1 /// <summary> 2 /// 一个耗时耗资源的对象方法 3 /// </summary> 4 public class RealSubject : ISubject 5 { 6 public RealSubject() 7 { 8 Thread.Sleep(2000); 9 long lResult = 0; 10 for (int i = 0; i < 100000000; i++) 11 { 12 lResult += i; 13 } 14 Console.WriteLine("RealSubject被构造。。。"); 15 } 16 17 /// <summary> 18 /// 火车站查询火车票 19 /// </summary> 20 public bool GetSomething() 21 { 22 Console.WriteLine("坐车去火车站看看余票信息。。。"); 23 Thread.Sleep(3000); 24 Console.WriteLine("到火车站,看到是有票的"); 25 return true; 26 } 27 28 /// <summary> 29 /// 火车站买票 30 /// </summary> 31 public void DoSomething() 32 { 33 Console.WriteLine("开始排队。。。"); 34 Thread.Sleep(2000); 35 Console.WriteLine("终于买到票了。。。"); 36 } 37 }
我们应用的时候不直接访问这个这个实际业务,而是可以通过访问代理来实现这个业务,下面我们可以增加一个代理类如下:
1 public class ProxySubject : ISubject 2 { 3 //组合一下 4 private static ISubject _Subject = new RealSubject(); 5 public void DoSomething() 6 { 7 try 8 { 9 Console.WriteLine("prepare DoSomething..."); 10 _Subject.DoSomething(); 11 } 12 catch (Exception ex) 13 { 14 Console.WriteLine(ex.Message); 15 throw ex; 16 } 17 } 18 19 private static Dictionary<string, bool> ProxyDictionary = new Dictionary<string, bool>(); 20 public bool GetSomething() 21 { 22 try 23 { 24 Console.WriteLine("prepare GetSomething..."); 25 string key = "Proxy_GetSomething"; 26 bool bResult = false; 27 if (!ProxyDictionary.ContainsKey(key)) 28 { 29 bResult = _Subject.GetSomething(); 30 ProxyDictionary.Add(key, bResult); 31 } 32 else 33 { 34 bResult = ProxyDictionary[key]; 35 } 36 return bResult; 37 } 38 catch (Exception ex) 39 { 40 Console.WriteLine(ex.Message); 41 throw ex; 42 } 43 } 44 }
这个类中通过属性注入的方式来实现对真实业务类的调用,然后我们可以通过下面的方式来调用
1 { 2 Console.WriteLine("***********Proxy**************"); 3 ISubject subject = new ProxySubject(); 4 subject.GetSomething(); 5 //subject.DoSomething(); 6 }
然后写到这里会有很多人,为啥不直接调用真实的业务类,而非得要使用代理调用呢?这个问题我们接下来来讨论一下:
比如我们买火车票,我们可以直接去火车站买火车票,但是如果火车站离我们住的地方比较远,过去不方便,然后周围又有代售点,那我们是不是多一种选择,既可以去火车站又可以直接去代售点买呢,其实我们写代理类也类似于这个道理,有时候我们需要增加一些自己的需求,比如增加个日志,增加个异常处理,然后又想提升一下性能,这些完全都可以再ProxySubject中做,而不需要去修改实际业务类。
通过代理,能够为对象扩展功能(不是增加业务)而不去修改原始业务类,也就是包了一层,这就是代理要做的事情!
3:装饰器模式
装饰器模式是结构型设计模式巅峰之作,主要是通过组合+继承来完成的!
她主要实现的是每个类都可以定制自己的特殊功能,并且功能的顺序可以随意切换,是不是感觉很稀奇怎么实现的!
接下来我们还是以代码来加以理解说明,我们首先有一个抽象学生类
1 public abstract class AbstractStudent 2 { 3 public int Id { get; set; } 4 public string Name { get; set; } 5 6 public abstract void Study(); 7 }
然后我们有两个具体的学生类,一个普通的学生类,一个是VIP学生类,两个实体类分别继承抽象学生类如下:
1 /// <summary> 2 /// 一个普通的公开课学员,学习公开课 3 /// </summary> 4 public class StudentFree : AbstractStudent 5 { 6 public override void Study() 7 { 8 //Console.WriteLine("上课前要预习"); 9 10 Console.WriteLine("{0} is a free student studying .net Free", base.Name); 11 } 12 } 13 14 /// <summary> 15 /// 一个普通的vip学员,学习vip课程 16 /// </summary> 17 public class StudentVip : AbstractStudent 18 { 19 /// <summary> 20 /// 付费 上课前要预习 21 /// 上课学习 22 /// </summary> 23 public override void Study() 24 { 25 Console.WriteLine("{0} is a vip student studying .net Vip", base.Name); 26 } 27 }
接着我们定义一个装饰器的基类如下:
1 /// <summary> 2 /// 继承+组合 3 /// 装饰器的基类 4 /// 也是一个学员,继承了抽象类 5 /// </summary> 6 public class BaseStudentDecorator : AbstractStudent 7 { 8 private AbstractStudent _Student = null;//用了组合加override 9 public BaseStudentDecorator(AbstractStudent student) 10 { 11 this._Student = student; 12 } 13 14 public override void Study() 15 { 16 this._Student.Study(); 17 } 18 }
然后有的学生要付费,有的学生要做作业,有的学生要视频回放,针对于上面三个功能,我们直接定义三个类,分别如下:
1 /// <summary> 2 /// 父类是BaseStudentDecorator,爷爷类AbstractStudent 3 /// </summary> 4 public class StudentVideoDecorator : BaseStudentDecorator 5 { 6 public StudentVideoDecorator(AbstractStudent student) 7 : base(student)//表示父类的构造函数 8 { 9 } 10 public override void Study() 11 { 12 base.Study(); 13 Console.WriteLine("视频代码回看"); 14 } 15 } 16 17 /// <summary> 18 /// 父类是BaseStudentDecorator,爷爷类AbstractStudent 19 /// </summary> 20 public class StudentHomeworkDecorator : BaseStudentDecorator 21 { 22 public StudentHomeworkDecorator(AbstractStudent student) 23 : base(student)//表示父类的构造函数 24 { 25 26 } 27 28 public override void Study() 29 { 30 base.Study(); 31 32 Console.WriteLine("巩固练习"); 33 34 } 35 } 36 37 /// <summary> 38 /// 父类是BaseStudentDecorator,爷爷类AbstractStudent 39 /// </summary> 40 public class StudentPayDecorator : BaseStudentDecorator 41 { 42 public StudentPayDecorator(AbstractStudent student) 43 : base(student)//表示父类的构造函数 44 { 45 46 } 47 48 public override void Study() 49 { 50 Console.WriteLine("付费"); 51 base.Study(); 52 } 53 }
接下来我们就可以调用了,我们先定义一个学生类
1 AbstractStudent student = new StudentVip() 2 { 3 Id = 666, 4 Name = "加菲猫" 5 };
然后我们定义一个基础的装饰器类:
BaseStudentDecorator decorator = new BaseStudentDecorator(student);
通过里氏替换原则可以转换为如下:
1 AbstractStudent decorator = new BaseStudentDecorator(student);//里氏替换
走到这一步我们发现decorator和student的类型一致,于是我们把decorator换成student,于是变成了
student = new BaseStudentDecorator(student);//引用替换一下
然后可以student.Study(); 由此我们整个装饰器完成了!
下面奉上具体的调用代码
AbstractStudent student = new StudentVip() { Id = 666, Name = "加菲猫" }; student.Study(); //BaseStudentDecorator decorator = new BaseStudentDecorator(student); //AbstractStudent decorator = new BaseStudentDecorator(student);//里氏替换 student = new BaseStudentDecorator(student);//引用替换一下 student = new StudentHomeworkDecorator(student); student = new StudentVideoDecorator(student); student.Study();
输出的内容如下:
下面进行补充代码说明一下:
1 public class A 2 { 3 public virtual void Show() 4 { 5 Console.WriteLine("A的show"); 6 } 7 } 8 public class B : A 9 { 10 public override void Show() 11 { 12 Console.WriteLine("B的show-ing"); 13 base.Show(); 14 Console.WriteLine("B的show-end"); 15 } 16 } 17 public class C : B 18 { 19 public override void Show() 20 { 21 Console.WriteLine("C的show-ing"); 22 base.Show(); 23 Console.WriteLine("C的show-end"); 24 } 25 }
运行结果为如下:
三:行为型设计模式
行为型设计模式,主要关注对象和行为的分离,把不稳定的地方移出去,自己只写稳定的,能保证自身的稳定,一共有11种模式,分别为:【模版方法模式】【策略模式】【状态模式】【命令模式】【迭代器模式】【备忘录模式】【观察者模式】【中介者模式】【访问者模式】【责任链模式】【解释器模式】
1 /// <summary> 2 /// 银行客户端 3 /// </summary> 4 public abstract class AbstractClient 5 { 6 public void Query(int id, string name, string password) 7 { 8 if (this.CheckUser(id, password)) 9 { 10 double balance = this.QueryBalance(id); 11 double interest = this.CalculateInterest(balance); 12 this.Show(name, balance, interest); 13 } 14 else 15 { 16 Console.WriteLine("账户密码错误"); 17 } 18 } 19 20 public bool CheckUser(int id, string password) 21 { 22 return DateTime.Now < DateTime.Now.AddDays(1); 23 } 24 25 public double QueryBalance(int id) 26 { 27 return new Random().Next(10000, 1000000); 28 } 29 30 /// <summary> 31 /// 活期 定期 利率不同 32 /// </summary> 33 /// <param name="balance"></param> 34 /// <returns></returns> 35 public abstract double CalculateInterest(double balance); 36 37 public virtual void Show(string name, double balance, double interest) 38 { 39 Console.WriteLine("尊敬的{0}客户,你的账户余额为:{1},利息为{2}", 40 name, balance, interest); 41 } 42 }
然后我们声明两个子类,一个是活期用户,一个是定期用户,分别继承于上面的抽象类,代码如下:
1 /// <summary> 2 /// 银行客户端 3 /// </summary> 4 public class ClientVip : AbstractClient 5 { 6 /// <summary> 7 /// 活期 定期 利率不同 8 /// </summary> 9 /// <param name="balance"></param> 10 /// <returns></returns> 11 public override double CalculateInterest(double balance) 12 { 13 return balance * 0.005; 14 } 15 16 public override void Show(string name, double balance, double interest) 17 { 18 Console.WriteLine("尊贵的{0} vip客户,您的账户余额为:{1},利息为{2}", 19 name, balance, interest); 20 } 21 }
1 /// <summary> 2 /// 银行客户端 3 /// </summary> 4 public class ClientRegular : AbstractClient 5 { 6 /// <summary> 7 /// 活期 定期 利率不同 8 /// </summary> 9 /// <param name="balance"></param> 10 /// <returns></returns> 11 public override double CalculateInterest(double balance) 12 { 13 return balance * 0.003; 14 } 15 }
这样不同的业务可以直接在子类中实现,相同的业务可以在父类中实现,这就是所谓的模板模式,有没有发现我们平常基本上都是这样的写,然后只是不晓得它有如此一个高大上的名字而已!模板设计模式,好像就只是把一个复杂的多步骤业务,然后定义一个父类(模板),模板负责完成流程,把步骤分解,固定不变的类定义为父类,各不相同的定义为子类,就是把部分行为做了分离,所以有时候设计模式没有那么神奇,只不过是把常用的东西跟场景结合,沉淀下来起个名字。
1 /// <summary> 2 /// 只是为了把多个对象产生关系,方便保存和调用 3 /// 方法本身其实没用 4 /// </summary> 5 public interface IObserver 6 { 7 void Action(); 8 }
然后定一个多个实体分别继承于上面的接口,如下:
1 public class Chicken : IObserver 2 { 3 public void Action() 4 { 5 this.Woo(); 6 } 7 public void Woo() 8 { 9 Console.WriteLine("{0} Woo", this.GetType().Name); 10 } 11 } 12 public class Dog : IObserver 13 { 14 public void Action() 15 { 16 this.Wang(); 17 } 18 public void Wang() 19 { 20 Console.WriteLine("{0} Wang", this.GetType().Name); 21 } 22 } 23 public class Baby : IObserver 24 { 25 public void Action() 26 { 27 this.Cry(); 28 } 29 30 public void Cry() 31 { 32 Console.WriteLine("{0} Cry", this.GetType().Name); 33 } 34 }
然后定义一只猫如下:
1 public class Cat 2 { 3 public void Miao() 4 { 5 Console.WriteLine("{0} Miao.....", this.GetType().Name); 6 7 8 new Chicken().Woo(); 9 new Baby().Cry(); 10 new Dog().Wang(); 11 } 12 }
这样写会触发猫的不稳定性,如果猫叫了一声,接着新增一种动作,那意味着要修改猫的miao的方法,也就是说猫不仅要瞄,还要触发各种动作,违背了单一职责,所以我们由此想到猫只管自己叫,然后具体其它的动作可以不可以甩锅给别人,所以我们将代码进行改版增加MiaoObserver和MiaoEvent如下:
1 public class Cat 2 { 3 public void Miao() 4 { 5 Console.WriteLine("{0} Miao.....", this.GetType().Name); 6 7 new Chicken().Woo(); 8 new Baby().Cry(); 9 new Dog().Wang(); 10 } 11 12 private List<IObserver> _ObserverList = new List<IObserver>(); 13 public void AddObserver(IObserver observer) 14 { 15 this._ObserverList.Add(observer); 16 } 17 public void MiaoObserver() 18 { 19 Console.WriteLine("{0} MiaoObserver.....", this.GetType().Name); 20 if (this._ObserverList != null && this._ObserverList.Count > 0) 21 { 22 foreach (var item in this._ObserverList) 23 { 24 item.Action(); 25 } 26 } 27 } 28 29 private event Action MiaoHandler; 30 public void MiaoEvent() 31 { 32 Console.WriteLine("{0} MiaoEvent.....", this.GetType().Name); 33 if (this.MiaoHandler != null) 34 { 35 foreach (Action item in this.MiaoHandler.GetInvocationList()) 36 { 37 item.Invoke(); 38 } 39 } 40 } 41 }
然后调用的地方如下:
1 { 2 Console.WriteLine("***************Common******************"); 3 Cat cat = new Cat(); 4 cat.Miao(); 5 } 6 { 7 Console.WriteLine("***************Observer******************"); 8 Cat cat = new Cat(); 9 cat.AddObserver(new Chicken()); 10 cat.AddObserver(new Baby()); 11 cat.AddObserver(new Dog()); 12 cat.MiaoObserver(); 13 } 14 { 15 Console.WriteLine("***************Observer******************"); 16 Cat cat = new Cat(); 17 cat.AddObserver(new Chicken()); 18 cat.AddObserver(new Baby()); 19 cat.AddObserver(new Dog()); 20 cat.MiaoObserver(); 21 }
以后增加动作只需要再调用端随时增加,顺序也随意修改,这样成功了保证了猫的稳定性的同时也实现了猫叫后触发了其它的动作!这就是所谓的观察者模式,把不稳定的地方移出去,自己只写稳定的,能保证自身的稳定!
3:责任链模式
责任链模式是:请求的处理流程,沿着链子顺序执行,还运行链子扩展和订制,这是行为型设计模式的巅峰之作!
我们还是从业务场景出发:请假流程的审批,比如请假时间少于等于8小时,则PM可以审批;请假时长大于8小时小于等于16小时的,部门主管审批;请假时长大于16小于等于32个小时,公司主管可以审批;如果我们看到这个业务流程,很多人第一反应是写下面的代码:
1 if (context.Hour <= 8) 2 { 3 Console.WriteLine("PM审批通过"); 4 } 5 else if (context.Hour <= 16) 6 { 7 Console.WriteLine("部门主管审批通过"); 8 } 9 else if (context.Hour <= 32) 10 { 11 Console.WriteLine("公司主管审批通过"); 12 } 13 else 14 { 15 Console.WriteLine("************"); 16 }
把整个业务流程写到上端,一旦修改流程,直接修改代码。所以我们可以通过了解业务逻辑来进行分析,我们审批的人有PM,Charge,Manager,CEO四个人,然后审批者都有名字,都有审批的这个功能,另外我们还需要一个申请人ApplyContext,所以我们创建一个基类如下:
1 public abstract class AbstractAuditor 2 { 3 public string Name { get; set; } 4 public abstract void Audit(ApplyContext context); 5 6 private AbstractAuditor _NextAuditor = null; 7 public void SetNext(AbstractAuditor auditor) 8 { 9 this._NextAuditor = auditor; 10 } 11 protected void AuditNext(ApplyContext context) 12 { 13 if (this._NextAuditor != null) 14 { 15 this._NextAuditor.Audit(context); 16 } 17 else 18 { 19 context.AuditResult = false; 20 context.AuditRemark = "不允许请假!"; 21 } 22 } 23 }
然后创建PM,Charge,Manager,CEO如下:
1 public class PM : AbstractAuditor 2 { 3 public override void Audit(ApplyContext context) 4 { 5 Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); 6 if (context.Hour <= 8) 7 { 8 context.AuditResult = true; 9 context.AuditRemark = "允许请假!"; 10 } 11 else 12 { 13 base.AuditNext(context); 14 } 15 } 16 }
1 public class Charge: AbstractAuditor 2 { 3 public override void Audit(ApplyContext context) 4 { 5 Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); 6 if (context.Hour <= 16) 7 { 8 context.AuditResult = true; 9 context.AuditRemark = "允许请假!"; 10 } 11 else 12 { 13 base.AuditNext(context); 14 } 15 } 16 }
1 public class Manager : AbstractAuditor 2 { 3 public override void Audit(ApplyContext context) 4 { 5 Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); 6 if (context.Hour <= 24) 7 { 8 context.AuditResult = true; 9 context.AuditRemark = "允许请假!"; 10 } 11 else 12 { 13 base.AuditNext(context); 14 } 15 } 16 }
然后进行调用,可以新增一个类组成链子形式的如下:
1 public class AuditorBuilder 2 { 3 /// <summary> 4 /// 那就反射+配置文件 5 /// 链子的组成都可以通过配置文件 6 /// </summary> 7 /// <returns></returns> 8 public static AbstractAuditor Build() 9 { 10 AbstractAuditor pm = new PM() 11 { 12 Name = "张琪琪" 13 }; 14 AbstractAuditor charge = new Charge() 15 { 16 Name = "吴可可" 17 }; 18 AbstractAuditor ceo = new CEO() 19 { 20 Name = "加菲猫" 21 }; 22 23 pm.SetNext(pm); 24 charge.SetNext(charge); 25 ceo.SetNext(ceo); 26 return pm; 27 } 28 }
调用如下:
1 ApplyContext context = new ApplyContext() 2 { 3 Id = 506, 4 Name = "小新", 5 Hour = 32, 6 Description = "我周一要请假回家", 7 AuditResult = false, 8 AuditRemark = "" 9 }; 10 11 AbstractAuditor auditor = AuditorBuilder.Build(); 12 auditor.Audit(context); 13 if (!context.AuditResult) 14 { 15 Console.WriteLine("不干了!"); 16 }
上面就是所谓的责任链设计模式,如果整个流程审批人修改,不用修改底层逻辑,而是直接把调用的地方修改即可。
以上就是三大类设计模式中典型的设计模式,其实没有什么设计模式是完美无缺的,一个设计模式就是解决一类的问题的,通常设计模式在解决一类问题的同时,还会带来别的问题,我们设计者要做的事儿,就是要扬长避短,充分发挥长处!