C# 23种设计模式
原文:https://www.cnblogs.com/for-easy-fast/p/12425528.html
设计模式的分类
创建型模式: 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。 结构型模式: 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。 行为型模式: 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。——关注对象的创建
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。——关注类与类之间的关系
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。——关注对象和行为的分离
其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:
接下来我们分别来讲讲一下这三类设计模式
创建性设计模式
看到这个很多人会想,对象的创建难道不就是new()一下,然后就能解决的吗?其实不然,这里面有很多套路,他包含:【单例模式】【原型】【简单工厂】【工厂方法】【抽象工厂】【建造者模式】等
单例模式
主要应用于构造对象耗时好资源,且很多地方都需要去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:单例一般运用的场景
单例是保证全局唯一的一个实例,主要是应对一些特殊情况,比如数据库连接池(内置资源),再比如:流水号或者订单号生成器,使用同一个变量保证初始值是同一个!
原型设计模式
原型设计模式是在单例的基础上面升级了一下,然后把对象从内存的层面复制了一下,然后返回一个新的对象,但是又不走构造函数
注意:原型模式是一个新的对象,新的地址,但是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:原型模式适用的场景
1:创建新对象成本较大(例如初始化时间长,占用CPU多或占太多网络资源),新对象可以通过复制已有对象来获得,如果相似对象,则可以对其成员变量稍作修改。
2:系统要保存对象的状态,而对象的状态很小。
3:需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的组合状态,通过复制原型对象得到新实例可以比使用构造函数创建一个新实例更加方便。
简单工厂(不属于23种设计模式)
不直接new,然后把对象的创建转移到工厂中,这种是细节没有消失,只是转移了矛盾,并没有消除矛盾,而是把矛盾集中在同一个工厂中
创建一个简单工厂的实例如下:
public interface IRace { /// <summary> /// show出王者 /// </summary> void ShowKing(); } /// <summary> /// War3种族之一 /// </summary> public class Human : IRace { public Human(int id, DateTime dateTime, string reamrk) { } public Human() { } public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Sky"); } } /// <summary> /// War3种族之一 /// </summary> public class Undead : IRace { public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "GoStop"); } } /// <summary> /// War3种族之一 /// </summary> public class ORC : IRace { public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Grubby"); } } /// <summary> /// War3种族之一 /// </summary> public class NE : IRace { public void ShowKing() { Console.WriteLine("The King of {0} is {1}", this.GetType().Name, "Moon"); } } public enum RaceType { Human, Undead, ORC, NE } /// <summary> /// 细节没有消失,只是转移 /// 矛盾也没有消除,只是转移 /// 除此之外把所有的业务都写在这个里面,也集中了矛盾 /// </summary> /// <param name="raceType"></param> /// <returns></returns> public static IRace CreateRace(RaceType raceType) { IRace iRace = null; switch (raceType) { case RaceType.Human: iRace = new Human(); break; case RaceType.Undead: iRace = new Undead(); break; case RaceType.ORC: iRace = new ORC(); break; case RaceType.NE: iRace = new NE(); break; //增加一个分支 default: throw new Exception("wrong raceType"); } return iRace; }
然后调用如下
{ 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反编译工具进行查看,具体可以看下图
工厂方法
由于简单工厂模式系统难以扩展,一旦添加新产品就不得不修改简单工厂的方法,这样就会造就了简单工厂的实现逻辑过于复杂,所以出现了工厂方法。
工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点
A:工厂方法的实现
创建工厂方法如下:
namespace FactoryMethod.Factory { public interface IFactory { IRace CreateRace(); } public class UndeadFactory : IFactory { public IRace CreateRace() { return new Undead(); } } public class ORCFactory : IFactory { public IRace CreateRace() { return new ORC(); } } public class NEFactory : IFactory { public IRace CreateRace() { return new NE(); } } public class HumanFactory : IFactory { public virtual IRace CreateRace() { return new Human(); } } public class HumanFactoryAdvanced : HumanFactory { public override IRace CreateRace() { Console.WriteLine("123"); return new Human(); } } }
调用如下:
{ 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:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.Config”文件中找到相关定义,具体定义如下:
<httpHandlers> <add path="*.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" /> <add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True" /> <add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True" /> </httpHandlers>
抽象工厂
创建一组密不可分的对象,屏蔽对象的创建,约束强制保障产品簇,
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就是一个抽象工厂
/// 扮演抽象工厂的角色 /// 创建连接数据库时所需要的对象集合, /// 这个对象集合包括有 DbConnection对象(这个是抽象产品类,如绝味例子中的YaBo类)、DbCommand类、DbDataAdapter类,针对不同的具体工厂都需要实现该抽象类中方法, public abstract class DbProviderFactory { // 提供了创建具体产品的接口方法 protected DbProviderFactory(); public virtual DbCommand CreateCommand(); public virtual DbCommandBuilder CreateCommandBuilder(); public virtual DbConnection CreateConnection(); public virtual DbConnectionStringBuilder CreateConnectionStringBuilder(); public virtual DbDataAdapter CreateDataAdapter(); public virtual DbDataSourceEnumerator CreateDataSourceEnumerator(); public virtual DbParameter CreateParameter(); public virtual CodeAccessPermission CreatePermission(PermissionState state); }
建造者模式
这个主要针对于一些复杂的工厂方法来说的!建造者模式,当然也有叫生成器模式的,英文名称是Builder Pattern。说到建造者我们首先想到的是盖房子,盖房子简单的说有三个步骤:打地基,砌砖,粉刷。我们就以盖房子为例解释建造者模式的用法。
建造者模式有三个角色:建造者,具体的建造者,监工。理清这三个角色的作用我们就可以愉快的使用建造者模式了。
建造者:一般为抽象类或接口,定义了建造者的功能。如盖房子例子的建造者有打地基,砌砖和粉刷的功能。
具体的建造者:实现了建造者的抽象方法(或接口)。不同的具体建造者生产的组件不同,如一个技术好的建造者打地基深,砌砖整齐,粉刷光滑,而技术差的建造者打地基浅,砌砖错乱,粉刷粗糙。
监工:制定建造的算法。建造者可以打地基,砌砖,粉刷,但是不知道先粉刷还是先打地基,监工就是给建造者制定盖房子步骤的。
代码实现如下,
建造者和具体建造者:
//建造者抽象类,定义了建造者的能力 public abstract class Builder { public abstract void Dadiji();//打地基 public abstract void QiZhuan();//砌砖 public abstract void FenShua();//粉刷 } /// <summary> /// 技术好的建造者 /// </summary> public class GoodBuilder : Builder { private StringBuilder house = new StringBuilder(); public override void Dadiji() { house.Append("深地基-->"); //这里一般是new一个部件,添加到实例中,如 house.Diji=new Diji("深地基") //为了演示方便 用sringBuilder表示一个复杂的房子,string表示房子的部件 } public override void FenShua() { house.Append("粉刷光滑-->"); } public override void QiZhuan() { house.Append("砌砖整齐-->"); } public string GetHouse() { return house.Append("好质量房子建成了!").ToString(); } } /// <summary> /// 技术差的建造者 /// </summary> public class BadBuilder:Builder { private StringBuilder house = new StringBuilder(); public override void Dadiji() { house.Append("挖浅地基-->"); } public override void FenShua() { house.Append("粉刷粗糙-->"); } public override void QiZhuan() { house.Append("砌砖错乱-->"); } public string GetHouse() { return house.Append("坏质量房子建成了!").ToString(); } }
监工:
//监工类,制定盖房子的步骤 public class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } //制定盖房子的流程, public void Construct() { builder.Dadiji();//先打地基 builder.QiZhuan();//再砌砖 builder.FenShua();//最后粉刷 } }
客户端调用:
class Program { static void Main(string[] args) { //监工1派遣技术好的建造者盖房子 GoodBuilder goodBuilder = new GoodBuilder(); Director director1 = new Director(goodBuilder); director1.Construct(); string house1 = goodBuilder.GetHouse(); Console.WriteLine(house1); //监工2派遣技术差的建造者盖房子 GoodBuilder badBuilder = new GoodBuilder(); Director director2 = new Director(goodBuilder); director2.Construct(); string house2 = goodBuilder.GetHouse(); Console.WriteLine(house2); Console.ReadKey(); } }
运行结果:
结构性设计模式
结构性设计模式总共包含七种,分别为【适配器设计模式】【代理模式】【装饰器模式】【组合模式】【享元模式】【外观模式】【桥接模式】,他们主要实现的核心是:使用组合包一层,然后增加功能,但是多种结构型模式为何又不相同,是因为他们解决不同的问题,然后有不同的侧重点,也有不同的规范!
下面主要介绍一下适配器设计模式,代理模式,装饰器模式 三种
适配器设计模式
主要的功能就是字面上面的意思,做适配转接的功能,他主要分为类适配器模式(继承) 和对象适配器模式(组合),一般组合是优于继承的,通过代码我们来加以说明!
我们先定义一个接口
/// <summary> /// 数据访问接口 /// </summary> public interface IHelper { void Add<T>(); void Delete<T>(); void Update<T>(); void Query<T>(); }
然后下面的类分别要实现这个接口
public class SqlserverHelper : 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); } }
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>(); } }
这样的两种方式改善代码后,然后可以通过下面调用:
//继承 既满足现有的规范调用,又没有修改RedisHelper //类适配器模式 Console.WriteLine("*****************************"); { IHelper helper = new RedisHelperInherit(); helper.Add<Program>(); helper.Delete<Program>(); helper.Update<Program>(); helper.Query<Program>(); } //组合 既满足现有的规范调用,又没有修改RedisHelper //对象适配器 Console.WriteLine("*****************************"); { IHelper helper = new RedisHelperObject(); helper.Add<Program>(); helper.Delete<Program>(); helper.Update<Program>(); helper.Query<Program>(); }
A:我们上面说了组合是优于继承的,具体分为以下两点来解释
1:侵入性:二者都会先构造一个redishelper,继承是强侵入的,父类的东西子类必须有
2:灵活性:继承只为一个类服务,结构可以为多个类型服务(属性注入,构造函数注入,方法注入 三种)
B:适配器主要是解决重构的问题,新东西和旧系统不吻合,通过继承/组合进行适配
代理模式
通过代理业务类去完成对真实业务类的调用,代理类不能扩展业务功能,比如我们常见的代理如:FQ代理,火车票代理,VPN代理
下面我们还是通过代码来解释何为代理,比如我们现在有个实际业务的接口如下:
/// <summary> /// 业务接口 /// </summary> public interface ISubject { /// <summary> /// get /// </summary> /// <returns></returns> bool GetSomething(); /// <summary> /// do /// </summary> void DoSomething(); }
然后我们有个实际的业务实现类如下:
/// <summary> /// 一个耗时耗资源的对象方法 /// </summary> public class RealSubject : ISubject { public RealSubject() { Thread.Sleep(2000); long lResult = 0; for (int i = 0; i < 100000000; i++) { lResult += i; } Console.WriteLine("RealSubject被构造。。。"); } /// <summary> /// 火车站查询火车票 /// </summary> public bool GetSomething() { Console.WriteLine("坐车去火车站看看余票信息。。。"); Thread.Sleep(3000); Console.WriteLine("到火车站,看到是有票的"); return true; } /// <summary> /// 火车站买票 /// </summary> public void DoSomething() { Console.WriteLine("开始排队。。。"); Thread.Sleep(2000); Console.WriteLine("终于买到票了。。。"); } }
我们应用的时候不直接访问这个这个实际业务,而是可以通过访问代理来实现这个业务,下面我们可以增加一个代理类如下:
public class ProxySubject : ISubject { //组合一下 private static ISubject _Subject = new RealSubject(); public void DoSomething() { try { Console.WriteLine("prepare DoSomething..."); _Subject.DoSomething(); } catch (Exception ex) { Console.WriteLine(ex.Message); throw ex; } } private static Dictionary<string, bool> ProxyDictionary = new Dictionary<string, bool>(); public bool GetSomething() { try { Console.WriteLine("prepare GetSomething..."); string key = "Proxy_GetSomething"; bool bResult = false; if (!ProxyDictionary.ContainsKey(key)) { bResult = _Subject.GetSomething(); ProxyDictionary.Add(key, bResult); } else { bResult = ProxyDictionary[key]; } return bResult; } catch (Exception ex) { Console.WriteLine(ex.Message); throw ex; } } }
这个类中通过属性注入的方式来实现对真实业务类的调用,然后我们可以通过下面的方式来调用
{ Console.WriteLine("***********Proxy**************"); ISubject subject = new ProxySubject(); subject.GetSomething(); //subject.DoSomething(); }
然后写到这里会有很多人,为啥不直接调用真实的业务类,而非得要使用代理调用呢?这个问题我们接下来来讨论一下:
比如我们买火车票,我们可以直接去火车站买火车票,但是如果火车站离我们住的地方比较远,过去不方便,然后周围又有代售点,那我们是不是多一种选择,既可以去火车站又可以直接去代售点买呢,其实我们写代理类也类似于这个道理,有时候我们需要增加一些自己的需求,比如增加个日志,增加个异常处理,然后又想提升一下性能,这些完全都可以再ProxySubject中做,而不需要去修改实际业务类。
通过代理,能够为对象扩展功能(不是增加业务)而不去修改原始业务类,也就是包了一层,这就是代理要做的事情!
装饰器模式
装饰器模式是结构型设计模式巅峰之作,主要是通过组合+继承来完成的!
她主要实现的是每个类都可以定制自己的特殊功能,并且功能的顺序可以随意切换,是不是感觉很稀奇怎么实现的!
接下来我们还是以代码来加以理解说明,我们首先有一个抽象学生类
public abstract class AbstractStudent { public int Id { get; set; } public string Name { get; set; } public abstract void Study(); }
然后我们有两个具体的学生类,一个普通的学生类,一个是VIP学生类,两个实体类分别继承抽象学生类如下:
/// <summary> /// 一个普通的公开课学员,学习公开课 /// </summary> public class StudentFree : AbstractStudent { public override void Study() { //Console.WriteLine("上课前要预习"); Console.WriteLine("{0} is a free student studying .net Free", base.Name); } } /// <summary> /// 一个普通的vip学员,学习vip课程 /// </summary> public class StudentVip : AbstractStudent { /// <summary> /// 付费 上课前要预习 /// 上课学习 /// </summary> public override void Study() { Console.WriteLine("{0} is a vip student studying .net Vip", base.Name); } }
接着我们定义一个装饰器的基类如下:
/// <summary> /// 继承+组合 /// 装饰器的基类 /// 也是一个学员,继承了抽象类 /// </summary> public class BaseStudentDecorator : AbstractStudent { private AbstractStudent _Student = null;//用了组合加override public BaseStudentDecorator(AbstractStudent student) { this._Student = student; } public override void Study() { this._Student.Study(); } }
然后有的学生要付费,有的学生要做作业,有的学生要视频回放,针对于上面三个功能,我们直接定义三个类,分别如下:
/// <summary> /// 父类是BaseStudentDecorator,爷爷类AbstractStudent /// </summary> public class StudentVideoDecorator : BaseStudentDecorator { public StudentVideoDecorator(AbstractStudent student) : base(student)//表示父类的构造函数 { } public override void Study() { base.Study(); Console.WriteLine("视频代码回看"); } } /// <summary> /// 父类是BaseStudentDecorator,爷爷类AbstractStudent /// </summary> public class StudentHomeworkDecorator : BaseStudentDecorator { public StudentHomeworkDecorator(AbstractStudent student) : base(student)//表示父类的构造函数 { } public override void Study() { base.Study(); Console.WriteLine("巩固练习"); } } /// <summary> /// 父类是BaseStudentDecorator,爷爷类AbstractStudent /// </summary> public class StudentPayDecorator : BaseStudentDecorator { public StudentPayDecorator(AbstractStudent student) : base(student)//表示父类的构造函数 { } public override void Study() { Console.WriteLine("付费"); base.Study(); } }
接下来我们就可以调用了,我们先定义一个学生类
AbstractStudent student = new StudentVip() { Id = 666, Name = "加菲猫" };
然后我们定义一个基础的装饰器类:
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();
输出的内容如下:
下面进行补充代码说明一下:
public class A { public virtual void Show() { Console.WriteLine("A的show"); } } public class B : A { public override void Show() { Console.WriteLine("B的show-ing"); base.Show(); Console.WriteLine("B的show-end"); } } public class C : B { public override void Show() { Console.WriteLine("C的show-ing"); base.Show(); Console.WriteLine("C的show-end"); } }
运行结果为如下:
外观模式
外观模式也被叫做门面模式,这种模式的作用是:隐藏系统的复杂性,并向客户端提供了一个可以访问系统的统一接口,这个统一的接口组合了子系统的多个接口。使用统一的接口使得子系统更容易被访问或者使用。 以去医院看病为例,去医院看病时可能要去挂号、门诊、划价、取药等,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。我们先了解下外观模式的三种角色:
子系统角色:实现了各种子功能,子系统之间可以相互交户,也可以提供给客户端直接调用的接口。
门面角色:熟悉子系统的功能,可以把子系统的功能组合在一起然后提供一个统一的接口供客户端调用。
客户端角色:调用Facede来实现要完成的功能。
下边使用网友的电脑开关机的例子(链接:java设计模式之外观模式(门面模式))介绍外观模式的用法:每台电脑都有CPU,Memory,Disk。我们开关电脑时不会一个一个地打开或关闭各个部件,而是通过一个统一的开关机按钮一次性打开各个部件。通过外观模式可以实现用户的与部件间的解耦。
子系统(cpu,memory,disk)代码:
/// <summary> /// CPU子系统 /// </summary> public class CPU { public void CPUStart() { Console.WriteLine("CPU is start..."); } public void CPUShutdown() { Console.WriteLine("CPU is shot down..."); } } /// <summary> /// 内存子系统 /// </summary> public class Memory { public void MemoryStart() { Console.WriteLine("Memory is start..."); } public void MemoryShutdown() { Console.WriteLine("Memory is shot down..."); } } /// <summary> /// 硬盘子系统 /// </summary> public class Disk { public void DiskStart() { Console.WriteLine("Disk is start..."); } public void DiskShutdown() { Console.WriteLine("Disk is shot down..."); } }
门面类(Computer)代码:
/// <summary> /// 电脑 facede角色 /// </summary> public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer() { this.cpu = new CPU(); this.memory = new Memory(); this.disk = new Disk(); } public void Start() { cpu.CPUStart(); memory.MemoryStart(); disk.DiskStart(); Console.WriteLine("computer start end!"); } public void Shutdown() { cpu.CPUShutdown(); memory.MemoryShutdown(); disk.DiskShutdown(); Console.WriteLine("computer shutdown end!"); } }
客户端调用:
class Program { static void Main(string[] args) { Computer computer = new Computer(); //开机 computer.Start(); Console.WriteLine(); //关机 computer.Shutdown(); Console.ReadKey(); } }
程序运行结果:
外观模式的使用:
外观模式在我们的开发中使用的比较频繁,以三层架构为例:
子系统角色:Dal层,负责数据访问,比如有UserDal和RoleDal,UserDal返回的数据格式为[名字:张三,角色ID:3],RoleDal层返回的数据格式[角色ID:3,角色名:管理员]
门面角色:Bll层,负责具体业务(汇总子系统的功能,这里使用UserDal和RoleDal两个子系统角色的功能),返回的数据格式[名字:张三 ,角色名:管理员]
客户端角色:UI层,通过Bll层直接拿到格式如[名字:张三 ,角色名:管理员]的数据。
外观模式的优点:
1.隐藏了系统的复杂性,让客户端使用系统功能时变很简单;
2.实现客户端和子系统间的解耦。
外观模式的缺点:
1.不符合开闭原则,如客户端要使用更多功能时,不仅仅在子系统进行添加或修改操作,也必须修改门面层。
桥接模式介绍
桥接模式的角色
public abstract class Shape { //形状内部包含了另一个维度:color protected IColor color; public void SetColor(IColor color) { this.color = color; } //设置形状 public abstract void Draw(); } /// <summary> /// 圆形 /// </summary> public class Circle : Shape { public override void Draw() { color.Paint("圆形"); } } /// <summary> /// 长方形 /// </summary> public class Rectangle : Shape { public override void Draw() { color.Paint("长方形"); } } /// <summary> /// 三角形 /// </summary> public class Triangle : Shape { public override void Draw() { color.Paint("三角形"); } }
颜色接口和三种实现类:
/// <summary> /// 颜色接口 /// </summary> public interface IColor { void Paint(string shape); } /// <summary> /// 蓝色 /// </summary> public class Blue : IColor { public void Paint(string shape) { Console.WriteLine($"蓝色的{shape}"); } } /// <summary> /// 黄色 /// </summary> public class Yellow : IColor { public void Paint(string shape) { Console.WriteLine($"黄色的{shape}"); } } /// <summary> /// 红色 /// </summary> public class Red : IColor { public void Paint(string shape) { Console.WriteLine($"红色的{shape}"); } }
客户端调用代码:
class Program { static void Main(string[] args) { Shape circle = new Circle(); IColor blue = new Blue(); circle.SetColor(blue);//设置颜色 circle.Draw();//画图 Shape triangle = new Triangle(); triangle.SetColor(blue); triangle.Draw(); Console.ReadKey(); } }
程序运行结果
桥接模式的使用场景:
当系统实现有多个角度分类,每种分类都可能变化时使用。近几年提出的微服务概念采用了桥接模式的思想,通过各种服务的组合来实现一个大的系统。
桥接模式的优点:
1.实现抽象和具体的分离,降低了各个分类角度间的耦合;
2.扩展性好,解决了多角度分类使用继承可能出现的子类爆炸问题。
桥接模式的缺点:
桥接模式的引进需要通过聚合关联关系建立抽象层,增加了理解和设计系统的难度。
组合模式
在软件开发中我们经常会遇到处理部分与整体的情况,如我们经常见到的树形菜单,一个菜单项的子节点可以指向具体的内容,也可以是子菜单。类似的情况还有文件夹,文件夹的下级可以是文件夹也可以是文件。举一个例子:一个公司的组织架构是这样的,首先是总公司,总公司下边有直属员工和各个部门,各个部门下边有本部门的子部门和员工。我们去怎么去获取这个公司的组织架构呢(就是有层次地遍历出公司的部门名和员工名)?
组合模式可以很好地解决这类问题,组合模式通过让树形结构的叶子节点和树枝节点使用同样的接口,结合递归的思想来处理部分与整体关系,这种方式模糊了简单对象(叶子)和对象组(树枝)间的概念,让我们可以像处理单个对象一样去处理对象组。
树叶和树枝都要使用相同的接口,所以先创建一个抽象类,其内部定义了树枝和树叶的公共接口:
/// <summary> /// 抽象部件 定义了树枝和树叶的公共属性和接口 /// </summary> public abstract class Component { public string name; public Component(string name) { this.name = name; } //添加子节点 public abstract void Add(Component c); //删除子节点 public abstract void Remove(Component c); //展示方法,dept为节点深度 public abstract void Display(int dept); }
员工类,相当于树叶,没有下一级:
//具体员工,树形结构的Leaf public class Employee : Component { public Employee(string name):base(name) { this.name = name; } //Leaf不能添加/删除子节点所以空实现 public override void Add(Component c) { } public override void Remove(Component c) { } public override void Display(int dept) { Console.WriteLine(new string('-', dept)+name); } }
部门类,相当于树枝,下边的节点可有有子部门,也可以有员工:
/// <summary> /// 部门类,相当于树枝 /// </summary> public class Depart : Component { public Depart(string name) : base(name) { this.name = name; } //添加子节点 public List<Component> children=new List<Component>(); public override void Add(Component c) { children.Add(c); } //删除子节点 public override void Remove(Component c) { children.Remove(c); } //展示自己和和内部的所有子节点,这里是组合模式的核心 public override void Display(int dept) { Console.WriteLine(new string('-',dept)+name); foreach (var item in children) { //这里用到了递归的思想 item.Display(dept + 4); } } }
客户端调用:
class Program { static void Main(string[] args) { Component DepartA = new Depart("A总公司"); Component DepartAX = new Depart("AX部门"); Component DepartAY = new Depart("AY部门"); Component DepartAX1 = new Depart("AX1子部门"); Component DepartAX2 = new Depart("AX2子部门"); Component Ae1 = new Employee("公司直属员工1"); Component AXe1= new Employee("AX部门员工1"); Component AX1e1= new Employee("AX1部门员工1"); Component AX1e2= new Employee("AX1部门员工2"); Component AYe1= new Employee("AY部门员工1"); Component AYe2= new Employee("AY部门员工2"); DepartA.Add(Ae1); DepartA.Add(DepartAX); DepartA.Add(DepartAY); DepartAX.Add(AXe1); DepartAX.Add(DepartAX1); DepartAX.Add(DepartAX2); DepartAX1.Add(AX1e1); DepartAX1.Add(AX1e2); DepartAY.Add(AYe1); DepartAY.Add(AYe2); //遍历总公司 DepartA.Display(1); Console.ReadKey(); } }
运行结果如下:
上边的例子中部门类中包含了一个List children,这个List内部装的是该部门的子节点,这些子节点可以是子部门也可以是员工,在部门类的Display方法中通过foreach来遍历每一个子节点,如果子节点是员工则直接调用员工类中的Display方法打印出名字;如果子节点是子部门,调用部门类的Display遍历子部门的下级节点,直到下级节点只有员工或者没有下级节点为止。这里用到了递归的思想。
组合模式的使用场景:当我们处理部分-整体的层次结构时,希望使用统一的接口来处理部分和整体时使用。
组合模式的优点:在树形结构的处理中模糊了对象和对象组的概念,使用对象和对象组采用了统一的接口,让我们可以像处理简单对象一样处理对象组。
享元模式介绍
在软件开发中我们经常遇到多次使用相似或者相同对象的情况,如果每次使用这个对象都去new一个新的实例会很浪费资源。这时候很多人会想到前边介绍过的一个设计模式:原型模式,原型模式通过拷贝现有对象来生成一个新的实例,使用拷贝来替代new。原型模式可以很好的解决创建多个相同/相似实例的问题,为什么还要用享元模式呢?这是因为这两种模式的使用场景是不同的,原型模式侧重于”创建“,我们通过拷贝确确实实的创建了新的实例,它属于创建型设计模式;而享元模式侧重于“重用”,即如果有现有的实例就不去创建了,直接拿来用就行了。
下面以大头儿子家开车为例介绍享元模式的用法。我们都知道大头儿子家里有三个人,这里就不用介绍了,家里现有一辆红色车和一辆蓝色车,小头爸爸,扁头妈妈和大头儿子开车时都是用家里现有的车,而不是每次开车都要新买一辆,只有想开的车家里没有时才会去买一辆,如大头儿子想开白色的车,但家里没有白色的车,这时候才去买一辆回来。我们直接在代码中理解享元模式的用法:
抽象车类Car定义了具体车共有的接口方法Use,无论什么车都就是可以用来开的,具体车类RealCar实现了Use接口。我们获取Car的实例不是通过new来获取,而是通过车库CarFactory的GetCar方法来获取,在GetCar方法中获取车时,首先判断车库中是否存在我们想要的车,如果有直接拿来用,如果没有才去买(new)一辆新车。
///抽象车类 public abstract class Car { //开车 public abstract void Use(Driver d); } /// <summary> /// 具体的车类 /// </summary> public class RealCar : Car { //颜色 public string Color { get; set; } public RealCar(string color) { this.Color = color; } //开车 public override void Use(Driver d) { Console.WriteLine($"{d.Name}开{this.Color}的车"); } } /// <summary> /// 车库 /// </summary> public class CarFactory { private Dictionary<string, Car> carPool=new Dictionary<string, Car>(); //初始的时候,只有红色和绿色两辆汽车 public CarFactory() { carPool.Add("红色", new RealCar("红色")); carPool.Add("绿色", new RealCar("蓝色")); } //获取汽车 public Car GetCar(string key) { //如果车库有就用车库里的车,车库没有就买一个(new一个) if (!carPool.ContainsKey(key)) { carPool.Add(key, new RealCar(key)); } return carPool[key]; } } /// <summary> /// 司机类 /// </summary> public class Driver { public string Name { get; set; } public Driver(string name) { this.Name = name; } }
客户端调用:
class Program { static void Main(string[] args) { CarFactory carFactory = new CarFactory(); //小头爸爸开蓝色的车 Driver d1 = new Driver("小头爸爸"); Car c1=carFactory.GetCar("蓝色"); c1.Use(d1); //扁头妈妈开蓝色的车 Driver d2 = new Driver("扁头妈妈"); Car c2 = carFactory.GetCar("蓝色"); c2.Use(d2); if (c1.Equals(c2)) { Console.WriteLine("小头爸爸和扁头妈妈开的是同一辆车"); } //车库没有白色的车,就new一辆白色的车 Driver d3 = new Driver("大头儿子"); Car c3 = carFactory.GetCar("白色"); c3.Use(d3); Console.ReadKey(); } }
运行程序结果如下:我们可以看到小头爸爸和扁头妈妈用的是同一辆车,就是复用了一个实例。
在使用享元模式时一个最大的问题是分离出对象的外部状态和内部状态。我们把对象内部的不会受环境改变而改变的部分作为内部状态,如例子中车的颜色,车的颜色不会随着外部因素司机的不同而改变;外部状态指的是随环境改变而改变的部分,对车来说,司机就是外部状态,我们可以通过公共接口的参数来传入外部状态。
享元模式的使用场景:
当系统中大量使用某些相同或者相似的对象,这些对象要耗费大量的内存,并且这些对象剔除外部状态后可以通过一个对象来替代,这时可以考虑使用享元模式。在软件系统中享元模式大量用于各种池技术,如数据库连接对象池,字符串缓存池,HttpApplication池等。
享元模式的优点:
通过对象的复用减少了对象的数量,节省内存。
享元模式的缺点:
需要分离对象的外部状态和内部状态,使用不当会引起线程安全问题,提高了系统的复杂度。
行为型设计模式
行为型设计模式,主要关注对象和行为的分离,把不稳定的地方移出去,自己只写稳定的,能保证自身的稳定,一共有11种模式,分别为:【模版方法模式】【策略模式】【状态模式】【命令模式】【迭代器模式】【备忘录模式】【观察者模式】【中介者模式】【访问者模式】【责任链模式】【解释器模式】
模板方法模式
/// <summary> /// 银行客户端 /// </summary> public abstract class AbstractClient { public void Query(int id, string name, string password) { if (this.CheckUser(id, password)) { double balance = this.QueryBalance(id); double interest = this.CalculateInterest(balance); this.Show(name, balance, interest); } else { Console.WriteLine("账户密码错误"); } } public bool CheckUser(int id, string password) { return DateTime.Now < DateTime.Now.AddDays(1); } public double QueryBalance(int id) { return new Random().Next(10000, 1000000); } /// <summary> /// 活期 定期 利率不同 /// </summary> /// <param name="balance"></param> /// <returns></returns> public abstract double CalculateInterest(double balance); public virtual void Show(string name, double balance, double interest) { Console.WriteLine("尊敬的{0}客户,你的账户余额为:{1},利息为{2}", name, balance, interest); } }
然后我们声明两个子类,一个是活期用户,一个是定期用户,分别继承于上面的抽象类,代码如下:
/// <summary> /// 银行客户端 /// </summary> public class ClientVip : AbstractClient { /// <summary> /// 活期 定期 利率不同 /// </summary> /// <param name="balance"></param> /// <returns></returns> public override double CalculateInterest(double balance) { return balance * 0.005; } public override void Show(string name, double balance, double interest) { Console.WriteLine("尊贵的{0} vip客户,您的账户余额为:{1},利息为{2}", name, balance, interest); } }
/// <summary> /// 银行客户端 /// </summary> public class ClientRegular : AbstractClient { /// <summary> /// 活期 定期 利率不同 /// </summary> /// <param name="balance"></param> /// <returns></returns> public override double CalculateInterest(double balance) { return balance * 0.003; } }
这样不同的业务可以直接在子类中实现,相同的业务可以在父类中实现,这就是所谓的模板模式,有没有发现我们平常基本上都是这样的写,然后只是不晓得它有如此一个高大上的名字而已!模板设计模式,好像就只是把一个复杂的多步骤业务,然后定义一个父类(模板),模板负责完成流程,把步骤分解,固定不变的类定义为父类,各不相同的定义为子类,就是把部分行为做了分离,所以有时候设计模式没有那么神奇,只不过是把常用的东西跟场景结合,沉淀下来起个名字。
观察者模式
/// <summary> /// 只是为了把多个对象产生关系,方便保存和调用 /// 方法本身其实没用 /// </summary> public interface IObserver { void Action(); }
然后定一个多个实体分别继承于上面的接口,如下:
public class Chicken : IObserver { public void Action() { this.Woo(); } public void Woo() { Console.WriteLine("{0} Woo", this.GetType().Name); } } public class Dog : IObserver { public void Action() { this.Wang(); } public void Wang() { Console.WriteLine("{0} Wang", this.GetType().Name); } } public class Baby : IObserver { public void Action() { this.Cry(); } public void Cry() { Console.WriteLine("{0} Cry", this.GetType().Name); } }
然后定义一只猫如下:
public class Cat { public void Miao() { Console.WriteLine("{0} Miao.....", this.GetType().Name); new Chicken().Woo(); new Baby().Cry(); new Dog().Wang(); } }
这样写会触发猫的不稳定性,如果猫叫了一声,接着新增一种动作,那意味着要修改猫的miao的方法,也就是说猫不仅要瞄,还要触发各种动作,违背了单一职责,所以我们由此想到猫只管自己叫,然后具体其它的动作可以不可以甩锅给别人,所以我们将代码进行改版增加MiaoObserver和MiaoEvent如下:
public class Cat { public void Miao() { Console.WriteLine("{0} Miao.....", this.GetType().Name); new Chicken().Woo(); new Baby().Cry(); new Dog().Wang(); } private List<IObserver> _ObserverList = new List<IObserver>(); public void AddObserver(IObserver observer) { this._ObserverList.Add(observer); } public void MiaoObserver() { Console.WriteLine("{0} MiaoObserver.....", this.GetType().Name); if (this._ObserverList != null && this._ObserverList.Count > 0) { foreach (var item in this._ObserverList) { item.Action(); } } } private event Action MiaoHandler; public void MiaoEvent() { Console.WriteLine("{0} MiaoEvent.....", this.GetType().Name); if (this.MiaoHandler != null) { foreach (Action item in this.MiaoHandler.GetInvocationList()) { item.Invoke(); } } } }
然后调用的地方如下:
{ Console.WriteLine("***************Common******************"); Cat cat = new Cat(); cat.Miao(); } { Console.WriteLine("***************Observer******************"); Cat cat = new Cat(); cat.AddObserver(new Chicken()); cat.AddObserver(new Baby()); cat.AddObserver(new Dog()); cat.MiaoObserver(); } { Console.WriteLine("***************Observer******************"); Cat cat = new Cat(); cat.AddObserver(new Chicken()); cat.AddObserver(new Baby()); cat.AddObserver(new Dog()); cat.MiaoObserver(); }
以后增加动作只需要再调用端随时增加,顺序也随意修改,这样成功了保证了猫的稳定性的同时也实现了猫叫后触发了其它的动作!这就是所谓的观察者模式,把不稳定的地方移出去,自己只写稳定的,能保证自身的稳定!
责任链模式
从生活中的例子可以发现,某个请求可能需要几个人的审批,即使技术经理审批完了,还需要上一级的审批。这样的例子,还有公司中的请假,少于3天的,直属Leader就可以批准,3天到7天之内就需要项目经理批准,多余7天的就需要技术总监的批准了。介绍了这么多生活中责任链模式的例子的,下面具体给出面向对象中责任链模式的定义。
责任链模式指的是——某个请求需要多个对象进行处理,从而避免请求的发送者和接收之间的耦合关系。将这些对象连成一条链子,并沿着这条链子传递该请求,直到有对象处理它为止。
从责任链模式的定义可以发现,责任链模式涉及的对象只有处理者角色,但由于有多个处理者,它们具有共同的处理请求的方法,所以这里抽象出一个抽象处理者角色进行代码复用。这样分析下来,责任链模式的结构图也就不言而喻了,具体结构图如下所示。
主要涉及两个角色:
- 抽象处理者角色(Handler):定义出一个处理请求的接口。这个接口通常由接口或抽象类来实现。
- 具体处理者角色(ConcreteHandler):具体处理者接受到请求后,可以选择将该请求处理掉,或者将请求传给下一个处理者。因此,每个具体处理者需要保存下一个处理者的引用,以便把请求传递下去。
责任链模式是:请求的处理流程,沿着链子顺序执行,还运行链子扩展和订制,这是行为型设计模式的巅峰之作!
我们还是从业务场景出发:请假流程的审批,比如请假时间少于等于8小时,则PM可以审批;请假时长大于8小时小于等于16小时的,部门主管审批;请假时长大于16小于等于32个小时,公司主管可以审批;如果我们看到这个业务流程,很多人第一反应是写下面的代码:
if (context.Hour <= 8) { Console.WriteLine("PM审批通过"); } else if (context.Hour <= 16) { Console.WriteLine("部门主管审批通过"); } else if (context.Hour <= 32) { Console.WriteLine("公司主管审批通过"); } else { Console.WriteLine("************"); }
把整个业务流程写到上端,一旦修改流程,直接修改代码。所以我们可以通过了解业务逻辑来进行分析,我们审批的人有PM,Charge,Manager,CEO四个人,然后审批者都有名字,都有审批的这个功能,另外我们还需要一个申请人ApplyContext,所以我们创建一个基类如下:
/// <summary> /// 请假申请 /// </summary> public class ApplyContext { public int Id { get; set; } public string Name { get; set; } /// <summary> /// 请假时长 /// </summary> public int Hour { get; set; } public string Description { get; set; } public bool AuditResult { get; set; } public string AuditRemark { get; set; } }
public abstract class AbstractAuditor { public string Name { get; set; } public abstract void Audit(ApplyContext context); private AbstractAuditor _NextAuditor = null; public void SetNext(AbstractAuditor auditor) { this._NextAuditor = auditor; } protected void AuditNext(ApplyContext context) { if (this._NextAuditor != null) { this._NextAuditor.Audit(context); } else { context.AuditResult = false; context.AuditRemark = "不允许请假!"; } } }
然后创建PM,Charge,Manager,CEO如下
public class PM : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 8) { context.AuditResult = true; context.AuditRemark = "允许请假!"; } else { base.AuditNext(context); } } }
public class Charge: AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 16) { context.AuditResult = true; context.AuditRemark = "允许请假!"; } else { base.AuditNext(context); } } }
public class Manager : AbstractAuditor { public override void Audit(ApplyContext context) { Console.WriteLine($"This is {this.GetType().Name} {this.Name} Audit"); if (context.Hour <= 24) { context.AuditResult = true; context.AuditRemark = "允许请假!"; } else { base.AuditNext(context); } } }
然后进行调用,可以新增一个类组成链子形式的如下:
public class AuditorBuilder { /// <summary> /// 那就反射+配置文件 /// 链子的组成都可以通过配置文件 /// </summary> /// <returns></returns> public static AbstractAuditor Build() { AbstractAuditor pm = new PM() { Name = "张琪琪" }; AbstractAuditor charge = new Charge() { Name = "吴可可" }; AbstractAuditor ceo = new CEO() { Name = "加菲猫" }; pm.SetNext(pm); charge.SetNext(charge); ceo.SetNext(ceo); return pm; } }
调用如下:
ApplyContext context = new ApplyContext() { Id = 506, Name = "小新", Hour = 32, Description = "我周一要请假回家", AuditResult = false, AuditRemark = "" }; AbstractAuditor auditor = AuditorBuilder.Build(); auditor.Audit(context); if (!context.AuditResult) { Console.WriteLine("不干了!"); }
上面就是所谓的责任链设计模式,如果整个流程审批人修改,不用修改底层逻辑,而是直接把调用的地方修改即可。
以上就是三大类设计模式中典型的设计模式,其实没有什么设计模式是完美无缺的,一个设计模式就是解决一类的问题的,通常设计模式在解决一类问题的同时,还会带来别的问题,我们设计者要做的事儿,就是要扬长避短,充分发挥长处!
中介者模式介绍
中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系,中介者使各个对象之间不需要显式地相互引用,从而降低耦合性。在开发中我们会遇到各个对象相互引用的情况,每个对象都可以和多个对象进行交互,这时将会形成复杂的一对多结构的网状结构,各个对象之间过度耦合,这样不利于类的复用和扩展。如果引入了中介者模式,各个对象都通过中介者进行交互,那么对象之间的关系将变成一对一的星型结构。
我们采用园友LearningHard玩牌的例子来理解中介者模式的用法。在现实生活中,两个人打牌,如果某个人赢了会影响到对方的状态。标准中介者模式有抽象中介者角色,具体中介者角色、抽象同事类和具体同事类四个角色,其中打牌的人都是具体的同事类的对象,算账的平台是中介者对象。如果此时不采用中介者模式实现的话,则代码实现打牌的场景如下所示:
//抽象玩家类 public abstract class AbstractCardPlayer { public int MoneyCount { get; set; } public AbstractCardPlayer() { this.MoneyCount = 0; } public abstract void ChangeCount(int count, AbstractCardPlayer other); } //玩家A类 public class PlayerA : AbstractCardPlayer { public override void ChangeCount(int count, AbstractCardPlayer other) { this.MoneyCount += count; other.MoneyCount -= count; } } //玩家B类 public class PlayerB : AbstractCardPlayer { public override void ChangeCount(int count, AbstractCardPlayer other) { this.MoneyCount += count; other.MoneyCount -= count; } } class Program { static void Main(string[] args) { AbstractCardPlayer a = new PlayerA() { MoneyCount = 20 }; AbstractCardPlayer b = new PlayerB() { MoneyCount = 20 }; //玩家a赢了玩家b 5元 Console.WriteLine("a赢了b5元"); a.ChangeCount(5, b); Console.WriteLine($"玩家a现在有{a.MoneyCount}元"); Console.WriteLine($"玩家b现在有{b.MoneyCount}元"); //玩家b赢了玩家a 10元 Console.WriteLine("b赢了a10元"); b.ChangeCount(10, a); Console.WriteLine($"玩家a现在有{a.MoneyCount}元"); Console.WriteLine($"玩家b现在有{b.MoneyCount}元"); Console.ReadKey(); } }
运行结果如下:
上边的代码满足了玩牌的功能,但是有一些缺陷:我们看到上边栗子中算钱的功能是交给赢家的a.ChangeCount(count, b)方法来实现的,这时是赢家找输家要钱 赢家a和输家b是直接通信的。当玩家比较多的时候,例如a赢了,bcde四个玩家都会输5元,那么a就要和bcde玩家都要通信(多玩家方法改成:a.ChangeCount(count,b,c,d,e)),如b赢了同理,各个玩家组成了一个复杂的通信网络,就像上边的网状图,各个玩家过度耦合。如果我们引入一个中间人来负责统一结算,赢家就可以直接找中间人结算,不必直接找所有的输家要账了,代码如下:
//抽象玩家类 public abstract class AbstractCardPlayer { public int MoneyCount { get; set; } public AbstractCardPlayer() { this.MoneyCount = 0; } public abstract void ChangeCount(int count, AbstractMediator mediator); } //玩家A类 public class PlayerA : AbstractCardPlayer { //通过中介者来算账,不用直接找输家了 public override void ChangeCount(int count, AbstractMediator mediator) { mediator.AWin(count); } } //玩家B类 public class PlayerB : AbstractCardPlayer { public override void ChangeCount(int count, AbstractMediator mediator) { mediator.BWin(count); } } //抽象中介者 public abstract class AbstractMediator { //中介者必须知道所有同事 public AbstractCardPlayer A; public AbstractCardPlayer B; public AbstractMediator(AbstractCardPlayer a,AbstractCardPlayer b) { A = a; B = b; } public abstract void AWin(int count); public abstract void BWin(int count); } //具体中介者 public class Mediator : AbstractMediator { public Mediator(AbstractCardPlayer a,AbstractCardPlayer b):base(a,b){} public override void AWin(int count) { A.MoneyCount += count; B.MoneyCount -= count; } public override void BWin(int count) { A.MoneyCount -= count; B.MoneyCount += count; } } class Program { static void Main(string[] args) { AbstractCardPlayer a = new PlayerA() { MoneyCount = 20 }; AbstractCardPlayer b = new PlayerB() { MoneyCount = 20 }; AbstractMediator mediator = new Mediator(a, b); //玩家a赢了玩家b 5元 Console.WriteLine("a赢了b5元"); a.ChangeCount(5, mediator); Console.WriteLine($"玩家a现在有{a.MoneyCount}元"); Console.WriteLine($"玩家b现在有{b.MoneyCount}元"); //玩家b赢了玩家a 10元 Console.WriteLine("b赢了a10元"); b.ChangeCount(10, mediator); Console.WriteLine($"玩家a现在有{a.MoneyCount}元"); Console.WriteLine($"玩家b现在有{b.MoneyCount}元"); Console.ReadKey(); } }
运行结果和不用中介者的例子一致。我们可以看到中介者模式降低了各个同事对象的耦合,同事类之间不用直接通信,直接找中介者就行了,但是中介者模式并没有降低业务的复杂度,中介者将同事类间的复杂交互逻辑从业务代码中转移到了中介者类的内部。标准中介者模式有抽象中介者角色,具体中介者角色、抽象同事类和具体同事类四个角色,在实际开发中有时候没必要对具体中介者角色和具体用户角色进行抽象(如联合国作为一个中介者,负责调停各个国家纠纷,但是没必要把单独的联合国抽象成一个抽象中介者类;上边例子的抽象玩家类和抽象中介者类都是没必要的),我们可以根据具体的情况来来选择是否使用抽象中介者和抽象用户角色。
中介者模式优点:
1 降低了同事类交互的复杂度,将一对多转化成了一对一;
2 各个类之间的解耦;
3 符合迪米特原则。
中介者模式缺点:
1 业务复杂时中介者类会变得复杂难以维护。
迭代器模式介绍
迭代器模式主要用于遍历聚合对象,将聚合对象的遍历行为分离出来,抽象为一个迭代器来负责。迭代器模式用的十分普遍,C#/JAVA等高级语言都对迭代器进行了封装用于遍历数组,集合,列表等,因为各种高级语言都对这种模式做了很好的封装,所以这种模式的使用价值远大于它的学习价值,MartinFlower甚至在网站上提出过撤销这个设计模式,所以这里不打算介绍迭代器模式的概念和原理,而是介绍C#中的迭代器模式应用:C#中的枚举器和迭代器。
先看一个简单的例子:
static void Main(string[] args) { int[] arr = { 2,3,5,8}; foreach (int item in arr) { Console.WriteLine("item's Value is :{0}",item); } Console.ReadKey(); }
为什么数组能够通过foreach遍历呢?原因是数组实现了IEnumerable接口,IEumerable接口中只有一个成员方法:GetEnumerator(),这个方法返回一个枚举器对象,这个枚举器就是迭代器模式中的迭代器。枚举器可以依次返回请求中数组中的元素,它知道元素的顺序并跟踪元素在序列中的位置,然后返回请求的当前项,我们可以通过GetEnumerator方法获取枚举器对象。那么什么是枚举器呢?实现IEnumerator接口的类型就是枚举器,该接口有三个成员:
current :获取当前位置元素
MoveNext() :把枚举器位置前进到下一项,返回bool,表示位置是否有效(如果没有下一项返回false)
Reset() :把位置重置为原始状态的位置(有索引是一般为-1)
我们重新实现上边例子的foreach操作:
static void Main(string[] args) { int[] arr = { 2,3,5,8}; //获取arr的枚举器 IEnumerator ie = arr.GetEnumerator(); while (ie.MoveNext()) { int i = (int)ie.Current; Console.WriteLine("item's value is:{0}",i); } Console.ReadKey(); }
知道了foreach内部是怎么运行的后,我们就可以自己实现一个可以用foreach遍历的类了:自定义的类要实现IEnumerable接口的GetEnumerator方法,这个方法返回一个枚举器(就是一个继承IEnumerator接口的类型),以遍历自定义颜色集合为例,代码如下:
class Program { static void Main(string[] args) { ColorList colors = new ColorList(); //foreach遍历自定义的类型 foreach (var item in colors) { Console.WriteLine(item); } Console.ReadKey(); } /// <summary> /// 自定义类 ColorList实现IEnumerable接口 /// </summary> public class ColorList : IEnumerable { //实现GetEnumerator接口方法 public IEnumerator GetEnumerator() { return new ColorEnumrator(new string[]{ "red", "blue", "green", "pink" }); } } /// <summary> /// 自定义枚举器 /// </summary> public class ColorEnumrator : IEnumerator { string[] _colors; //位置索引 private int _position = -1; //枚举器构造方法 public ColorEnumrator(string[] theColors) { _colors = new string[theColors.Length]; for (int i = 0; i < theColors.Length; i++) { _colors[i] = theColors[i]; } } //获取当前项的值 public object Current { get { if (_position < 0 || _position > _colors.Length) { throw new Exception("超过边界了!"); } return _colors[_position]; } } //指向下一项 public bool MoveNext() { if (_position<_colors.Length-1) { _position++; return true; } return false; } //复位 public void Reset() { _position = -1; } } }
序运行结果如下:
总结:可枚举类型是实现了IEnumerable接口的类,IEnumerable接口只有一个成员GetEnumerator方法,用于获取枚举器(实现IEnumerator接口的实例)。
迭代器
通过手动实现IEnumerable接口的IEnumerator方法我们已经可以实现自定义可枚举类型了,在C#2.0中提供了更简单的创建可枚举类型的方式:迭代器。我们使用迭代器时,不用我们自己去手动创建IEnumerator实例,编译器会自动帮我们生成IEnumerator内部的Current,MoveNext和Reset方法。使用迭代器上边的例子可以简化为:
class Program { static void Main(string[] args) { ColorList colors = new ColorList(); //foreach遍历自定义的ColorList类型 foreach (var item in colors) { Console.WriteLine(item); } Console.ReadKey(); } } /// <summary> /// 自定义颜色集合,实现IEnumerable接口 /// </summary> public class ColorList : IEnumerable { //实现GetEnumerator接口方法 public IEnumerator GetEnumerator() { string[] colors= { "red", "green", "blue", "pink" }; for (int i = 0; i < colors.Length; i++) { //yield return的作用是指定下一项的内容 yield return colors[i]; } //想反向遍历时可以这样写 //for (int i = colors.Length-1; i >=0; i--) //{ // yield return colors[i]; //} } }
程序运行结果和手动写Enumerator的例子一致,我们可以看出使用迭代器来生成可枚举类型要简单很多,这并不是创建迭代器的过程简单了,而是微软让编译器帮我们自动生成了IEnumerator中的current,MoveNext(),Reset()等内容。
命令模式
命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。
A.命令模式的结构
既然,命令模式是实现把发出命令的责任和执行命令的责任分割开,然而中间必须有某个对象来帮助发出命令者来传达命令,使得执行命令的接收者可以收到命令并执行命令。例如,开学了,院领导说计算机学院要进行军训,计算机学院的学生要跑1000米,院领导的话也就相当于一个命令,他不可能直接传达给到学生,他必须让教官来发出命令,并监督学生执行该命令。在这个场景中,发出命令的责任是属于学院领导,院领导充当与命令发出者的角色,执行命令的责任是属于学生,学生充当于命令接收者的角色,而教官就充当于命令的发出者或命令请求者的角色,然而命令模式的精髓就在于把每个命令抽象为对象。从而命令模式的结构如下图所示:
从命令模式的结构图可以看出,它涉及到五个角色,它们分别是:
- 客户角色:发出一个具体的命令并确定其接受者。
- 命令角色:声明了一个给所有具体命令类实现的抽象接口
- 具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。
- 请求者角色:负责调用命令对象执行命令。
- 接受者角色:负责具体行为的执行。
B.命令模式的实现
现在,让我们以上面的军训的例子来实现一个命令模式,在实现之前,可以参考下命令模式的结构图来分析下实现过程。
军训场景中,具体的命令即是学生跑1000米,这里学生是命令的接收者,教官是命令的请求者,院领导是命令的发出者,即客户端角色。要实现命令模式,则必须需要一个抽象命令角色来声明约定,这里以抽象类来来表示。命令的传达流程是:
命令的发出者必须知道具体的命令、接受者和传达命令的请求者,对应于程序也就是在客户端角色中需要实例化三个角色的实例对象了。
命令的请求者负责调用命令对象的方法来保证命令的执行,对应于程序也就是请求者对象需要有命令对象的成员,并在请求者对象的方法内执行命令。
具体命令就是跑1000米,这自然属于学生的责任,所以是具体命令角色的成员方法,而抽象命令类定义这个命令的抽象接口。
有了上面的分析之后,具体命令模式的实现代码如下所示:
// 教官,负责调用命令对象执行请求 public class Invoke { public Command _command; public Invoke(Command command) { this._command = command; } public void ExecuteCommand() { _command.Action(); } } // 命令抽象类 public abstract class Command { // 命令应该知道接收者是谁,所以有Receiver这个成员变量 protected Receiver _receiver; public Command(Receiver receiver) { this._receiver = receiver; } // 命令执行方法 public abstract void Action(); } // public class ConcreteCommand :Command { public ConcreteCommand(Receiver receiver) : base(receiver) { } public override void Action() { // 调用接收的方法,因为执行命令的是学生 _receiver.Run1000Meters(); } } // 命令接收者——学生 public class Receiver { public void Run1000Meters() { Console.WriteLine("跑1000米"); } } // 院领导 class Program { static void Main(string[] args) { // 初始化Receiver、Invoke和Command Receiver r = new Receiver(); Command c = new ConcreteCommand(r); Invoke i = new Invoke(c); // 院领导发出命令 i.ExecuteCommand(); } }
c.命令模式的适用场景
在下面的情况下可以考虑使用命令模式:
- 系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
- 系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
- 如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
- 系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。
D.命令模式的优缺点
命令模式使得命令发出的一个和接收的一方实现低耦合,从而有以下的优点:
- 命令模式使得新的命令很容易被加入到系统里。
- 可以设计一个命令队列来实现对请求的Undo和Redo操作。
- 可以较容易地将命令写入日志。
- 可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。
命令模式的缺点:
- 使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。
E.总结
// 院领导 class Program { static void Main(string[] args) { // 行为的请求者和行为的实现者之间呈现一种紧耦合关系 Receiver r = new Receiver(); r.Run1000Meters(); } } public class Receiver { // 操作 public void Run1000Meters() { Console.WriteLine("跑1000米"); } }
命令模式的实现要点在于把某个具体的命令抽象化为具体的命令类,并通过加入命令请求者角色来实现将命令发送者对命令执行者的依赖分割开,在上面军训的例子中,如果不使用命令模式的话,则命令的发送者将对命令接收者是强耦合的关系,实现代码如下:
// 院领导 class Program { static void Main(string[] args) { // 行为的请求者和行为的实现者之间呈现一种紧耦合关系 Receiver r = new Receiver(); r.Run1000Meters(); } } public class Receiver { // 操作 public void Run1000Meters() { Console.WriteLine("跑1000米"); } }
状态者模式
A.状态者模式的结构
既然状态者模式是对已有对象的状态进行抽象,则自然就有抽象状态者类和具体状态者类,而原来已有对象需要保存抽象状态者类的引用,通过调用抽象状态者的行为来改变已有对象的行为。经过上面的分析,状态者模式的结构图也就很容易理解了,具体结构图如下图示。
从上图可知,状态者模式涉及以下三个角色:
-
Account类:维护一个State类的一个实例,该实例标识着当前对象的状态。
-
State类:抽象状态类,定义了一个具体状态类需要实现的行为约定。
-
SilveStater、GoldState和RedState类:具体状态类,实现抽象状态类的每个行为。
B.状态者模式的实现
下面,就以银行账户的状态来实现下状态者模式。银行账户根据余额可分为RedState、SilverState和GoldState。这些状态分别代表透支账号,新开账户和标准账户。账号余额在【-100.0,0.0】范围表示处于RedState状态,账号余额在【0.0 , 1000.0】范围表示处于SilverState,账号在【1000.0, 100000.0】范围表示处于GoldState状态。下面以这样的一个场景实现下状态者模式,具体实现代码如下所示:
namespace StatePatternSample { public class Account { public State State {get;set;} public string Owner { get; set; } public Account(string owner) { this.Owner = owner; this.State = new SilverState(0.0, this); } public double Balance { get {return State.Balance; }} // 余额 // 存钱 public void Deposit(double amount) { State.Deposit(amount); Console.WriteLine("存款金额为 {0:C}——", amount); Console.WriteLine("账户余额为 =:{0:C}", this.Balance); Console.WriteLine("账户状态为: {0}", this.State.GetType().Name); Console.WriteLine(); } // 取钱 public void Withdraw(double amount) { State.Withdraw(amount); Console.WriteLine("取款金额为 {0:C}——",amount); Console.WriteLine("账户余额为 =:{0:C}", this.Balance); Console.WriteLine("账户状态为: {0}", this.State.GetType().Name); Console.WriteLine(); } // 获得利息 public void PayInterest() { State.PayInterest(); Console.WriteLine("Interest Paid --- "); Console.WriteLine("账户余额为 =:{0:C}", this.Balance); Console.WriteLine("账户状态为: {0}", this.State.GetType().Name); Console.WriteLine(); } } // 抽象状态类 public abstract class State { // Properties public Account Account { get; set; } public double Balance { get; set; } // 余额 public double Interest { get; set; } // 利率 public double LowerLimit { get; set; } // 下限 public double UpperLimit { get; set; } // 上限 public abstract void Deposit(double amount); // 存款 public abstract void Withdraw(double amount); // 取钱 public abstract void PayInterest(); // 获得的利息 } // Red State意味着Account透支了 public class RedState : State { public RedState(State state) { // Initialize this.Balance = state.Balance; this.Account = state.Account; Interest = 0.00; LowerLimit = -100.00; UpperLimit = 0.00; } // 存款 public override void Deposit(double amount) { Balance += amount; StateChangeCheck(); } // 取钱 public override void Withdraw(double amount) { Console.WriteLine("没有钱可以取了!"); } public override void PayInterest() { // 没有利息 } private void StateChangeCheck() { if (Balance > UpperLimit) { Account.State = new SilverState(this); } } } // Silver State意味着没有利息得 public class SilverState :State { public SilverState(State state) : this(state.Balance, state.Account) { } public SilverState(double balance, Account account) { this.Balance = balance; this.Account = account; Interest = 0.00; LowerLimit = 0.00; UpperLimit = 1000.00; } public override void Deposit(double amount) { Balance += amount; StateChangeCheck(); } public override void Withdraw(double amount) { Balance -= amount; StateChangeCheck(); } public override void PayInterest() { Balance += Interest * Balance; StateChangeCheck(); } private void StateChangeCheck() { if (Balance < LowerLimit) { Account.State = new RedState(this); } else if (Balance > UpperLimit) { Account.State = new GoldState(this); } } } // Gold State意味着有利息状态 public class GoldState : State { public GoldState(State state) { this.Balance = state.Balance; this.Account = state.Account; Interest = 0.05; LowerLimit = 1000.00; UpperLimit = 1000000.00; } // 存钱 public override void Deposit(double amount) { Balance += amount; StateChangeCheck(); } // 取钱 public override void Withdraw(double amount) { Balance -= amount; StateChangeCheck(); } public override void PayInterest() { Balance += Interest * Balance; StateChangeCheck(); } private void StateChangeCheck() { if (Balance < 0.0) { Account.State = new RedState(this); } else if (Balance < LowerLimit) { Account.State = new SilverState(this); } } } class App { static void Main(string[] args) { // 开一个新的账户 Account account = new Account("Learning Hard"); // 进行交易 // 存钱 account.Deposit(1000.0); account.Deposit(200.0); account.Deposit(600.0); // 付利息 account.PayInterest(); // 取钱 account.Withdraw(2000.00); account.Withdraw(500.00); // 等待用户输入 Console.ReadKey(); } } }
上面代码的运行结果如下图所示:
从上图可以发现,进行存取款交易,会影响到Account内部的状态,由于状态的改变,从而影响到Account类行为的改变,而且这些操作都是发生在运行时的。
C.状态者模式的应用场景
在以下情况下可以考虑使用状态者模式。
-
当一个对象状态转换的条件表达式过于复杂时可以使用状态者模式。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简单化。
-
当一个对象行为取决于它的状态,并且它需要在运行时刻根据状态改变它的行为时,就可以考虑使用状态者模式。
D.状态者模式的优缺点
状态者模式的主要优点是:
-
将状态判断逻辑每个状态类里面,可以简化判断的逻辑。
-
当有新的状态出现时,可以通过添加新的状态类来进行扩展,扩展性好。
状态者模式的主要缺点是:
-
如果状态过多的话,会导致有非常多的状态类,加大了开销。
E.应用状态者模式完善中介者模式
// 抽象牌友类 public abstract class AbstractCardPartner { public int MoneyCount { get; set; } public AbstractCardPartner() { MoneyCount = 0; } public abstract void ChangeCount(int Count, AbstractMediator mediator); } // 牌友A类 public class ParterA : AbstractCardPartner { // 依赖与抽象中介者对象 public override void ChangeCount(int Count, AbstractMediator mediator) { mediator.ChangeCount(Count); } } // 牌友B类 public class ParterB : AbstractCardPartner { // 依赖与抽象中介者对象 public override void ChangeCount(int Count, AbstractMediator mediator) { mediator.ChangeCount(Count); } } // 抽象状态类 public abstract class State { protected AbstractMediator meditor; public abstract void ChangeCount(int count); } // A赢状态类 public class AWinState : State { public AWinState(AbstractMediator concretemediator) { this.meditor = concretemediator; } public override void ChangeCount(int count) { foreach (AbstractCardPartner p in meditor.list) { ParterA a = p as ParterA; // if (a != null) { a.MoneyCount += count; } else { p.MoneyCount -= count; } } } } // B赢状态类 public class BWinState : State { public BWinState(AbstractMediator concretemediator) { this.meditor = concretemediator; } public override void ChangeCount(int count) { foreach (AbstractCardPartner p in meditor.list) { ParterB b = p as ParterB; // 如果集合对象中时B对象,则对B的钱添加 if (b != null) { b.MoneyCount += count; } else { p.MoneyCount -= count; } } } } // 初始化状态类 public class InitState : State { public InitState() { Console.WriteLine("游戏才刚刚开始,暂时还有玩家胜出"); } public override void ChangeCount(int count) { // return; } } // 抽象中介者类 public abstract class AbstractMediator { public List<AbstractCardPartner> list = new List<AbstractCardPartner>(); public State State { get; set; } public AbstractMediator(State state) { this.State = state; } public void Enter(AbstractCardPartner partner) { list.Add(partner); } public void Exit(AbstractCardPartner partner) { list.Remove(partner); } public void ChangeCount(int count) { State.ChangeCount(count); } } // 具体中介者类 public class MediatorPater : AbstractMediator { public MediatorPater(State initState) : base(initState) { } } class Program { static void Main(string[] args) { AbstractCardPartner A = new ParterA(); AbstractCardPartner B = new ParterB(); // 初始钱 A.MoneyCount = 20; B.MoneyCount = 20; AbstractMediator mediator = new MediatorPater(new InitState()); // A,B玩家进入平台进行游戏 mediator.Enter(A); mediator.Enter(B); // A赢了 mediator.State = new AWinState(mediator); mediator.ChangeCount(5); Console.WriteLine("A 现在的钱是:{0}", A.MoneyCount);// 应该是25 Console.WriteLine("B 现在的钱是:{0}", B.MoneyCount); // 应该是15 // B 赢了 mediator.State = new BWinState(mediator); mediator.ChangeCount(10); Console.WriteLine("A 现在的钱是:{0}", A.MoneyCount);// 应该是25 Console.WriteLine("B 现在的钱是:{0}", B.MoneyCount); // 应该是15 Console.Read(); } }
策略模式
在现实生活中,策略模式的例子也非常常见,例如,中国的所得税,分为企业所得税、外商投资企业或外商企业所得税和个人所得税,针对于这3种所得税,针对每种,所计算的方式不同,个人所得税有个人所得税的计算方式,而企业所得税有其对应计算方式。如果不采用策略模式来实现这样一个需求的话,可能我们会定义一个所得税类,该类有一个属性来标识所得税的类型,并且有一个计算税收的CalculateTax()方法,在该方法体内需要对税收类型进行判断,通过if-else语句来针对不同的税收类型来计算其所得税。这样的实现确实可以解决这个场景吗,但是这样的设计不利于扩展,如果系统后期需要增加一种所得税时,此时不得不回去修改CalculateTax方法来多添加一个判断语句,这样明白违背了“开放——封闭”原则。此时,我们可以考虑使用策略模式来解决这个问题,既然税收方法是这个场景中的变化部分,此时自然可以想到对税收方法进行抽象。
前面介绍了策略模式用来解决的问题,下面具体给出策略的定义。策略模式是针对一组算法,将每个算法封装到具有公共接口的独立的类中,从而使它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
A.策略模式的结构
下面是策略模式的结构图:
该模式涉及到三个角色:
-
环境角色(Context):持有一个Strategy类的引用
-
抽象策略角色(Strategy):这是一个抽象角色,通常由一个接口或抽象类来实现。此角色给出所有具体策略类所需实现的接口。
-
具体策略角色(ConcreteStrategy):包装了相关算法或行为。
B.策略模式的实现
下面就以所得税的例子来实现下策略模式,具体实现代码如下所示:
namespace StrategyPattern { // 所得税计算策略 public interface ITaxStragety { double CalculateTax(double income); } // 个人所得税 public class PersonalTaxStrategy : ITaxStragety { public double CalculateTax(double income) { return income * 0.12; } } // 企业所得税 public class EnterpriseTaxStrategy : ITaxStragety { public double CalculateTax(double income) { return (income - 3500) > 0 ? (income - 3500) * 0.045 : 0.0; } } public class InterestOperation { private ITaxStragety m_strategy; public InterestOperation(ITaxStragety strategy) { this.m_strategy = strategy; } public double GetTax(double income) { return m_strategy.CalculateTax(income); } } class App { static void Main(string[] args) { // 个人所得税方式 InterestOperation operation = new InterestOperation(new PersonalTaxStrategy()); Console.WriteLine("个人支付的税为:{0}", operation.GetTax(5000.00)); // 企业所得税 operation = new InterestOperation(new EnterpriseTaxStrategy()); Console.WriteLine("企业支付的税为:{0}", operation.GetTax(50000.00)); Console.Read(); } } }
C.策略者模式在.NET中应用
在.NET Framework中也不乏策略模式的应用例子。例如,在.NET中,为集合类型ArrayList和List<T>提供的排序功能,其中实现就利用了策略模式,定义了IComparer接口来对比较算法进行封装,实现IComparer接口的类可以是顺序,或逆序地比较两个对象的大小,具体.NET中的实现可以使用反编译工具查看List.Sort(IComparer)的实现。其中List<T>就是承担着环境角色,而IComparer<T>接口承担着抽象策略角色,具体的策略角色就是实现了IComparer<T>接口的类,List<T>类本身实现了存在实现了该接口的类,我们可以自定义继承与该接口的具体策略类。
D.策略者模式的适用场景
在下面的情况下可以考虑使用策略模式:
-
一个系统需要动态地在几种算法中选择一种的情况下。那么这些算法可以包装到一个个具体的算法类里面,并为这些具体的算法类提供一个统一的接口。
-
如果一个对象有很多的行为,如果不使用合适的模式,这些行为就只好使用多重的if-else语句来实现,此时,可以使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象涉及的概念。
E.策略者模式的优缺点
策略模式的主要优点有:
-
策略类之间可以自由切换。由于策略类都实现同一个接口,所以使它们之间可以自由切换。
-
易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码。
-
避免使用多重条件选择语句,充分体现面向对象设计思想。
策略模式的主要缺点有:
-
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这点可以考虑使用IOC容器和依赖注入的方式来解决,关于IOC容器和依赖注入(Dependency Inject)的文章可以参考:IoC 容器和Dependency Injection 模式。
-
策略模式会造成很多的策略类。
访问者模式
访问者模式是封装一些施加于某种数据结构之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保存不变。访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操作之间的耦合度降低,使得操作集合可以相对自由地改变。
A.访问者模式的结构图
从上面描述可知,访问者模式是用来封装某种数据结构中的方法。具体封装过程是:每个元素接受一个访问者的调用,每个元素的Accept方法接受访问者对象作为参数传入,访问者对象则反过来调用元素对象的操作。具体的访问者模式结构图如下所示。
这里需要明确一点:访问者模式中具体访问者的数目和具体节点的数目没有任何关系。从访问者的结构图可以看出,访问者模式涉及以下几类角色。
-
抽象访问者角色(Vistor):声明一个活多个访问操作,使得所有具体访问者必须实现的接口。
-
具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口。
-
抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数。
-
具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。
-
结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。
B.访问者模式的实现
在讲诉访问者模式的实现时,我想先不用访问者模式的方式来实现某个场景。具体场景是——现在我想遍历每个元素对象,然后调用每个元素对象的Print方法来打印该元素对象的信息。如果此时不采用访问者模式的话,实现这个场景再简单不过了,具体实现代码如下所示:
namespace DonotUsevistorPattern { // 抽象元素角色 public abstract class Element { public abstract void Print(); } // 具体元素A public class ElementA : Element { public override void Print() { Console.WriteLine("我是元素A"); } } // 具体元素B public class ElementB : Element { public override void Print() { Console.WriteLine("我是元素B"); } } // 对象结构 public class ObjectStructure { private ArrayList elements = new ArrayList(); public ArrayList Elements { get { return elements; } } public ObjectStructure() { Random ran = new Random(); for (int i = 0; i < 6; i++) { int ranNum = ran.Next(10); if (ranNum > 5) { elements.Add(new ElementA()); } else { elements.Add(new ElementB()); } } } } class Program { static void Main(string[] args) { ObjectStructure objectStructure = new ObjectStructure(); // 遍历对象结构中的对象集合,访问每个元素的Print方法打印元素信息 foreach (Element e in objectStructure.Elements) { e.Print(); } Console.Read(); } } }
上面代码很准确了解决了我们刚才提出的场景,但是需求在时刻变化的,如果此时,我除了想打印元素的信息外,还想打印出元素被访问的时间,此时我们就不得不去修改每个元素的Print方法,再加入相对应的输入访问时间的输出信息。这样的设计显然不符合“开-闭”原则,即某个方法操作的改变,会使得必须去更改每个元素类。既然,这里变化的点是操作的改变,而每个元素的数据结构是不变的。所以此时就思考——能不能把操作于元素的操作和元素本身的数据结构分开呢?解开这两者的耦合度,这样如果是操作发现变化时,就不需要去更改元素本身了,但是如果是元素数据结构发现变化,例如,添加了某个字段,这样就不得不去修改元素类了。此时,我们可以使用访问者模式来解决这个问题,即把作用于具体元素的操作由访问者对象来调用。具体的实现代码如下所示:
namespace VistorPattern { // 抽象元素角色 public abstract class Element { public abstract void Accept(IVistor vistor); public abstract void Print(); } // 具体元素A public class ElementA :Element { public override void Accept(IVistor vistor) { // 调用访问者visit方法 vistor.Visit(this); } public override void Print() { Console.WriteLine("我是元素A"); } } // 具体元素B public class ElementB :Element { public override void Accept(IVistor vistor) { vistor.Visit(this); } public override void Print() { Console.WriteLine("我是元素B"); } } // 抽象访问者 public interface IVistor { void Visit(ElementA a); void Visit(ElementB b); } // 具体访问者 public class ConcreteVistor :IVistor { // visit方法而是再去调用元素的Accept方法 public void Visit(ElementA a) { a.Print(); } public void Visit(ElementB b) { b.Print(); } } // 对象结构 public class ObjectStructure { private ArrayList elements = new ArrayList(); public ArrayList Elements { get { return elements; } } public ObjectStructure() { Random ran = new Random(); for (int i = 0; i < 6; i++) { int ranNum = ran.Next(10); if (ranNum > 5) { elements.Add(new ElementA()); } else { elements.Add(new ElementB()); } } } } class Program { static void Main(string[] args) { ObjectStructure objectStructure = new ObjectStructure(); foreach (Element e in objectStructure.Elements) { // 每个元素接受访问者访问 e.Accept(new ConcreteVistor()); } Console.Read(); } } }
从上面代码可知,使用访问者模式实现上面场景后,元素Print方法的访问封装到了访问者对象中了(我觉得可以把Print方法封装到具体访问者对象中。),此时客户端与元素的Print方法就隔离开了。此时,如果需要添加打印访问时间的需求时,此时只需要再添加一个具体的访问者类即可。此时就不需要去修改元素中的Print()方法了。
C.访问者模式的应用场景
每个设计模式都有其应当使用的情况,那让我们看看访问者模式具体应用场景。如果遇到以下场景,此时我们可以考虑使用访问者模式。
-
如果系统有比较稳定的数据结构,而又有易于变化的算法时,此时可以考虑使用访问者模式。因为访问者模式使得算法操作的添加比较容易。
-
如果一组类中,存在着相似的操作,为了避免出现大量重复的代码,可以考虑把重复的操作封装到访问者中。(当然也可以考虑使用抽象类了)
-
如果一个对象存在着一些与本身对象不相干,或关系比较弱的操作时,为了避免操作污染这个对象,则可以考虑把这些操作封装到访问者对象中。
D.访问者模式的优缺点
访问者模式具有以下优点:
-
访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。
-
访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与"中介者模式"。
-
访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。
访问者模式也有如下的缺点:
-
增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。
E.总结
访问者模式是用来封装一些施加于某种数据结构之上的操作。它使得可以在不改变元素本身的前提下增加作用于这些元素的新操作,访问者模式的目的是把操作从数据结构中分离出来。
备忘录模式
备忘录模式的具体定义是:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。
A.备忘录模式的结构图
介绍完备忘录模式的定义之后,下面具体看看备忘录模式的结构图:
备忘录模式中主要有三类角色:
-
发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。
-
备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。
-
管理者角色:负责保存备忘录对象。
B.备忘录模式的实现
下面以备份手机通讯录为例子来实现了备忘录模式,具体的实现代码如下所示:
// 联系人 public class ContactPerson { public string Name { get; set; } public string MobileNum { get; set; } } // 发起人 public class MobileOwner { // 发起人需要保存的内部状态 public List<ContactPerson> ContactPersons { get; set; } public MobileOwner(List<ContactPerson> persons) { ContactPersons = persons; } // 创建备忘录,将当期要保存的联系人列表导入到备忘录中 public ContactMemento CreateMemento() { // 这里也应该传递深拷贝,new List方式传递的是浅拷贝, // 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝 // 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝 return new ContactMemento(new List<ContactPerson>(this.ContactPersons)); } // 将备忘录中的数据备份导入到联系人列表中 public void RestoreMemento(ContactMemento memento) { // 下面这种方式是错误的,因为这样传递的是引用, // 则删除一次可以恢复,但恢复之后再删除的话就恢复不了. // 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成 this.ContactPersons = memento.contactPersonBack; } public void Show() { Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count); foreach (ContactPerson p in ContactPersons) { Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum); } } } // 备忘录 public class ContactMemento { // 保存发起人的内部状态 public List<ContactPerson> contactPersonBack; public ContactMemento(List<ContactPerson> persons) { contactPersonBack = persons; } } // 管理角色 public class Caretaker { public ContactMemento ContactM { get; set; } } class Program { static void Main(string[] args) { List<ContactPerson> persons = new List<ContactPerson>() { new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"}, new ContactPerson() { Name = "Tony", MobileNum = "234565"}, new ContactPerson() { Name = "Jock", MobileNum = "231455"} }; MobileOwner mobileOwner = new MobileOwner(persons); mobileOwner.Show(); // 创建备忘录并保存备忘录对象 Caretaker caretaker = new Caretaker(); caretaker.ContactM = mobileOwner.CreateMemento(); // 更改发起人联系人列表 Console.WriteLine("----移除最后一个联系人--------"); mobileOwner.ContactPersons.RemoveAt(2); mobileOwner.Show(); // 恢复到原始状态 Console.WriteLine("-------恢复联系人列表------"); mobileOwner.RestoreMemento(caretaker.ContactM); mobileOwner.Show(); Console.Read(); } }
具体的运行结果如下图所示:
从上图可以看出,刚开始通讯录中有3个联系人,然后移除以后一个后变成2个联系人了,最后恢复原来的联系人列表后,联系人列表中又恢复为3个联系人了。
上面代码只是保存了一个还原点,即备忘录中只保存了3个联系人的数据,但是,如果想备份多个还原点怎么办呢?即恢复到3个人后,又想恢复到前面2个人的状态,这时候可能你会想,这样没必要啊,到时候在删除不就好了。但是如果在实际应用中,可能我们发了很多时间去创建通讯录中只有2个联系人的状态,恢复到3个人的状态后,发现这个状态时错误的,还是原来2个人的状态是正确的,难道我们又去花之前的那么多时间去重复操作吗?这显然不合理,如果就思考,能不能保存多个还原点呢?保存多个还原点其实很简单,只需要保存多个备忘录对象就可以了。具体实现代码如下所示:
namespace MultipleMementoPattern { // 联系人 public class ContactPerson { public string Name { get; set; } public string MobileNum { get; set; } } // 发起人 public class MobileOwner { public List<ContactPerson> ContactPersons { get; set; } public MobileOwner(List<ContactPerson> persons) { ContactPersons = persons; } // 创建备忘录,将当期要保存的联系人列表导入到备忘录中 public ContactMemento CreateMemento() { // 这里也应该传递深拷贝,new List方式传递的是浅拷贝, // 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝 // 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝 return new ContactMemento(new List<ContactPerson>(this.ContactPersons)); } // 将备忘录中的数据备份导入到联系人列表中 public void RestoreMemento(ContactMemento memento) { if (memento != null) { // 下面这种方式是错误的,因为这样传递的是引用, // 则删除一次可以恢复,但恢复之后再删除的话就恢复不了. // 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成 this.ContactPersons = memento.ContactPersonBack; } } public void Show() { Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count); foreach (ContactPerson p in ContactPersons) { Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum); } } } // 备忘录 public class ContactMemento { public List<ContactPerson> ContactPersonBack {get;set;} public ContactMemento(List<ContactPerson> persons) { ContactPersonBack = persons; } } // 管理角色 public class Caretaker { // 使用多个备忘录来存储多个备份点 public Dictionary<string, ContactMemento> ContactMementoDic { get; set; } public Caretaker() { ContactMementoDic = new Dictionary<string, ContactMemento>(); } } class Program { static void Main(string[] args) { List<ContactPerson> persons = new List<ContactPerson>() { new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"}, new ContactPerson() { Name = "Tony", MobileNum = "234565"}, new ContactPerson() { Name = "Jock", MobileNum = "231455"} }; MobileOwner mobileOwner = new MobileOwner(persons); mobileOwner.Show(); // 创建备忘录并保存备忘录对象 Caretaker caretaker = new Caretaker(); caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 更改发起人联系人列表 Console.WriteLine("----移除最后一个联系人--------"); mobileOwner.ContactPersons.RemoveAt(2); mobileOwner.Show(); // 创建第二个备份 Thread.Sleep(1000); caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 恢复到原始状态 Console.WriteLine("-------恢复联系人列表,请从以下列表选择恢复的日期------"); var keyCollection = caretaker.ContactMementoDic.Keys; foreach (string k in keyCollection) { Console.WriteLine("Key = {0}", k); } while (true) { Console.Write("请输入数字,按窗口的关闭键退出:"); int index = -1; try { index = Int32.Parse(Console.ReadLine()); } catch { Console.WriteLine("输入的格式错误"); continue; } ContactMemento contactMentor = null; if (index < keyCollection.Count && caretaker.ContactMementoDic.TryGetValue(keyCollection.ElementAt(index), out contactMentor)) { mobileOwner.RestoreMemento(contactMentor); mobileOwner.Show(); } else { Console.WriteLine("输入的索引大于集合长度!"); } } } } }
这样就保存了多个状态,客户端可以选择恢复的状态点,具体运行结果如下所示:
C.备忘录模式的适用场景
在以下情况下可以考虑使用备忘录模式:
-
如果系统需要提供回滚操作时,使用备忘录模式非常合适。例如文本编辑器的Ctrl+Z撤销操作的实现,数据库中事务操作。
D.备忘录模式的优缺点
备忘录模式具有以下优点:
-
如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。
-
备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。
当然,备忘录模式也存在一定的缺点:
-
在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。
E.总结
备忘录模式主要思想是——利用备忘录对象来对保存发起人的内部状态,当发起人需要恢复原来状态时,再从备忘录对象中进行获取,在实际开发过程也应用到这点,例如数据库中的事务处理。
【解释器模式】,英文名称是:Interpreter Pattern。按老规矩,先从名称上来看看这个模式,个人的最初理解“解释器”和Google的中英翻译功能类似。如果有一天你去国外旅游去了,比如去美国吧,美国人是讲英语的,我们是讲汉语的,如果英语听不懂,讲不好,估计沟通就完蛋了,不能沟通,估计玩的就很难尽兴了,因为有很多景点的解说你可能不明白(没有中文翻译的情况下,一般情况会有的)。所以我们需要一个软件,可以把中英文互译,那彼此就可以更好的理解对方的意思,我感觉翻译软件也可以称得上是解释器,把你不懂的解释成你能理解的。我们写代码,需要编译器把我们写的代码编译成机器可以理解的机器语言,从这方面来讲,C#的编译器也是一种解释器。
解释器模式的详细介绍
动机(Motivate)
在软件构建过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
意图(Intent)
给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。 ——《设计模式》GoF
结构图(Structure)
模式的组成 可以看出,在解释器模式的结构图有以下角色: (1)、抽象表达式(AbstractExpression):定义解释器的接口,约定解释器的解释操作。其中的Interpret接口,正如其名字那样,它是专门用来解释该解释器所要实现的功能。
(2)、终结符表达式(Terminal Expression):实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
(3)、非终结符表达式(Nonterminal Expression):文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,“+”就是非终结符,解析“+”的解释器就是一个非终结符表达式。
(4)、环境角色(Context):这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。
(5)、客户端(Client):指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成使用解释器对象描述的抽象语法树,然后调用解释操作。
解释器模式的代码实现
在很多场合都需要把数字转换成中文,我们就可以使用解释器来实现该功能,把给定的数字解释成符合语法规范的汉字表示法。实现代码如下:
namespace InterpreterPattern { // 抽象表达式 public abstract class Expression { protected Dictionary<string, int> table = new Dictionary<string, int>(9); protected Expression() { table.Add("一", 1); table.Add("二", 2); table.Add("三", 3); table.Add("四", 4); table.Add("五", 5); table.Add("六", 6); table.Add("七", 7); table.Add("八", 8); table.Add("九", 9); } public virtual void Interpreter(Context context) { if (context.Statement.Length == 0) { return; } foreach (string key in table.Keys) { int value = table[key]; if (context.Statement.EndsWith(key + GetPostFix())) { context.Data += value * this.Multiplier(); context.Statement = context.Statement.Substring(0, context.Statement.Length - this.GetLength()); } if (context.Statement.EndsWith("零")) { context.Statement = context.Statement.Substring(0, context.Statement.Length - 1); } } } public abstract string GetPostFix(); public abstract int Multiplier(); //这个可以通用,但是对于个位数字例外,所以用虚方法 public virtual int GetLength() { return this.GetPostFix().Length + 1; } } //个位表达式 public sealed class GeExpression : Expression { public override string GetPostFix() { return ""; } public override int Multiplier() { return 1; } public override int GetLength() { return 1; } } //十位表达式 public sealed class ShiExpression : Expression { public override string GetPostFix() { return "十"; } public override int Multiplier() { return 10; } } //百位表达式 public sealed class BaiExpression : Expression { public override string GetPostFix() { return "百"; } public override int Multiplier() { return 100; } } //千位表达式 public sealed class QianExpression : Expression { public override string GetPostFix() { return "千"; } public override int Multiplier() { return 1000; } } //万位表达式 public sealed class WanExpression : Expression { public override string GetPostFix() { return "万"; } public override int Multiplier() { return 10000; } public override void Interpreter(Context context) { if (context.Statement.Length == 0) { return; } ArrayList tree = new ArrayList(); tree.Add(new GeExpression()); tree.Add(new ShiExpression()); tree.Add(new BaiExpression()); tree.Add(new QianExpression()); foreach (string key in table.Keys) { if (context.Statement.EndsWith(GetPostFix())) { int temp = context.Data; context.Data = 0; context.Statement = context.Statement.Substring(0, context.Statement.Length - this.GetLength()); foreach (Expression exp in tree) { exp.Interpreter(context); } context.Data = temp + context.Data * this.Multiplier(); } } } } //亿位表达式 public sealed class YiExpression : Expression { public override string GetPostFix() { return "亿"; } public override int Multiplier() { return 100000000; } public override void Interpreter(Context context) { ArrayList tree = new ArrayList(); tree.Add(new GeExpression()); tree.Add(new ShiExpression()); tree.Add(new BaiExpression()); tree.Add(new QianExpression()); foreach (string key in table.Keys) { if (context.Statement.EndsWith(GetPostFix())) { int temp = context.Data; context.Data = 0; context.Statement = context.Statement.Substring(0, context.Statement.Length - this.GetLength()); foreach (Expression exp in tree) { exp.Interpreter(context); } context.Data = temp + context.Data * this.Multiplier(); } } } } //环境上下文 public sealed class Context { private string _statement; private int _data; public Context(string statement) { this._statement = statement; } public string Statement { get { return this._statement; } set { this._statement = value; } } public int Data { get { return this._data; } set { this._data = value; } } } class Program { static void Main(string[] args) { string roman = "五亿七千三百零二万六千四百五十二"; //分解:((五)亿)((七千)(三百)(零)(二)万) //((六千)(四百)(五十)(二)) Context context = new Context(roman); ArrayList tree = new ArrayList(); tree.Add(new GeExpression()); tree.Add(new ShiExpression()); tree.Add(new BaiExpression()); tree.Add(new QianExpression()); tree.Add(new WanExpression()); tree.Add(new YiExpression()); foreach (Expression exp in tree) { exp.Interpreter(context); } Console.Write(context.Data); Console.Read(); } } }
解释器模式的实现要点
使用Interpreter模式来表示文法规则,从而可以使用面向对象技巧方便地“扩展”文法。
Interpreter模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具。
(1)、解释器模式的主要优点有:
1】、易于改变和扩展文法。
2】、每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
3】、实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
4】、增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”
(2)、解释器模式的主要缺点有:
1】、对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
2】、执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
(3)、在下面的情况下可以考虑使用解释器模式:
Interpreter模式的应用场合是Interpreter模式应用中的难点,只有满足“业务规则频繁变化,且类似的模式不断重复出现,并且容易抽象为语法规则的问题”才适合使用Interpreter模式。
1】、当一个语言需要解释执行,并可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式(如XML文档解释、正则表达式等领域)
3】、一个语言的文法较为简单.
4】、当执行效率不是关键和主要关心的问题时可考虑解释器模式(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。)
NET 解释器模式的实现
正则表达式就是一个典型的解释器。ASP.NET中,把aspx文件转化为dll时,会对html语言进行处理,这个处理过程也包含了解释器的模式在里面。Interpreter模式其实有Composite模式的影子,但它们解决的问题是不一样的。