当前位置: 首页 > news >正文

C# 23设计模式备忘

创建型模式:单例(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;}}
}

扩展链接:C#中的深复制和浅复制(在C#中克隆对象)

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;//用了组合加overridepublic 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.不符合开闭原则,如客户端要使用更多功能时,不仅仅在子系统进行添加或修改操作,也必须修改门面层。

桥接模式介绍

  桥接模式用于将抽象化和实现化解耦,使得两者可以独立变化。在面向对象中用通俗的话说明:一个类可以通过多角度来分类,每一种分类都可能变化,那么就把多角度分离出来让各个角度都能独立变化,降低各个角度间的耦合。这样说可能不太好理解,举一个画几何图形的例子:我们画的几何图形可以按照形状和颜色两个角度的进行分类,按形状分类,分为圆形、长方形、三角形,按照颜色分类分为蓝色图形、黄色图形和红色图形,而形状和颜色都是可以添加的,比如我们也可以添加五角星形状,颜色可以添加一个绿色。如果按继承来实现的话,如图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>/// 圆形/// </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);}

员工类,相当于树叶,没有下一级:

//具体员工,树形结构的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>/// 部门类,相当于树枝/// </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和CommandReceiver r = new Receiver();Command c = new ConcreteCommand(r);Invoke i = new Invoke(c);// 院领导发出命令i.ExecuteCommand();}}

c.命令模式的适用场景

   在下面的情况下可以考虑使用命令模式:

  1. 系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
  2. 系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
  3. 如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
  4. 系统需要使用命令模式作为“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.状态者模式的结构

  既然状态者模式是对已有对象的状态进行抽象,则自然就有抽象状态者类和具体状态者类,而原来已有对象需要保存抽象状态者类的引用,通过调用抽象状态者的行为来改变已有对象的行为。经过上面的分析,状态者模式的结构图也就很容易理解了,具体结构图如下图示。

  

img

  从上图可知,状态者模式涉及以下三个角色:

  • 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{// 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 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);// 应该是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和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方法接受访问者对象作为参数传入,访问者对象则反过来调用元素对象的操作。具体的访问者模式结构图如下所示。

img

  这里需要明确一点:访问者模式中具体访问者的数目和具体节点的数目没有任何关系。从访问者的结构图可以看出,访问者模式涉及以下几类角色。

  • 抽象访问者角色(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.备忘录模式的结构图

 介绍完备忘录模式的定义之后,下面具体看看备忘录模式的结构图:

img

  备忘录模式中主要有三类角色:

  • 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。

  • 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。

  • 管理者角色:负责保存备忘录对象。

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();}}

  具体的运行结果如下图所示:

img

  从上图可以看出,刚开始通讯录中有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("输入的索引大于集合长度!");}}     }}
}

  这样就保存了多个状态,客户端可以选择恢复的状态点,具体运行结果如下所示:

img

C.备忘录模式的适用场景

 在以下情况下可以考虑使用备忘录模式:

  • 如果系统需要提供回滚操作时,使用备忘录模式非常合适。例如文本编辑器的Ctrl+Z撤销操作的实现,数据库中事务操作。

D.备忘录模式的优缺点

 备忘录模式具有以下优点:

  • 如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。

  • 备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。

  当然,备忘录模式也存在一定的缺点:

  • 在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。

E.总结

  备忘录模式主要思想是——利用备忘录对象来对保存发起人的内部状态,当发起人需要恢复原来状态时,再从备忘录对象中进行获取,在实际开发过程也应用到这点,例如数据库中的事务处理。

解释器模式

【解释器模式】,英文名称是:Interpreter Pattern。按老规矩,先从名称上来看看这个模式,个人的最初理解“解释器”和Google的中英翻译功能类似。如果有一天你去国外旅游去了,比如去美国吧,美国人是讲英语的,我们是讲汉语的,如果英语听不懂,讲不好,估计沟通就完蛋了,不能沟通,估计玩的就很难尽兴了,因为有很多景点的解说你可能不明白(没有中文翻译的情况下,一般情况会有的)。所以我们需要一个软件,可以把中英文互译,那彼此就可以更好的理解对方的意思,我感觉翻译软件也可以称得上是解释器,把你不懂的解释成你能理解的。我们写代码,需要编译器把我们写的代码编译成机器可以理解的机器语言,从这方面来讲,C#的编译器也是一种解释器。

