茌平做创建网站公司,线上做笔记的网站,做企业手机网站,网页设计与制作教程 刘瑞新创建型模式#xff1a;单例#xff08;Singleton#xff09;模式#xff1a;某个类只能生成一个实例#xff0c;该类提供了一个全局访问点供外部获取该实例#xff0c;其拓展是有限多例模式。
原型#xff08;Prototype#xff09;模式#xff1a;将一个对象作为原型单例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/// 单例类一个构造对象很耗时耗资源类型/// 懒汉式单例模式/// /summarypublic class Singleton{/// summary/// 构造函数耗时耗资源/// /summaryprivate 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 促进线程安全 让线程按顺序操作/// /summaryprivate static volatile Singleton _Singleton null;//因为单例会有线程安全问题所以会加锁的操作 private static readonly object Singleton_Lock new object();/// summary/// 2 公开的静态方法提供对象实例/// 双重if加锁会提高性能/// /summary/// returns/returnspublic 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/// 单例类一个构造对象很耗时耗资源类型 /// /// 饿汉式/// /summarypublic class SingletonSecond{private static SingletonSecond _SingletonSecond null;/// summary/// 1 构造函数耗时耗资源/// /summaryprivate 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配置文件/// /summarystatic SingletonSecond(){_SingletonSecond new SingletonSecond();Console.WriteLine(SingletonSecond 被启动);}public static SingletonSecond CreateInstance(){return _SingletonSecond;}//饿汉式 只要使用类就会被构造}
}
第三种声明如下静态变量初始化时走私有构造函数 namespace SingletonPattern
{/// summary/// 单例类一个构造对象很耗时耗资源类型/// 饿汉式/// /summarypublic class SingletonThird{/// summary/// 构造函数耗时耗资源/// /summaryprivate SingletonThird(){long lResult 0;for (int i 0; i 10000000; i){lResult i;}Thread.Sleep(1000);Console.WriteLine({0}被构造一次, this.GetType().Name);}/// summary/// 静态字段在第一次使用这个类之前由CLR保证初始化且只初始化一次/// 这个比构造函数还早/// /summaryprivate static SingletonThird _SingletonThird new SingletonThird();public static SingletonThird CreateInstance(){return _SingletonThird;}//饿汉式 只要使用类就会被构造}
}
C单例一般运用的场景
单例是保证全局唯一的一个实例主要是应对一些特殊情况比如数据库连接池(内置资源再比如流水号或者订单号生成器使用同一个变量保证初始值是同一个 原型设计模式
原型设计模式是在单例的基础上面升级了一下然后把对象从内存的层面复制了一下然后返回一个新的对象但是又不走构造函数
注意原型模式是一个新的对象新的地址但是copy仅仅是浅拷贝而不是深拷贝所以有时候需要注意一下
A.原型模式的声明如下 namespace SingletonPattern
{/// summary/// 原型模式单例的基础上升级了一下把对象从内存层面复制了一下/// 然后返回是个新对象但是又不是new出来的不走构造函数/// /summarypublic class Prototype{/// summary/// 构造函数耗时耗资源/// /summaryprivate Prototype(){long lResult 0;for (int i 0; i 10000000; i){lResult i;}Thread.Sleep(2000);Console.WriteLine({0}被构造一次, this.GetType().Name);}/// summary/// 3 全局唯一静态 重用这个变量/// /summaryprivate static volatile Prototype _Prototype new Prototype();/// summary/// 2 公开的静态方法提供对象实例/// /summary/// returns/returnspublic static Prototype CreateInstance(){Prototype prototype (Prototype)_Prototype.MemberwiseClone();return prototype;}}
}
扩展链接C#中的深复制和浅复制在C#中克隆对象
B原型模式适用的场景
1创建新对象成本较大例如初始化时间长占用CPU多或占太多网络资源新对象可以通过复制已有对象来获得如果相似对象则可以对其成员变量稍作修改。
2系统要保存对象的状态而对象的状态很小。
3需要避免使用分层次的工厂类来创建分层次的对象并且类的实例对象只有一个或很少的组合状态通过复制原型对象得到新实例可以比使用构造函数创建一个新实例更加方便。 简单工厂(不属于23种设计模式)
不直接new然后把对象的创建转移到工厂中这种是细节没有消失只是转移了矛盾并没有消除矛盾而是把矛盾集中在同一个工厂中
创建一个简单工厂的实例如下 public interface IRace{/// summary/// show出王者/// /summaryvoid ShowKing();}/// summary/// War3种族之一/// /summarypublic 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种族之一/// /summarypublic class ORC : IRace{public void ShowKing(){Console.WriteLine(The King of {0} is {1}, this.GetType().Name, Grubby);}}/// summary/// War3种族之一/// /summarypublic 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 nameraceType/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”文件中找到相关定义具体定义如下 httpHandlersadd path*.axd verb* typeSystem.Web.HttpNotFoundHandler validateTrue /add path*.aspx verb* typeSystem.Web.UI.PageHandlerFactory validateTrue /add path*.ashx verb* typeSystem.Web.UI.SimpleHandlerFactory validateTrue //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/// 技术好的建造者/// /summarypublic class GoodBuilder : Builder{private StringBuilder house new StringBuilder();public override void Dadiji(){house.Append(深地基--);//这里一般是new一个部件添加到实例中如 house.Dijinew Diji(深地基)//为了演示方便 用sringBuilder表示一个复杂的房子string表示房子的部件}public override void FenShua(){house.Append(粉刷光滑--);}public override void QiZhuan(){house.Append(砌砖整齐--);}public string GetHouse(){return house.Append(好质量房子建成了).ToString();}}/// summary/// 技术差的建造者/// /summarypublic 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/// 数据访问接口/// /summarypublic interface IHelper{void AddT();void DeleteT();void UpdateT();void QueryT();}
然后下面的类分别要实现这个接口 public class SqlserverHelper : IHelper
{public void AddT(){Console.WriteLine(This is {0} Add, this.GetType().Name);}public void DeleteT(){Console.WriteLine(This is {0} Delete, this.GetType().Name);}public void UpdateT(){Console.WriteLine(This is {0} Update, this.GetType().Name);}public void QueryT(){Console.WriteLine(This is {0} Query, this.GetType().Name);}
} public class MysqlHelper : IHelper
{public void AddT(){Console.WriteLine(This is {0} Add, this.GetType().Name);}public void DeleteT(){Console.WriteLine(This is {0} Delete, this.GetType().Name);}public void UpdateT(){Console.WriteLine(This is {0} Update, this.GetType().Name);}public void QueryT(){Console.WriteLine(This is {0} Query, this.GetType().Name);}
}
然后我们调用的时候如下 Console.WriteLine(*****************************);
{IHelper helper new SqlserverHelper();helper.AddProgram();helper.DeleteProgram();helper.UpdateProgram();helper.QueryProgram();
}
Console.WriteLine(*****************************);
{IHelper helper new MysqlHelper();helper.AddProgram();helper.DeleteProgram();helper.UpdateProgram();helper.QueryProgram();
}
程序已经确定好了规范都要实现IHelper所以我们都可以使用IHelper来接收但是现在我们新增一个RedisHelper第三方的接口如下 /// summary
/// 第三方提供的 openstack servicestack
/// 不能修改
/// /summary
public class RedisHelper
{public RedisHelper(){Console.WriteLine($构造RedisHelper);}public void AddRedisT(){Console.WriteLine(This is {0} Add, this.GetType().Name);}public void DeleteRedisT(){Console.WriteLine(This is {0} Delete, this.GetType().Name);}public void UpdateRedisT(){Console.WriteLine(This is {0} Update, this.GetType().Name);}public void QueryRedisT(){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 AddT(){base.AddRedisT();}public void DeleteT(){base.DeleteRedisT();}public void QueryT(){base.QueryRedisT();}public void UpdateT(){base.UpdateRedisT();}
}
第二种通过组合分为属性注入构造函数注入方法注入三种方式的方式来改善新增类如下 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 AddT(){this._RedisHelper.AddRedisT();}public void DeleteT(){this._RedisHelper.DeleteRedisT();}public void QueryT(){this._RedisHelper.QueryRedisT();}public void UpdateT(){this._RedisHelper.UpdateRedisT();}}
这样的两种方式改善代码后然后可以通过下面调用 //继承 既满足现有的规范调用又没有修改RedisHelper //类适配器模式Console.WriteLine(*****************************);{IHelper helper new RedisHelperInherit();helper.AddProgram();helper.DeleteProgram();helper.UpdateProgram();helper.QueryProgram();}//组合 既满足现有的规范调用又没有修改RedisHelper //对象适配器Console.WriteLine(*****************************);{IHelper helper new RedisHelperObject();helper.AddProgram();helper.DeleteProgram();helper.UpdateProgram();helper.QueryProgram();}
A我们上面说了组合是优于继承的具体分为以下两点来解释
1侵入性二者都会先构造一个redishelper继承是强侵入的父类的东西子类必须有 2灵活性继承只为一个类服务结构可以为多个类型服务属性注入构造函数注入方法注入 三种
B适配器主要是解决重构的问题新东西和旧系统不吻合通过继承/组合进行适配 代理模式 通过代理业务类去完成对真实业务类的调用代理类不能扩展业务功能比如我们常见的代理如FQ代理火车票代理VPN代理
下面我们还是通过代码来解释何为代理比如我们现在有个实际业务的接口如下 /// summary
/// 业务接口
/// /summary
public interface ISubject
{/// summary/// get/// /summary/// returns/returnsbool GetSomething();/// summary/// do/// /summaryvoid 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/// 火车站查询火车票/// /summarypublic bool GetSomething(){Console.WriteLine(坐车去火车站看看余票信息。。。);Thread.Sleep(3000);Console.WriteLine(到火车站看到是有票的);return true;}/// summary/// 火车站买票/// /summarypublic 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 Dictionarystring, bool ProxyDictionary new Dictionarystring, 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/// 一个普通的公开课学员,学习公开课/// /summarypublic 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/// 付费 上课前要预习 /// 上课学习/// /summarypublic 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;//用了组合加overridepublic BaseStudentDecorator(AbstractStudent student){this._Student student;}public override void Study(){this._Student.Study();}
}
然后有的学生要付费有的学生要做作业有的学生要视频回放针对于上面三个功能我们直接定义三个类分别如下 /// summary/// 父类是BaseStudentDecorator爷爷类AbstractStudent/// /summarypublic class StudentVideoDecorator : BaseStudentDecorator{public StudentVideoDecorator(AbstractStudent student): base(student)//表示父类的构造函数{}public override void Study(){base.Study();Console.WriteLine(视频代码回看);}}/// summary/// 父类是BaseStudentDecorator爷爷类AbstractStudent/// /summarypublic 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子系统/// /summarypublic class CPU{public void CPUStart(){Console.WriteLine(CPU is start...);}public void CPUShutdown(){Console.WriteLine(CPU is shot down...);}}/// summary/// 内存子系统/// /summarypublic class Memory{public void MemoryStart(){Console.WriteLine(Memory is start...);}public void MemoryShutdown(){Console.WriteLine(Memory is shot down...);}}/// summary/// 硬盘子系统/// /summarypublic class Disk{public void DiskStart(){Console.WriteLine(Disk is start...);}public void DiskShutdown(){Console.WriteLine(Disk is shot down...);}}
门面类Computer代码: /// summary/// 电脑 facede角色/// /summarypublic 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和RoleDalUserDal返回的数据格式为[名字张三角色ID3],RoleDal层返回的数据格式[角色ID:3,角色名管理员] 门面角色Bll层负责具体业务汇总子系统的功能这里使用UserDal和RoleDal两个子系统角色的功能返回的数据格式[名字张三 角色名管理员] 客户端角色UI层通过Bll层直接拿到格式如[名字张三 角色名管理员]的数据。
外观模式的优点 1.隐藏了系统的复杂性让客户端使用系统功能时变很简单 2.实现客户端和子系统间的解耦。
外观模式的缺点 1.不符合开闭原则如客户端要使用更多功能时不仅仅在子系统进行添加或修改操作也必须修改门面层。
桥接模式介绍 桥接模式用于将抽象化和实现化解耦使得两者可以独立变化。在面向对象中用通俗的话说明一个类可以通过多角度来分类每一种分类都可能变化那么就把多角度分离出来让各个角度都能独立变化降低各个角度间的耦合。这样说可能不太好理解举一个画几何图形的例子我们画的几何图形可以按照形状和颜色两个角度的进行分类按形状分类分为圆形、长方形、三角形按照颜色分类分为蓝色图形、黄色图形和红色图形而形状和颜色都是可以添加的比如我们也可以添加五角星形状颜色可以添加一个绿色。如果按继承来实现的话如图1所示我们需要的具体的子类就有9种形状种类*颜色种类如果我们添加一个五角星形状则必须再添加蓝色五角星黄色五角星和红色五角星三个具体子类添加一种颜色也一样需要添加这个颜色的各种形状。当我们的形状和颜色的种类都很多时就需要很多的具体子类造成子类爆炸。 画图的例子只有两个角度的分类当一个类有更多角度分类时具体子类种类分类1种类*分类2种类*分类3种类...就更多了。这时我们可以用桥接模式优化将形状和颜色通过继承生产的强耦合关系改成弱耦合的关联关系这里采用了组合大于继承的思想。如下图采用桥接模式时如果我们想添加一个五角星只需要添加一个形状类的子类五角星接即可不需要再去添加各种颜色的具体五角星了如果我们想要一个蓝色五角星就将五角星和蓝色进行组合来获取。这样设计降低了形状和颜色的耦合减少了具体子类的种类。 桥接模式的角色
Abstraction抽象化生成的类如形状类
Implementor行为实现接口抽象化后关注的其他的特性如例子中颜色接口。注意我们可以把颜色抽象化生成抽象类把形状作为行为实现接口
RefinedAbstraction抽象类子类如圆形长方形等
ConcreteImplementor行为实现接口的实现类如黄色红色等 画几何图形例子的代码实现 形状抽象类和三种子类的形状 public abstract class Shape{//形状内部包含了另一个维度colorprotected IColor color;public void SetColor(IColor color){this.color color;}//设置形状public abstract void Draw();}/// summary/// 圆形/// /summarypublic class Circle : Shape{public override void Draw(){color.Paint(圆形);}}/// summary/// 长方形/// /summarypublic class Rectangle : Shape{public override void Draw(){color.Paint(长方形);}}/// summary/// 三角形/// /summarypublic class Triangle : Shape{public override void Draw(){color.Paint(三角形);}}
颜色接口和三种实现类 /// summary/// 颜色接口/// /summarypublic interface IColor{void Paint(string shape);}/// summary/// 蓝色/// /summarypublic class Blue : IColor{public void Paint(string shape){Console.WriteLine($蓝色的{shape});}}/// summary/// 黄色/// /summarypublic class Yellow : IColor{public void Paint(string shape){Console.WriteLine($黄色的{shape});}}/// summary/// 红色/// /summarypublic 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/// 抽象部件 定义了树枝和树叶的公共属性和接口/// /summarypublic 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);}
员工类相当于树叶没有下一级 //具体员工树形结构的Leafpublic 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/// 部门类相当于树枝/// /summarypublic class Depart : Component{public Depart(string name) : base(name){this.name name;}//添加子节点public ListComponent childrennew ListComponent();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/// 具体的车类/// /summarypublic 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/// 车库/// /summarypublic class CarFactory{private Dictionarystring, Car carPoolnew Dictionarystring, 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/// 司机类/// /summarypublic 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 c1carFactory.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 namebalance/param/// returns/returnspublic 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 namebalance/param/// returns/returnspublic 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/// 银行客户端/// /summarypublic class ClientRegular : AbstractClient{/// summary/// 活期 定期 利率不同/// /summary/// param namebalance/param/// returns/returnspublic 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 ListIObserver _ObserverList new ListIObserver();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(************);}
把整个业务流程写到上端一旦修改流程直接修改代码。所以我们可以通过了解业务逻辑来进行分析我们审批的人有PMChargeManagerCEO四个人然后审批者都有名字都有审批的这个功能另外我们还需要一个申请人ApplyContext所以我们创建一个基类如下 /// summary/// 请假申请/// /summarypublic class ApplyContext{public int Id { get; set; }public string Name { get; set; }/// summary/// 请假时长/// /summarypublic 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 不允许请假;}}
}
然后创建PMChargeManagerCEO如下 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/returnspublic 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(items 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(items 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接口/// /summarypublic class ColorList : IEnumerable{//实现GetEnumerator接口方法public IEnumerator GetEnumerator(){return new ColorEnumrator(new string[]{ red, blue, green, pink });}}/// summary/// 自定义枚举器/// /summarypublic 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接口/// /summarypublic 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中的currentMoveNext()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和CommandReceiver 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.00.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{// Propertiespublic 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){// Initializethis.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 ListAbstractCardPartner list new ListAbstractCardPartner();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);// 应该是25Console.WriteLine(B 现在的钱是{0}, B.MoneyCount); // 应该是15// B 赢了mediator.State new BWinState(mediator);mediator.ChangeCount(10);Console.WriteLine(A 现在的钱是{0}, A.MoneyCount);// 应该是25Console.WriteLine(B 现在的钱是{0}, B.MoneyCount); // 应该是15Console.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和ListT提供的排序功能其中实现就利用了策略模式定义了IComparer接口来对比较算法进行封装实现IComparer接口的类可以是顺序或逆序地比较两个对象的大小具体.NET中的实现可以使用反编译工具查看List.Sort(IComparer)的实现。其中ListT就是承担着环境角色而IComparerT接口承担着抽象策略角色具体的策略角色就是实现了IComparerT接口的类ListT类本身实现了存在实现了该接口的类我们可以自定义继承与该接口的具体策略类。
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();}// 具体元素Apublic class ElementA : Element{ public override void Print(){Console.WriteLine(我是元素A);}}// 具体元素Bpublic 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();}// 具体元素Apublic class ElementA :Element{public override void Accept(IVistor vistor){// 调用访问者visit方法vistor.Visit(this);}public override void Print(){Console.WriteLine(我是元素A);}}// 具体元素Bpublic 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 ListContactPerson ContactPersons { get; set; }public MobileOwner(ListContactPerson persons){ContactPersons persons;}// 创建备忘录将当期要保存的联系人列表导入到备忘录中 public ContactMemento CreateMemento(){// 这里也应该传递深拷贝new List方式传递的是浅拷贝// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝// 如果ContactPerson包括非string的引用类型就会有问题所以这里也应该用序列化传递深拷贝return new ContactMemento(new ListContactPerson(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 ListContactPerson contactPersonBack;public ContactMemento(ListContactPerson persons){contactPersonBack persons;}}// 管理角色public class Caretaker{public ContactMemento ContactM { get; set; }}class Program{static void Main(string[] args){ListContactPerson persons new ListContactPerson(){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 ListContactPerson ContactPersons { get; set; }public MobileOwner(ListContactPerson persons){ContactPersons persons;}// 创建备忘录将当期要保存的联系人列表导入到备忘录中 public ContactMemento CreateMemento(){// 这里也应该传递深拷贝new List方式传递的是浅拷贝// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝// 如果ContactPerson包括非string的引用类型就会有问题所以这里也应该用序列化传递深拷贝return new ContactMemento(new ListContactPerson(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 ListContactPerson ContactPersonBack {get;set;}public ContactMemento(ListContactPerson persons){ContactPersonBack persons;}}// 管理角色public class Caretaker{// 使用多个备忘录来存储多个备份点public Dictionarystring, ContactMemento ContactMementoDic { get; set; }public Caretaker(){ContactMementoDic new Dictionarystring, ContactMemento();}}class Program{static void Main(string[] args){ListContactPerson persons new ListContactPerson(){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.备忘录模式的适用场景 在以下情况下可以考虑使用备忘录模式 如果系统需要提供回滚操作时使用备忘录模式非常合适。例如文本编辑器的CtrlZ撤销操作的实现数据库中事务操作。
D.备忘录模式的优缺点 备忘录模式具有以下优点 如果某个操作错误地破坏了数据的完整性此时可以使用备忘录模式将数据恢复成原来正确的数据。 备份的状态数据保存在发起人角色之外这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理而备忘录角色又是由管理者角色管理符合单一职责原则。 当然备忘录模式也存在一定的缺点 在实际的系统中可能需要维护多个备份需要额外的资源这样对资源的消耗比较严重。
E.总结 备忘录模式主要思想是——利用备忘录对象来对保存发起人的内部状态当发起人需要恢复原来状态时再从备忘录对象中进行获取在实际开发过程也应用到这点例如数据库中的事务处理。 解释器模式
【解释器模式】英文名称是Interpreter Pattern。按老规矩先从名称上来看看这个模式个人的最初理解“解释器”和Google的中英翻译功能类似。如果有一天你去国外旅游去了比如去美国吧美国人是讲英语的我们是讲汉语的如果英语听不懂讲不好估计沟通就完蛋了不能沟通估计玩的就很难尽兴了因为有很多景点的解说你可能不明白没有中文翻译的情况下一般情况会有的。所以我们需要一个软件可以把中英文互译那彼此就可以更好的理解对方的意思我感觉翻译软件也可以称得上是解释器把你不懂的解释成你能理解的。我们写代码需要编译器把我们写的代码编译成机器可以理解的机器语言从这方面来讲C#的编译器也是一种解释器。
解释器模式的详细介绍
动机Motivate
在软件构建过程中如果某一特定领域的问题比较复杂类似的模式不断重复出现如果使用普通的编程方式来实现将面临非常频繁的变化。在这种情况下将特定领域的问题表达为某种语法规则下的句子然后构建一个解释器来解释这样的句子从而达到解决问题的目的。
意图Intent
给定一个语言定义它的文法的一种表示并定义一种解释器这个解释器使用该表示来解释语言中的句子。 ——《设计模式》GoF
结构图Structure 模式的组成 可以看出在解释器模式的结构图有以下角色 1、抽象表达式(AbstractExpression)定义解释器的接口约定解释器的解释操作。其中的Interpret接口正如其名字那样它是专门用来解释该解释器所要实现的功能。
2、终结符表达式(Terminal Expression)实现了抽象表达式角色所要求的接口主要是一个interpret()方法文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式RR1R2在里面R1和R2就是终结符对应的解析R1和R2的解释器就是终结符表达式。
3、非终结符表达式(Nonterminal Expression)文法中的每一条规则都需要一个具体的非终结符表达式非终结符表达式一般是文法中的运算符或者其他关键字比如公式RR1R2中“”就是非终结符解析“”的解释器就是一个非终结符表达式。
4、环境角色(Context)这个角色的任务一般是用来存放文法中各个终结符所对应的具体值比如RR1R2我们给R1赋值100给R2赋值200。这些信息需要存放到环境角色中很多情况下我们使用Map来充当环境角色就足够了。
5、客户端Client指的是使用解释器的客户端通常在这里将按照语言的语法做的表达式转换成使用解释器对象描述的抽象语法树然后调用解释操作。
解释器模式的代码实现
在很多场合都需要把数字转换成中文我们就可以使用解释器来实现该功能把给定的数字解释成符合语法规范的汉字表示法。实现代码如下 namespace InterpreterPattern
{// 抽象表达式public abstract class Expression{protected Dictionarystring, int table new Dictionarystring, 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文档解释、正则表达式等领域
2】、一些重复出现的问题可以用一种简单的语言来进行表达。
3】、一个语言的文法较为简单.
4】、当执行效率不是关键和主要关心的问题时可考虑解释器模式注高效的解释器通常不是通过直接解释抽象语法树来实现的而是需要将它们转换成其他形式使用解释器模式的执行效率并不高。
NET 解释器模式的实现
正则表达式就是一个典型的解释器。ASP.NET中把aspx文件转化为dll时会对html语言进行处理这个处理过程也包含了解释器的模式在里面。Interpreter模式其实有Composite模式的影子但它们解决的问题是不一样的。