解释器模式的详细介绍

动机(Motivate)

在软件构建过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。

意图(Intent)

给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。       ——《设计模式》GoF

结构图(Structure)

img

模式的组成 可以看出,在解释器模式的结构图有以下角色: (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文档解释、正则表达式等领域)

2】、一些重复出现的问题可以用一种简单的语言来进行表达。

3】、一个语言的文法较为简单.

4】、当执行效率不是关键和主要关心的问题时可考虑解释器模式(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。)

NET 解释器模式的实现

正则表达式就是一个典型的解释器。ASP.NET中,把aspx文件转化为dll时,会对html语言进行处理,这个处理过程也包含了解释器的模式在里面。Interpreter模式其实有Composite模式的影子,但它们解决的问题是不一样的。

相关文章:

C# 23设计模式备忘

创建型模式&#xff1a;单例&#xff08;Singleton&#xff09;模式&#xff1a;某个类只能生成一个实例&#xff0c;该类提供了一个全局访问点供外部获取该实例&#xff0c;其拓展是有限多例模式。 原型&#xff08;Prototype&#xff09;模式&#xff1a;将一个对象作为原型&…...

STL中的迭代器模式:将算法与数据结构分离

目录 1.概述 2.容器类 2.1.序列容器 2.2.关联容器 2.3.容器适配器 2.4.数组 3.迭代器 4.重用标准迭代器 5.总结 1.概述 在之前&#xff0c;我们讲了迭代器设计模式&#xff0c;分析了它的结构、角色以及优缺点&#xff1a; 设计模式之迭代器模式-CSDN博客 在 STL 中&a…...

TCP、UDP详解

目录 1.区别 1.1 概括 1.2 详解 2.TCP 2.1 内容 2.2 可靠传输 2.2.1 确认应答 2.2.2 超时重传 2.2.3 连接管理 三次握手 四次挥手 2.2.4 滑动窗口 2.2.5 流量控制 2.2.6 拥塞控制 2.2.7 延时应答 2.2.8 捎带应答 2.2.9 面向字节流 2.2.10 异常情况的处理 1.…...

【脚本工具库】批量下采样图像(附源码)

在图像处理领域&#xff0c;我们经常需要对大批量图像进行下采样操作&#xff0c;以便减小图像的尺寸和文件大小&#xff0c;这对于节省存储空间和提高处理速度非常有帮助。手动操作不仅耗时&#xff0c;而且容易出错。为了解决这个问题&#xff0c;我们可以编写一个Python脚本…...

Web渗透:文件包含漏洞

Ⅱ.远程文件包含 远程文件包含漏洞&#xff08;Remote File Inclusion, RFI&#xff09;是一种Web应用程序漏洞&#xff0c;允许攻击者通过URL从远程服务器包含并执行文件&#xff1b;RFI漏洞通常出现在动态包含文件的功能中&#xff0c;且用户输入未经适当验证和过滤。接着我…...

什么是yum源?如何对其进行配置?

哈喽&#xff0c;大家好呀&#xff01;这里是码农后端。今天来聊一聊Linux下的yum源及其配置相关的内容。简单来说&#xff0c;yum源就相当于一个管理软件的工具&#xff0c;可以想象成一个很大的仓库&#xff0c;里面存放着各种我们所需要的软件包及其依赖。 一、Linux下软件包…...

Node.js全栈指南:认识MIME和HTTP

MIME&#xff0c;全称 “多用途互联网邮件扩展类型”。 这名称相当学术&#xff0c;用人话来说就是&#xff1a; 我们浏览一个网页的时候&#xff0c;之所以能看到 html 文件展示成网页&#xff0c;图片可以正常显示&#xff0c;css 样式能正常影响网页效果&#xff0c;js 脚…...

基于weixin小程序智慧物业系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;用户管理&#xff0c;员工管理&#xff0c;房屋管理&#xff0c;缴费管理&#xff0c;车位管理&#xff0c;报修管理 工作人员账号功能包括&#xff1a;系统首页&#xff0c;维…...

成功解决​​​​​​​TypeError: __call__() got an unexpected keyword argument ‘first_int‘

成功解决TypeError: __call__() got an unexpected keyword argument first_int 目录 解决问题 解决思路 解决方法 T1、直接调用原始函数 T2、检查装饰器实现 T3、使用不同的调用方式 解决问题 result = multiply(**arguments) File "D:\ProgramData\Anaconda3\Li…...

vue3用自定义指令实现按钮权限

1&#xff0c;编写permission.ts文件 在src/utils/permission.ts import type { Directive } from "vue"; export const permission:Directive{// 在绑定元素的父组件被挂载后调用mounted(el,binding){// el&#xff1a;指令所绑定的元素&#xff0c;可以用来直接操…...

Nuxt3:当前页面滚动到指定位置

在Nuxt 3中&#xff0c;如果你想让当前页面跳转到指定位置&#xff0c;可以使用scrollIntoView方法。你需要给目标位置的元素添加一个ref引用&#xff0c;然后通过程序调用该ref来执行滚动。 以下是一个简单的例子&#xff1a; <template><div><!-- 其他内容 …...

word图题表题公式按照章节编号(不用题注)

预期效果&#xff1a; 其中3表示第三章&#xff0c;4表示第3章里的第4个图。标题、公式编号也是类似的。 为了达到这种按照章节编号的效果&#xff0c;原本可以用插入题注里的“包含章节编号” 但实际情况是&#xff0c;这不仅需要一级标题的序号是用“开始->多级列表”自动…...

最小生成树模型

文章目录 题单最小生成树模型1.[最短网络(prim)](https://www.acwing.com/problem/content/1142/)2. [局域网(kruskul)](https://www.acwing.com/problem/content/1143/)3. [繁忙的都市](https://www.acwing.com/problem/content/1144/)4. [ 联络员 ](https://www.acwing.com/p…...

基于盲信号处理的声音分离-基于改进的信息最大化的ICA算法

基于信息最大化的ICA算法的主要依据是使输入端与输出端的互信息达到最大&#xff0c;且输出各个分量之间的相关性最小化&#xff0c;即输出各个分量之间互信息量最小化&#xff0c;其算法的系统框图如图所示。 基于信息最大化的ICA算法的主要依据是使输入端与输出端的互信息达到…...

如何在Qt Designer中管理QSplitter

问题描述 当按下按钮时&#xff0c;我希望弹出一个对话框&#xff0c;用户可以在其中选择内容并最终按下 ‘Ok’ 按钮。我想在这个对话框中放置一个 QSplitter&#xff0c;左侧面板将显示树状结构&#xff0c;右侧将显示其他内容。如何正确实现这一点&#xff1f; 从 Qt 的示…...

关于新零售的一些思考

本文作为2024上半年大量输入之后的核心思考之一。工作到一定阶段之后&#xff0c;思考的重要性越来越高&#xff0c;后续会把自己的个人思考记录在这个新系列《施展爱思考》。背景是上半年面临业务转型从电商到新零售&#xff0c;本文是相关大量输入之后的思考&#xff0c;对新…...

C++初学者指南-2.输入和输出---从输入流错误中恢复

C初学者指南-2.输入和输出—从输入流错误中恢复 文章目录 C初学者指南-2.输入和输出---从输入流错误中恢复怎么了&#xff1f;解决方案&#xff1a;出错后重置输入流 怎么了&#xff1f; 示例&#xff1a;连续输入 int main () {cout << "i? ";int i 0;cin…...

毫秒级响应!清科优能应用 TDengine 建设虚拟电厂运营管理平台

小T导读&#xff1a;在清科优能的虚拟电厂运营管理平台建设中&#xff0c;项目初期预计涉及约一万台设备、总数据采集量达数十万&#xff0c;在数据库选择上&#xff0c;其希望能支持至少两千台设备的并发数据处理。本文介绍了清科优能的数据库选型经验以及最终应用效果&#x…...

【Ubuntu noble】apt 无法安装软件 Unable to locate package vim

宿主机以及 docker 无法定位软件包 将 /etc/apt/sources.list.d/ubuntu.sources 修改为以下内容&#xff08;主要是 Suites 字段增加了noble noble-updates&#xff09; Types: deb URIs: http://archive.ubuntu.com/ubuntu/ Suites: noble noble-updates noble-backports Com…...

Instagram APIj接口——快速获取Ins帖子媒体内容下载链接

一、引言 在社交媒体蓬勃发展的今天&#xff0c;Instagram已成为用户分享照片、视频和精彩瞬间的首选平台。然而&#xff0c;对于很多用户来说&#xff0c;想要保存或分享Instagram上的精彩内容却常常遇到困扰。为了解决这个问题&#xff0c;我们精心打造了一款全新的Instagra…...

Java基础(四)——字符串、StringBuffer、StringBuilder、StringJoiner

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…...

吐血推荐!3款视频生成工具,全部国产,都免费

AI视频大模型的爆发&#xff0c;让创作爆款视频不再是专业人士的能力。 今天二师兄给大家推荐3款免费的视频生成工具。 01 可灵 推荐指数 &#xff1a; 五颗星 先看效果 可灵大模型测试 可灵大模型是快手AI团队自主研发的视频生成大模型&#xff0c;具备强大的视频创作能力&a…...

【Web3】Web3.js 启动!并解决Web3 is not a constructor报错

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 本节教大家如何启动Web3.js 目录 Web3 启动&#xff01; 于是很愉快的报错 创建实例&#xff01; 出来了 Web3&#xff1a;模块…...

算法训练营第六十七天 | 卡码网110 字符串接龙、卡码网105 有向图的完全可达性、卡码网106 岛屿的周长

卡码网110 字符串接龙 这题一开始用的邻接表dfs&#xff0c;不幸超时 #include <iostream> #include <list> #include <string> #include <vector> using namespace std;int minLen 501;bool count(string a, string b) {int num 0;for (int i 0; …...

搭建 MySQL MHA

搭建 MySQL MHA 搭建 MySQL MHA实验拓扑图实验环境实验思路MHA架构故障模拟 实验部署数据库安装主从复制部署时间同步主服务器配置从服务器配置创建链接 MHA搭建安装依赖的环境安装 node 组件安装 manager 组件配置无密码认证在 manager 节点上配置 MHA管理 mysql 节点服务器创…...

python中的线程与进程

一、线程与进程 在计算机科学中&#xff0c;理解线程和进程的区别是重要的基础知识。这些概念对于多任务操作和并发编程尤为关键。下面将详细介绍线程与进程的区别、特点和各自的使用场景。 1.1 进程&#xff08;Process&#xff09; 进程是操作系统分配资源的基本单位。每个进…...

网络安全筑基篇——反序列化漏洞

目录 序列化是什么&#xff1f; 反序列化又是什么&#xff1f; 反序列化漏洞的危害 代码样例 常见的魔术方法 修复方式有哪些&#xff1f; 常见的反序列化漏洞 Shiro反序列化漏洞 Fastjson反序列化漏洞 序列化是什么&#xff1f; 将实例化对象转换成字节流的过程 反序…...

帝国cms定时审核并更新的方法

比如你网站采集了成千上万篇文章&#xff0c;不可能一下子全部放出来的&#xff0c;所以为了模拟人工发布&#xff0c;那么就需要定时审核发布文章内容&#xff0c;本文内容核心解决了更加个性化的逼真模拟人工更新网站内容。 第一&#xff1a;首先要满足你的表中有未审核的数据…...

一个简单好用安全的开源交互审计系统,支持SSH,Telnet,Kubernetes协议

前言 在当今的企业网络环境中&#xff0c;远程访问和交互审计成为了保障网络安-全的重要组成部分。然而&#xff0c;现有的解-决方案往往存在一些痛点&#xff0c;如复杂的配置、有限的协议支持、以及审计功能的不足。这些问题不仅增加了IT管理员的负担&#xff0c;也为企业的…...

使用Spring Boot和WebSocket实现实时通信

使用Spring Boot和WebSocket实现实时通信 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将探讨如何在Spring Boot应用中使用WebSocket实现实时通信&am…...

【Vue】集成富文本编辑器

这文章使用的是wangeditor插件&#xff0c;官网地址&#xff1a;wangEditor&#xff0c;这个比较简单 安装 npm i wangeditor --save 使用 <div id"editor"></div>import E from "wangeditor"const editor new E("#editor") e…...

【论文阅读】--Popup-Plots: Warping Temporal Data Visualization

弹出图&#xff1a;扭曲时态数据可视化 摘要1 引言2 相关工作3 弹出图3.1 椭球模型3.1.1 水平轨迹3.1.2 垂直轨迹3.1.3 组合轨迹 3.2 视觉映射与交互 4 实施5 结果6 评估7 讨论8 结论和未来工作致谢参考文献 期刊: IEEE Trans. Vis. Comput. Graph.&#xff08;发表日期: 2019&…...

重建大师引擎数0,本地引擎设置改不了,空三在跑,这样是正常的吗?

答&#xff1a;任务目录和引擎监控目录并没有按照网络集群设置&#xff0c;需要调整为网络路径。 重建大师是一款专为超大规模实景三维数据生产而设计的集群并行处理软件&#xff0c;输入倾斜照片&#xff0c;激光点云&#xff0c;POS信息及像控点&#xff0c;输出高精度彩色网…...

APM教程-SkyWalking安装和配置

SkyWalking简介 APM (Application Performance Management) 即应用性能管理&#xff0c;属于IT运维管理&#xff08;ITOM)范畴。主要是针对企业 关键业务的IT应用性能和用户体验的监测、优化&#xff0c;提高企业IT应用的可靠性和质量&#xff0c;保证用户得到良好的服务&#…...

斯坦福大学 AI 研究部门推出的“7 周人工智能学习计划”

①AI Python 基础&#xff0c;包括计算机原理、语法、判断语句等&#xff1b; ②AI Python 进阶&#xff0c;涉及 Linux 命令、多任务编程等&#xff1b; ③机器学习&#xff0c;涵盖算法、数据结构等&#xff1b; ④计算机视觉与图像处理&#xff0c;包含图像分类、目标检测…...

World of Warcraft [CLASSIC] plugin lua

World of Warcraft [CLASSIC] plugin lua 魔兽世界lua脚本插件 World of Warcraft API - Wowpedia - Your wiki guide to the World of Warcraft D:\World of Warcraft\_classic_\Interface\AddOns zwf.lua function CountdownFunc()CountdownFrame CreateFrame("Fram…...

背靠广汽、小马智行,如祺出行打得过滴滴和百度吗?

©自象限原创 作者丨艾AA 编辑丨薛黎 北京时间6月14日凌晨&#xff0c;在特斯拉股东大会上&#xff0c;马斯克阐述了对Robotaxi&#xff08;自动驾驶出租车&#xff09;商业模式的构想——特斯拉不仅会运营自己的无人驾驶出租车车队&#xff0c;还可以让特斯拉车主们的爱…...

CCSP自考攻略+经验总结

备考攻略 备考攻略准备阶段通读阶段精度阶段总复习阶段刷题阶段命运审判 写到最后 备考攻略 趁着对ssp知识点的理解还在&#xff0c;开始ccsp的考证之路&#xff0c;文章结构还是按照cissp备考篇的结构梳理。本次备考和cissp的离职在家备考不同&#xff0c;ccsp是在职利用非工…...

面试突击:ArrayList源码详解

本文已收录于&#xff1a;https://github.com/danmuking/all-in-one&#xff08;持续更新&#xff09; 前言 哈喽&#xff0c;大家好&#xff0c;我是 DanMu。ArrayList 是我们日常开发中不可避免要使用到的一个类&#xff0c;并且在面试过程中也是一个非常高频的知识点&#…...

力扣每日一题:2734. 执行子串操作后的字典序最小字符串

题目链接 脑子比较笨&#xff0c;分三种情况考虑&#xff1a; 以a开头。xxa&#xff0c;a在中间。 对于情况2还有两种可能&#xff1a; 1. 全是a&#xff0c;最后一个元素需要替换成z&#xff0c;因为必须执行一次操作。 2. aaaxxa&#xff0c;中间有一段非a&#xff0c;将这…...

C++11中std::thread的使用

C11 引入了 std::thread&#xff0c;它是用于创建和管理线程的标准库类。以下是详细的讲解&#xff0c;包括如何使用 std::thread 进行线程创建、管理和参数传递等操作。 1. 包含必要的头文件 在使用 std::thread 前&#xff0c;需要包含 <thread> 头文件&#xff1a; …...

酷瓜云课堂(内网版)v1.1.5 发布,局域网在线学习+考试系统

更新内容 更新layui-v2.9.10更新docker国内镜像地址增加导入镜像构建容器的方式教师不批阅非首次考试试卷轮播图增加专栏类型目标链接增加课程能否发布检查去除初始化kindeditor语言文件去除选择题EF选项优化富文本内容显示样式优化内容图片点击放大监听优化试题题干答案等图片…...

大数据之Hadoop部署

文章目录 服务器规划服务器环境准备1. 网络测试2. 安装额外软件包3. 安装基础工具4. 关闭防火墙5. 创建用户并配置权限6. 创建目录并设置权限7. 卸载JDK8. 修改主机名9. 配置hosts文件10. 重启服务器 配置免密登录安装Java安装Hadoop1. Hadoop部署2. 配置Hadoop3. 格式化Hadoop…...

Java异常处理中的“throw”与“throws”的区别

在Java中&#xff0c;throw 和 throws 是两个用于处理异常的关键词&#xff0c;它们的使用场景和目的有所不同 1. throw throw 关键字用于在Java程序中显式地抛出一个异常。当你检测到某些条件&#xff08;通常是错误条件&#xff09;时&#xff0c;你可以使用 throw 来抛出一…...

英语智汇学习系统

目 录 1 软件概述 1.1 项目研究背景及意义 2 系统相关技术 2.1 HTML、WXSS、JAVASCRIPT技术 2.2 Vanilla框架 2.3 uni-app框架 2.4 MYSQL数据库 3 需求分析 3.1 可行性分析 3.2 功能需求分析 3.3 系统用户及用例分析 3.4 非功能需求分析 3.5 数据流图…...

ExtractAItoTEXT 提取Adobe illustrator AI文件中的文字到文本文件翻译并写回到Adobe illustrator AI文件

Extract Text from Adobe illustrator to text for translate and write back to Adobe illustrator after translate in text file. Originally script from marceloliaohotmail.com during his work in SDL. Updated by me. 从Adobe illustrator中提取文本以进行翻译&#x…...

ms17-010 ms12-020 ms-08-067

MS17-010是一个由微软发布的安全公告编号&#xff0c;它指代了一个严重级别的安全漏洞&#xff0c;该漏洞存在于Microsoft Windows的Server Message Block 1.0 (SMBv1)协议处理中。这个漏洞被命名为“永恒之蓝”&#xff08;EternalBlue&#xff09;&#xff0c;因为它最初是由…...

【海思Hi3403V100】多目拼接相机套板硬件规划方案

海思Hi3403V100 是专业超高清智能网络摄像头 SoC。该芯片最高支持四路 sensor 输入&#xff0c;支持最高 4K60fps 的 ISP 图像处理能力&#xff0c;支持 3F 、WDR、多级降噪、六轴防抖、硬件拼接、多光谱融合等多种传统图像增强和处理算法&#xff0c;支持通过AI 算法对输入图像…...

AI的赚钱风向,彻底变了!

从2023年3月起&#xff0c;生成式AI技术的浪潮席卷全球&#xff0c;让不少人开始焦虑中国AI技术与美国的差距。然而&#xff0c;最近的趋势显示&#xff0c;AI创业的盈利模式已经发生了根本性的变化。今年&#xff0c;我们见证了AIGC&#xff08;人工智能生成内容&#xff09;企…...

服务器重启后jenkins任务内容不见了,并且新建任务也不见了

服务器centos7.4 背景&#xff1a;服务器异常重启后&#xff0c;jenkins上面的任务只剩下一些前端项目&#xff0c;后端的任务都不展示了&#xff0c;jenkins版本是Jenkins 2.346.3 解决方案&#xff1a;根据显示&#xff0c;jenkins很多的插件引用失败&#xff0c;显示需要升…...