设计模式
1、在某些场景下,针对某些问题的某种特定的解决方案
2、设计模式是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结
3、让代码更容易被人理解、保证代码可靠性、保证代码稳定性、保证代码易于扩展
本文大部分引用了博客园的昵称为Learning hard的博主,这个博主写的真的好,推荐去看看
单例设计模式
单例设计模式:确保一个类只有一个实例,并提供一个访问它的全局访问点
饿汉式
优点:1、代码层面,书写简单
2、线程方面,避免了线程同步问题
缺点:类加载的时候,直接创建了对象,没有实现“懒加载”,可能会造成内存浪费
懒汉式
优点:实现了“懒加载”,不会造成资源浪费
缺点:只适合单线程应用,多线程会出现线程安全问题
懒汉式:用到的时候才创建对象
public class Singleton{
private Singleton(){}
private static Singleton singleton = null; //不建立对象
public static synchronized Singleton getInstance(){
if(singleton == null) { //先判断是否为空
singleton = new Singleton (); //懒汉式做法
}
return singleton ;
}
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_39885372/article/details/114676526
饿汉式:不管用不用都先创建对象
public class Singleton{
public Singleton(){}
private static Singleton singleton = new Singleton(); //建立对象
public static Singleton getInstance(){
return singleton ;//直接返回单例对象 }}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_39885372/article/details/114676526
使用双重锁定来实现懒汉式单例设计模式
/// <summary>
/// 单例模式的实现
/// </summary>
public class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
// 定义一个标识确保线程同步
private static readonly object locker = new object();
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
// 双重锁定只需要一句判断就可以了
if (uniqueInstance == null)
{
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
简单工厂设计模式
简单工厂设计模式,又叫静态工程方法,它不属于23种设计模式之一
简单工厂设计模式,是由工厂决定创建出哪一种产品类的实例,是工厂模式家族中最简单的模式
优点:1、简单工厂设计模式解决了客户端直接依赖于具体对象的问题。客户端消除了创建对象的责任,仅仅承担使用的责任。简单工厂实现了对责任的分割。
2、简单工厂也起到了代码复用的作用
缺点:1、系统扩展困难,一旦加入新功能,就必须修改工厂逻辑
2、简单工厂集合了所有创建对象的逻辑,一旦不能正常工作,会导致整个系统和出现问题
什么是简单工厂模式呢?在现实生活中工厂是负责生产产品的,同样在设计模式中,简单工厂模式我们也可以理解为负责生产对象的一个类。我们平常在编程中,当使用“new”关键字创建一个对象时,此时该类就依赖于这个对象,也就是它们之间的耦合度高。当需求变化时,我们就不得不修改此类的源码。而我们写代码时,需要实现“高内聚、低耦合”,为了解决这一问题,此时我们可以用面向对象的原则去解决,该原则就是:封装变化,既然要封装变化,就要找到变化的代码,然后把变化的代码用类来封装起来。这样的一种思想就是我们简单工厂模式的实现方式。
在外面打工的人,免不了要经常在外面吃饭,当然我们也可以自己在家做饭吃,但是自己做饭吃麻烦,因为又要自己买菜,然而,出去吃饭就完全没有这些麻烦的,我们只需要到餐馆点菜就可以了,买菜的事情就交给餐馆做就可以了,这里餐馆就充当简单工厂的角色,下面让我们看看现实生活中的例子用代码是怎样来表现的。
/// <summary>
/// 自己做饭的情况
/// 没有简单工厂之前,客户想吃什么菜只能自己炒的
/// </summary>
public class Customer
{
/// <summary>
/// 烧菜方法
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static Food Cook(string type)
{
Food food = null;
// 客户A说:我想吃西红柿炒蛋怎么办?
// 客户B说:那你就自己烧啊
// 客户A说: 好吧,那就自己做吧
if (type.Equals("西红柿炒蛋"))
{
food = new TomatoScrambledEggs();
}
// 我又想吃土豆肉丝, 这个还是得自己做
// 我觉得自己做好累哦,如果能有人帮我做就好了?
else if (type.Equals("土豆肉丝"))
{
food = new ShreddedPorkWithPotatoes();
}
return food;
}
static void Main(string[] args)
{
// 做西红柿炒蛋
Food food1 = Cook("西红柿炒蛋");
food1.Print();
Food food2 = Cook("土豆肉丝");
food2.Print();
Console.Read();
}
}
/// <summary>
/// 菜抽象类
/// </summary>
public abstract class Food
{
// 输出点了什么菜
public abstract void Print();
}
/// <summary>
/// 西红柿炒鸡蛋这道菜
/// </summary>
public class TomatoScrambledEggs : Food
{
public override void Print()
{
Console.WriteLine("一份西红柿炒蛋!");
}
}
/// <summary>
/// 土豆肉丝这道菜
/// </summary>
public class ShreddedPorkWithPotatoes : Food
{
public override void Print()
{
Console.WriteLine("一份土豆肉丝");
}
}
自己做饭,如果我们想吃别的菜时,此时就需要去买这种菜和洗菜这些繁琐的操作,有了餐馆(也就是简单工厂)之后,我们就可以把这些操作交给餐馆去做,此时消费者(也就是我们)对菜(也就是具体对象)的依赖关系从直接变成的间接的,这样就是实现了面向对象的另一个原则——降低对象之间的耦合度,下面就具体看看有了餐馆之后的实现代码(即简单工厂的实现):
/// <summary>
/// 顾客充当客户端,负责调用简单工厂来生产对象
/// 即客户点菜,厨师(相当于简单工厂)负责烧菜(生产的对象)
/// </summary>
class Customer
{
static void Main(string[] args)
{
// 客户想点一个西红柿炒蛋
Food food1 = FoodSimpleFactory.CreateFood("西红柿炒蛋");
food1.Print();
// 客户想点一个土豆肉丝
Food food2 = FoodSimpleFactory.CreateFood("土豆肉丝");
food2.Print();
Console.Read();
}
}
/// <summary>
/// 菜抽象类
/// </summary>
public abstract class Food
{
// 输出点了什么菜
public abstract void Print();
}
/// <summary>
/// 西红柿炒鸡蛋这道菜
/// </summary>
public class TomatoScrambledEggs : Food
{
public override void Print()
{
Console.WriteLine("一份西红柿炒蛋!");
}
}
/// <summary>
/// 土豆肉丝这道菜
/// </summary>
public class ShreddedPorkWithPotatoes : Food
{
public override void Print()
{
Console.WriteLine("一份土豆肉丝");
}
}
/// <summary>
/// 简单工厂类, 负责 炒菜
/// </summary>
public class FoodSimpleFactory
{
public static Food CreateFood(string type)
{
Food food = null;
if (type.Equals("土豆肉丝"))
{
food= new ShreddedPorkWithPotatoes();
}
else if (type.Equals("西红柿炒蛋"))
{
food= new TomatoScrambledEggs();
}
return food;
}
}
工厂方法设计模式
工厂方法设计模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化,延迟到子类
抽象工厂角色:这是工厂方法模式的核心,是具体的工厂角色必须实现的接口或者必须继承的抽象类
具体工厂角色:它包含和具体业务逻辑有关的代码,由应用程序调用以创建具体的产品对象
抽象产品角色:它是具体产品继承的父类或者接口
具体产品角色类:具体工厂角色创建的对象,就是该类的实例
在简单工厂模式中讲到简单工厂模式的缺点,有一点是——简单工厂模式系统难以扩展,一旦添加新产品就不得不修改简单工厂方法,这样就会造成简单工厂的实现逻辑过于复杂,然而本专题介绍的工厂方法模式可以解决简单工厂模式中存在的这个问题,下面就具体看看工厂模式是如何解决该问题的。
工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。下面看下工厂模式的具体实现代码(这里还是以简单工厂模式中点菜的例子来实现):
namespace 设计模式之工厂方法模式
{
/// <summary>
/// 菜抽象类
/// </summary>
public abstract class Food
{
// 输出点了什么菜
public abstract void Print();
}
/// <summary>
/// 西红柿炒鸡蛋这道菜
/// </summary>
public class TomatoScrambledEggs : Food
{
public override void Print()
{
Console.WriteLine("西红柿炒蛋好了!");
}
}
/// <summary>
/// 土豆肉丝这道菜
/// </summary>
public class ShreddedPorkWithPotatoes : Food
{
public override void Print()
{
Console.WriteLine("土豆肉丝好了");
}
}
/// <summary>
/// 抽象工厂类
/// </summary>
public abstract class Creator
{
// 工厂方法
public abstract Food CreateFoddFactory();
}
/// <summary>
/// 西红柿炒蛋工厂类
/// </summary>
public class TomatoScrambledEggsFactory:Creator
{
/// <summary>
/// 负责创建西红柿炒蛋这道菜
/// </summary>
/// <returns></returns>
public override Food CreateFoddFactory()
{
return new TomatoScrambledEggs();
}
}
/// <summary>
/// 土豆肉丝工厂类
/// </summary>
public class ShreddedPorkWithPotatoesFactory:Creator
{
/// <summary>
/// 负责创建土豆肉丝这道菜
/// </summary>
/// <returns></returns>
public override Food CreateFoddFactory()
{
return new ShreddedPorkWithPotatoes();
}
}
/// <summary>
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 初始化做菜的两个工厂()
Creator shreddedPorkWithPotatoesFactory = new ShreddedPorkWithPotatoesFactory();
Creator tomatoScrambledEggsFactory = new TomatoScrambledEggsFactory();
// 开始做西红柿炒蛋
Food tomatoScrambleEggs = tomatoScrambledEggsFactory.CreateFoddFactory();
tomatoScrambleEggs.Print();
//开始做土豆肉丝
Food shreddedPorkWithPotatoes = shreddedPorkWithPotatoesFactory.CreateFoddFactory();
shreddedPorkWithPotatoes.Print();
Console.Read();
}
}
}
使用工厂方法实现的系统,如果系统需要添加新产品时,我们可以利用多态性来完成系统的扩展,对于抽象工厂类和具体工厂中的代码都不需要做任何改动。例如,我们我们还想点一个“肉末茄子”,此时我们只需要定义一个肉末茄子具体工厂类和肉末茄子类就可以。而不用像简单工厂模式中那样去修改工厂类中的实现(具体指添加case语句)。具体代码为:
/// <summary>
/// 肉末茄子这道菜
/// </summary>
public class MincedMeatEggplant : Food
{
/// <summary>
/// 重写抽象类中的方法
/// </summary>
public override void Print()
{
Console.WriteLine("肉末茄子好了");
}
}
/// <summary>
/// 肉末茄子工厂类,负责创建肉末茄子这道菜
/// </summary>
public class MincedMeatEggplantFactory : Creator
{
/// <summary>
/// 负责创建肉末茄子这道菜
/// </summary>
/// <returns></returns>
public override Food CreateFoddFactory()
{
return new MincedMeatEggplant();
}
}
/// <summary>
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 如果客户又想点肉末茄子了
// 再另外初始化一个肉末茄子工厂
Creator minceMeatEggplantFactor = new MincedMeatEggplantFactory();
// 利用肉末茄子工厂来创建肉末茄子这道菜
Food minceMeatEggplant = minceMeatEggplantFactor.CreateFoddFactory();
minceMeatEggplant.Print();
Console.Read();
}
}
工厂方法模式通过面向对象编程中的多态性来将对象的创建延迟到具体工厂中,从而解决了简单工厂模式中存在的问题,也很好地符合了开放封闭原则(即对扩展开发,对修改封闭)。
抽象工厂设计模式
抽象工厂:为了缩减创建子类工厂的数量,不必给每一个产品分配一个工厂类,可以将产品进行分组每组中的不同产品由同一个工厂类的不同方法来创建
在上一专题中介绍了工厂方法模式,工厂方法模式是为了克服简单工厂模式的缺点而设计出来的,简单工厂模式的工厂类随着产品类的增加需要增加额外的代码),而工厂方法模式每个具体工厂类只完成单个实例的创建,所以它具有很好的可扩展性。但是在现实生活中,一个工厂只创建单个产品这样的例子很少,因为现在的工厂都多元化了,一个工厂创建一系列的产品,如果我们要设计这样的系统时,工厂方法模式显然在这里不适用,然后抽象工厂模式却可以很好地解决一系列产品创建的问题,这是本专题所要介绍的内容。
下面就以生活中 “绝味” 连锁店的例子来实现一个抽象工厂模式。例如,绝味鸭脖想在江西南昌和上海开分店,但是由于当地人的口味不一样,在南昌的所有绝味的东西会做的辣一点,而上海不喜欢吃辣的,所以上海的所有绝味的东西都不会做的像南昌的那样辣,然而这点不同导致南昌绝味工厂和上海的绝味工厂生成所有绝味的产品都不同,也就是某个具体工厂需要负责一系列产品(指的是绝味所有食物)的创建工作,下面就具体看看如何使用抽象工厂模式来实现这种情况。
/// <summary>
/// 下面以绝味鸭脖连锁店为例子演示下抽象工厂模式
/// 因为每个地方的喜欢的口味不一样,有些地方喜欢辣点的,有些地方喜欢吃不辣点
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 南昌工厂制作南昌的鸭脖和鸭架
AbstractFactory nanChangFactory = new NanChangFactory();
YaBo nanChangYabo = nanChangFactory.CreateYaBo();
nanChangYabo.Print();
YaJia nanChangYajia= nanChangFactory.CreateYaJia();
nanChangYajia.Print();
// 上海工厂制作上海的鸭脖和鸭架
AbstractFactory shangHaiFactory = new ShangHaiFactory();
shangHaiFactory.CreateYaBo().Print();
shangHaiFactory.CreateYaJia().Print();
Console.Read();
}
}
/// <summary>
/// 抽象工厂类,提供创建两个不同地方的鸭架和鸭脖的接口
/// </summary>
public abstract class AbstractFactory
{
// 抽象工厂提供创建一系列产品的接口,这里作为例子,只给出了绝味中鸭脖和鸭架的创建接口
public abstract YaBo CreateYaBo();
public abstract YaJia CreateYaJia();
}
/// <summary>
/// 南昌绝味工厂负责制作南昌的鸭脖和鸭架
/// </summary>
public class NanChangFactory : AbstractFactory
{
// 制作南昌鸭脖
public override YaBo CreateYaBo()
{
return new NanChangYaBo();
}
// 制作南昌鸭架
public override YaJia CreateYaJia()
{
return new NanChangYaJia();
}
}
/// <summary>
/// 上海绝味工厂负责制作上海的鸭脖和鸭架
/// </summary>
public class ShangHaiFactory : AbstractFactory
{
// 制作上海鸭脖
public override YaBo CreateYaBo()
{
return new ShangHaiYaBo();
}
// 制作上海鸭架
public override YaJia CreateYaJia()
{
return new ShangHaiYaJia();
}
}
/// <summary>
/// 鸭脖抽象类,供每个地方的鸭脖类继承
/// </summary>
public abstract class YaBo
{
/// <summary>
/// 打印方法,用于输出信息
/// </summary>
public abstract void Print();
}
/// <summary>
/// 鸭架抽象类,供每个地方的鸭架类继承
/// </summary>
public abstract class YaJia
{
/// <summary>
/// 打印方法,用于输出信息
/// </summary>
public abstract void Print();
}
/// <summary>
/// 南昌的鸭脖类,因为江西人喜欢吃辣的,所以南昌的鸭脖稍微会比上海做的辣
/// </summary>
public class NanChangYaBo : YaBo
{
public override void Print()
{
Console.WriteLine("南昌的鸭脖");
}
}
/// <summary>
/// 上海的鸭脖没有南昌的鸭脖做的辣
/// </summary>
public class ShangHaiYaBo : YaBo
{
public override void Print()
{
Console.WriteLine("上海的鸭脖");
}
}
/// <summary>
/// 南昌的鸭架
/// </summary>
public class NanChangYaJia : YaJia
{
public override void Print()
{
Console.WriteLine("南昌的鸭架子");
}
}
/// <summary>
/// 上海的鸭架
/// </summary>
public class ShangHaiYaJia : YaJia
{
public override void Print()
{
Console.WriteLine("上海的鸭架子");
}
}
看完上面抽象工厂的实现之后,如果 “绝味”公司又想在湖南开一家分店怎么办呢? 因为湖南人喜欢吃麻辣的,下面就具体看看应用了抽象工厂模式的系统是如何应对这种需求的
/// <summary>
/// 如果绝味又想开一家湖南的分店时,因为湖南喜欢吃麻的
/// 所以这是有需要有一家湖南的工厂专门制作
/// </summary>
public class HuNanFactory : AbstractFactory
{
// 制作湖南鸭脖
public override YaBo CreateYaBo()
{
return new HuNanYaBo();
}
// 制作湖南鸭架
public override YaJia CreateYaJia()
{
return new HuNanYajia();
}
}
/// <summary>
/// 湖南的鸭脖
/// </summary>
public class HuNanYaBo : YaBo
{
public override void Print()
{
Console.WriteLine("湖南的鸭脖");
}
}
/// <summary>
/// 湖南的鸭架
/// </summary>
public class HuNanYajia : YaJia
{
public override void Print()
{
Console.WriteLine("湖南的鸭架子");
}
}
此时,只需要添加三个类:一个是湖南具体工厂类,负责创建湖南口味的鸭脖和鸭架,另外两个类是具有湖南口味的鸭脖类和鸭架类。从上面代码看出,抽象工厂对于系列产品的变化支持 “开放——封闭”原则(指的是要求系统对扩展开放,对修改封闭),扩展起来非常简便,但是,抽象工厂对于添加新产品这种情况就不支持”开放——封闭 “原则,这也是抽象工厂的缺点所在。
抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展,这真是抽象工厂模式的优点所在,然后抽象模式同时也存在不足的地方。下面就具体看下抽象工厂的缺点(缺点其实在前面的介绍中以已经涉及了):
抽象工厂模式很难支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。
知道了抽象工厂的优缺点之后,也就能很好地把握什么情况下考虑使用抽象工厂模式了,下面就具体看看使用抽象工厂模式的系统应该符合那几个前提:
- 一个系统不要求依赖产品类实例如何被创建、组合和表达的表达,这点也是所有工厂模式应用的前提。
- 这个系统有多个系列产品,而系统中只消费其中某一系列产品
- 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现
简单⼯⼚:⼀个⼯⼚类,⼀个产品抽象类,⼯⼚类创建⽅法依据传⼊参数并判断,选择创建具体产品对象。
⼯⼚⽅法:多个⼯⼚类,⼀个产品抽象类,⼀个产品抽象类,利⽤多态创建不同的产品对象,避免了⼤量的switch-case判断。
抽象⼯⼚:多个⼯⼚类,多个产品抽象类,产品⼦类分组,同⼀个⼯⼚实现类创建同组中的不同产品,减少了⼯⼚⼦类的数量。
原型设计模式(深拷贝与浅拷贝)
原型模式用于创建重复的对象,同时又能保证性能
原型模式:通过通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个对象的方法来创建出更多的同类型对象
原型设计模式,用于创建重复的对象,本质上低层用的是深拷贝或者浅拷贝
优点:
- 原型模式向客户隐藏了创建新实例的复杂性
- 原型模式允许动态增加或较少产品类。
- 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
- 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
缺点:
- 每个类必须配备一个克隆方法
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在内存中分配了多个一样的类实例对象,然后如果采用工厂模式来创建这样的系统的话,随着产品类的不断增加,导致子类的数量不断增多,反而增加了系统复杂程度,所以在这里使用工厂模式来封装类创建过程并不合适,然而原型模式可以很好地解决这个问题,因为每个类实例都是相同的,当我们需要多个相同的类实例时,没必要每次都使用new运算符去创建相同的类实例对象,此时我们一般思路就是想——只创建一个类实例对象,如果后面需要更多这样的实例,可以通过对原来对象拷贝一份来完成创建,这样在内存中不需要创建多个相同的类实例,从而减少内存的消耗和达到类实例的复用。 然而这个思路正是原型模式的实现方式。下面就具体介绍下设计模式中的原型设计模式。
在现实生活中,也有很多原型设计模式的例子,例如,细胞分裂的过程,一个细胞的有丝分裂产生两个相同的细胞;还有西游记中孙悟空变出后孙的本领和火影忍者中鸣人的隐分身忍术等。下面就以孙悟空为例子来演示下原型模式的实现。具体的实现代码如下:
///火影忍者中鸣人的影分身和孙悟空的的变都是原型模式
class Client
{
static void Main(string[] args)
{
// 孙悟空 原型
MonkeyKingPrototype prototypeMonkeyKing = new ConcretePrototype("MonkeyKing");
// 变一个
MonkeyKingPrototype cloneMonkeyKing = prototypeMonkeyKing.Clone() as ConcretePrototype;
Console.WriteLine("Cloned1:\t"+cloneMonkeyKing.Id);
// 变两个
MonkeyKingPrototype cloneMonkeyKing2 = prototypeMonkeyKing.Clone() as ConcretePrototype;
Console.WriteLine("Cloned2:\t" + cloneMonkeyKing2.Id);
Console.ReadLine();
}
}
/// <summary>
/// 孙悟空原型
/// </summary>
public abstract class MonkeyKingPrototype
{
public string Id { get; set; }
public MonkeyKingPrototype(string id)
{
this.Id = id;
}
// 克隆方法,即孙大圣说“变”
public abstract MonkeyKingPrototype Clone();
}
/// <summary>
/// 创建具体原型
/// </summary>
public class ConcretePrototype : MonkeyKingPrototype
{
public ConcretePrototype(string id)
: base(id)
{ }
/// <summary>
/// 浅拷贝
/// </summary>
/// <returns></returns>
public override MonkeyKingPrototype Clone()
{
// 调用MemberwiseClone方法实现的是浅拷贝,另外还有深拷贝
return (MonkeyKingPrototype)this.MemberwiseClone();
}
}
上面代码实现的浅拷贝的方式,浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么这两个对象将引用同一个字符串,而深拷贝是对对象实例中字段引用的对象也进行拷贝,如果一个对象有一个指向字符串的字段,并且我们对该对象进行了深拷贝的话,那么我们将创建一个对象和一个新的字符串,新的对象将引用新的字符串。也就是说,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响,而执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。
建造者设计模式
建造者模式,是将一个复杂对象的构建和它的表示分离,使得同样的构建过程,可以创建不同的表示。建造者模式使得建造代码与表示代码的分离,可以使客户端不必知道产品内部组成的细节,从而降低了客户端与具体产品之间的耦合度。
建造者模式:在我们软件开发中,有时会面临着“一个复杂对象”的创建过程,通常由各个部分的子对象用一定的算法构成。由于需求的变化,这个复杂对象的各个部分也经常面临着剧烈的变化,但是将他们组合到一起却相对的稳定
在软件系统中,有时需要创建一个复杂对象,并且这个复杂对象由其各部分子对象通过一定的步骤组合而成。例如一个采购系统中,如果需要采购员去采购一批电脑时,在这个实际需求中,电脑就是一个复杂的对象,它是由CPU、主板、硬盘、显卡、机箱等组装而成的,如果此时让采购员一台一台电脑去组装的话真是要累死采购员了,这里就可以采用建造者模式来解决这个问题,我们可以把电脑的各个组件的组装过程封装到一个建造者类对象里,建造者只要负责返还给客户端全部组件都建造完毕的产品对象就可以了。然而现实生活中也是如此的,如果公司要采购一批电脑,此时采购员不可能自己去买各个组件并把它们组织起来,此时采购员只需要像电脑城的老板说自己要采购什么样的电脑就可以了,电脑城老板自然会把组装好的电脑送到公司。下面就以这个例子来展开建造者模式的介绍。
在这个例子中,电脑城的老板是直接与客户(也就是指采购员)联系的,然而电脑的组装是由老板指挥装机人员去把电脑的各个部件组装起来,真真负责创建产品(这里产品指的就是电脑)的人就是电脑城的装机人员。理清了这个逻辑过程之后,下面就具体看下如何用代码来表示这种现实生活中的逻辑过程:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// 以组装电脑为例子
/// 每台电脑的组成过程都是一致的,但是使用同样的构建过程可以创建不同的表示(即可以组装成不一样的电脑,配置不一样)
/// 组装电脑的这个场景就可以应用建造者模式来设计
/// </summary>
namespace 设计模式之建造者模式
{
/// <summary>
/// 客户类
/// </summary>
class Customer
{
static void Main(string[] args)
{
// 客户找到电脑城老板说要买电脑,这里要装两台电脑
// 创建指挥者和构造者
Director director = new Director();
Builder b1 = new ConcreteBuilder1();
Builder b2 = new ConcreteBuilder2();
// 老板叫员工去组装第一台电脑
director.Construct(b1);
// 组装完,组装人员搬来组装好的电脑
Computer computer1 = b1.GetComputer();
computer1.Show();
// 老板叫员工去组装第二台电脑
director.Construct(b2);
Computer computer2 = b2.GetComputer();
computer2.Show();
Console.Read();
}
}
/// <summary>
/// 小王和小李难道会自愿地去组装嘛,谁不想休息的,这必须有一个人叫他们去组装才会去的
/// 这个人当然就是老板了,也就是建造者模式中的指挥者
/// 指挥创建过程类
/// </summary>
public class Director
{
// 组装电脑
public void Construct(Builder builder)
{
builder.BuildPartCPU();
builder.BuildPartMainBoard();
}
}
/// <summary>
/// 电脑类
/// </summary>
public class Computer
{
// 电脑组件集合
private IList<string> parts = new List<string>();
// 把单个组件添加到电脑组件集合中
public void Add(string part)
{
parts.Add(part);
}
public void Show()
{
Console.WriteLine("电脑开始在组装.......");
foreach (string part in parts)
{
Console.WriteLine("组件"+part+"已装好");
}
Console.WriteLine("电脑组装好了");
}
}
/// <summary>
/// 抽象建造者,这个场景下为 "组装人" ,这里也可以定义为接口
/// </summary>
public abstract class Builder
{
// 装CPU
public abstract void BuildPartCPU();
// 装主板
public abstract void BuildPartMainBoard();
// 当然还有装硬盘,电源等组件,这里省略
// 获得组装好的电脑
public abstract Computer GetComputer();
}
/// <summary>
/// 具体创建者,具体的某个人为具体创建者,例如:装机小王啊
/// </summary>
public class ConcreteBuilder1 : Builder
{
Computer computer = new Computer();
public override void BuildPartCPU()
{
computer.Add("CPU1");
}
public override void BuildPartMainBoard()
{
computer.Add("Main board1");
}
public override Computer GetComputer()
{
return computer;
}
}
/// <summary>
/// 具体创建者,具体的某个人为具体创建者,例如:装机小李啊
/// 又装另一台电脑了
/// </summary>
public class ConcreteBuilder2 : Builder
{
Computer computer = new Computer();
public override void BuildPartCPU()
{
computer.Add("CPU2");
}
public override void BuildPartMainBoard()
{
computer.Add("Main board2");
}
public override Computer GetComputer()
{
return computer;
}
}
}
- 在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。
- 建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的(也就是说电脑的内部组件是经常变化的,这里指的的变化如硬盘的大小变了,CPU由单核变双核等)。
- 产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。
- 在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。
- 由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。
适配器设计模式
适配器模式,将一个类的接口,转换成客户希望的另外一个接口
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
下面让我们看看适配器的定义,适配器模式——把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。适配器模式有类的适配器模式和对象的适配器模式两种形式,下面我们分别讨论这两种形式的实现和给出对应的类图来帮助大家理清类之间的关系。
类的适配器模式
在这里以生活中的一个例子来进行演示适配器模式的实现,具体场景是: 在生活中,我们买的电器插头是2个孔的,但是我们买的插座只有三个孔的,此时我们就希望电器的插头可以转换为三个孔的就好,这样我们就可以直接把它插在插座上,此时三个孔插头就是客户端期待的另一种接口,自然两个孔的插头就是现有的接口,适配器模式就是用来完成这种转换的,具体实现代码如下:
using System;
/// 这里以插座和插头的例子来诠释适配器模式
/// 现在我们买的电器插头是2个孔,但是我们买的插座只有3个孔的
/// 这是我们想把电器插在插座上的话就需要一个电适配器
namespace 设计模式之适配器模式
{
/// <summary>
/// 客户端,客户想要把2个孔的插头 转变成三个孔的插头,这个转变交给适配器就好
/// 既然适配器需要完成这个功能,所以它必须同时具体2个孔插头和三个孔插头的特征
/// </summary>
class Client
{
static void Main(string[] args)
{
// 现在客户端可以通过电适配要使用2个孔的插头了
IThreeHole threehole = new PowerAdapter();
threehole.Request();
Console.ReadLine();
}
}
/// <summary>
/// 三个孔的插头,也就是适配器模式中的目标角色
/// </summary>
public interface IThreeHole
{
void Request();
}
/// <summary>
/// 两个孔的插头,源角色——需要适配的类
/// </summary>
public abstract class TwoHole
{
public void SpecificRequest()
{
Console.WriteLine("我是两个孔的插头");
}
}
/// <summary>
/// 适配器类,接口要放在类的后面
/// 适配器类提供了三个孔插头的行为,但其本质是调用两个孔插头的方法
/// </summary>
public class PowerAdapter:TwoHole,IThreeHole
{
/// <summary>
/// 实现三个孔插头接口方法
/// </summary>
public void Request()
{
// 调用两个孔插头方法
this.SpecificRequest();
}
}
}
从上面代码中可以看出,客户端希望调用Request方法(即三个孔插头),但是我们现有的类(即2个孔的插头)并没有Request方法,它只有SpecificRequest方法(即两个孔插头本身的方法),然而适配器类(适配器必须实现三个孔插头接口和继承两个孔插头类)可以提供这种转换,它提供了Request方法的实现(其内部调用的是两个孔插头,因为适配器只是一个外壳罢了,包装着两个孔插头(因为只有这样,电器才能使用),并向外界提供三个孔插头的外观,)以供客户端使用。
优点:
- 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”
- 可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类
- 仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。
缺点:
- 用一个具体的Adapter类对Adaptee和Target进行匹配,当如果想要匹配一个类以及所有它的子类时,类的适配器模式就不能胜任了。因为类的适配器模式中没有引入Adaptee的实例,光调用this.SpecificRequest方法并不能去调用它对应子类的SpecificRequest方法。
- 采用了 “多继承”的实现方式,带来了不良的高耦合。
对象的适配器模式
上面都是类的适配器模式的介绍,然而适配器模式还有另外一种形式——对象的适配器模式,这里就具体讲解下它的实现,实现的分析思路:既然现在适配器类不能继承TwoHole抽象类了(因为用继承就属于类的适配器了),但是适配器类无论如何都要实现客户端期待的方法的,即Request方法,所以一定是要继承ThreeHole抽象类或IThreeHole接口的,然而适配器类的Request方法又必须调用TwoHole的SpecificRequest方法,又不能用继承,这时候就想,不能继承,但是我们可以在适配器类中创建TwoHole对象,然后在Requst中使用TwoHole的方法了。正如我们分析的那样,对象的适配器模式的实现正式如此。下面就让我看看具体实现代码:
namespace 对象的适配器模式
{
class Client
{
static void Main(string[] args)
{
// 现在客户端可以通过电适配要使用2个孔的插头了
ThreeHole threehole = new PowerAdapter();
threehole.Request();
Console.ReadLine();
}
}
/// <summary>
/// 三个孔的插头,也就是适配器模式中的目标(Target)角色
/// </summary>
public class ThreeHole
{
// 客户端需要的方法
public virtual void Request()
{
// 可以把一般实现放在这里
}
}
/// <summary>
/// 两个孔的插头,源角色——需要适配的类
/// </summary>
public class TwoHole
{
public void SpecificRequest()
{
Console.WriteLine("我是两个孔的插头");
}
}
/// <summary>
/// 适配器类,这里适配器类没有TwoHole类,
/// 而是引用了TwoHole对象,所以是对象的适配器模式的实现
/// </summary>
public class PowerAdapter : ThreeHole
{
// 引用两个孔插头的实例,从而将客户端与TwoHole联系起来
public TwoHole twoholeAdaptee = new TwoHole();
/// <summary>
/// 实现三个孔插头接口方法
/// </summary>
public override void Request()
{
twoholeAdaptee.SpecificRequest();
}
}
}
优点:
- 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”(这点是两种实现方式都具有的)
- 采用 “对象组合”的方式,更符合松耦合。
缺点:
- 使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
装饰器设计模式
装饰模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活
在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类)、AccessoriesPhone(挂件手机类)等,这样就会导致 ”子类爆炸“问题,为了解决这个问题,我们可以使用装饰者模式来动态地给一个对象添加额外的职责。下面让我们看看装饰者模式。
这里以手机和手机配件的例子来演示装饰者模式的实现,具体代码如下:
/// <summary>
/// 手机抽象类,即装饰者模式中的抽象组件类
/// </summary>
public abstract class Phone
{
public abstract void Print();
}
/// <summary>
/// 苹果手机,即装饰着模式中的具体组件类
/// </summary>
public class ApplePhone:Phone
{
/// <summary>
/// 重写基类方法
/// </summary>
public override void Print()
{
Console.WriteLine("开始执行具体的对象——苹果手机");
}
}
/// <summary>
/// 装饰抽象类,要让装饰完全取代抽象组件,所以必须继承自Photo
/// </summary>
public abstract class Decorator:Phone
{
private Phone phone;
public Decorator(Phone p)
{
this.phone = p;
}
public override void Print()
{
if (phone != null)
{
phone.Print();
}
}
}
/// <summary>
/// 贴膜,即具体装饰者
/// </summary>
public class Sticker : Decorator
{
public Sticker(Phone p)
: base(p)
{
}
public override void Print()
{
base.Print();
// 添加新的行为
AddSticker();
}
/// <summary>
/// 新的行为方法
/// </summary>
public void AddSticker()
{
Console.WriteLine("现在苹果手机有贴膜了");
}
}
/// <summary>
/// 手机挂件
/// </summary>
public class Accessories : Decorator
{
public Accessories(Phone p)
: base(p)
{
}
public override void Print()
{
base.Print();
// 添加新的行为
AddAccessories();
}
/// <summary>
/// 新的行为方法
/// </summary>
public void AddAccessories()
{
Console.WriteLine("现在苹果手机有漂亮的挂件了");
}
}
客户端调用代码
class Customer
{
static void Main(string[] args)
{
// 我买了个苹果手机
Phone phone = new ApplePhone();
// 现在想贴膜了
Decorator applePhoneWithSticker = new Sticker(phone);
// 扩展贴膜行为
applePhoneWithSticker.Print();
Console.WriteLine("----------------------\n");
// 现在我想有挂件了
Decorator applePhoneWithAccessories = new Accessories(phone);
// 扩展手机挂件行为
applePhoneWithAccessories.Print();
Console.WriteLine("----------------------\n");
// 现在我同时有贴膜和手机挂件了
Sticker sticker = new Sticker(phone);
Accessories applePhoneWithAccessoriesAndSticker = new Accessories(sticker);
applePhoneWithAccessoriesAndSticker.Print();
Console.ReadLine();
}
从上面的客户端代码可以看出,客户端可以动态地将手机配件增加到手机上,如果需要添加手机外壳时,此时只需要添加一个继承Decorator的手机外壳类,从而,装饰者模式扩展性也非常好。
在装饰者模式中各个角色有:
- 抽象构件(Phone)角色:给出一个抽象接口,以规范准备接受附加责任的对象。
- 具体构件(AppPhone)角色:定义一个将要接收附加责任的类。
- 装饰(Dicorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(Sticker和Accessories)角色:负责给构件对象 ”贴上“附加的责任。
优点:
- 装饰这模式和继承的目的都是扩展对象的功能,但装饰者模式比继承更灵活
- 通过使用不同的具体装饰类以及这些类的排列组合,设计师可以创造出很多不同行为的组合
- 装饰者模式有很好地可扩展性
缺点:装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变的更复杂。并且更多的对象会是的差错变得困难,特别是这些对象看上去都很像。
代理设计模式
代理设计模式:为其他对象提供一种代理,以控制对这个对象的访问
在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代理对象代替目标对象,然后客户端只需要访问代理对象,由代理对象去帮我们去请求目标对象并返回结果给客户端,这样的一个解决思路就是今天要介绍的代理模式。
1、远程代理:为⼀个对象在不同的地址空间,提供局部代表,这样可以隐藏⼀个对象存在于不同地址空间的事实。 代理的分类 客户端调⽤Web服务,会⽣成WebReference⽂件和⽂件夹,WebReference就是代理,使得客户端可以实现远程访问等功能。
2、虚拟代理,如果要创建开销很⼤的对象,可以通过代理来存放实例化需要很⻓时间的真实对象。 打开⼀个很⼤的⽹⻚,除了⽂字先出现外,图⽚和视频等都是慢慢出现的。 查询某个部⻔下所有的员⼯的信息,姓名、性别、年龄、毕业院校、部⻔、照⽚、slogen……
3、安全代理,⽤来控制真实对象的访问权限。 订单系统,要求是:⼀旦订单被创建,只有订单的创建⼈才可以修改订单中的数据,其他⼈则不能修改 相当于,有了⼀个订单对象,要控制外部对这个订单对象的访问权限,满⾜条件的可以访问,不满⾜的不可以访问。
代理模式——就是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。在一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。例如电脑桌面的快捷方式就是一个代理对象,快捷方式是它所引用的程序的一个代理。
看完代理模式的描述之后,下面以一个生活中的例子来解释下代理模式,在现实生活中,如果有同事出国或者朋友出国的情况下,我们经常会拖这位朋友帮忙带一些电子产品或化妆品等东西,这个场景中,出国的朋友就是一个代理,他(她)是他(她)朋友的一个代理,由于他朋友不能去国外买东西,他却可以,所以朋友们都托他帮忙带一些东西的。下面就以这个场景来实现下代理模式,具体代码如下:
// 客户端调用
class Client
{
static void Main(string[] args)
{
// 创建一个代理对象并发出请求
Person proxy = new Friend();
proxy.BuyProduct();
Console.Read();
}
}
// 抽象主题角色
public abstract class Person
{
public abstract void BuyProduct();
}
//真实主题角色
public class RealBuyPerson : Person
{
public override void BuyProduct()
{
Console.WriteLine("帮我买一个IPhone和一台苹果电脑");
}
}
// 代理角色
public class Friend:Person
{
// 引用真实主题实例
RealBuyPerson realSubject;
public override void BuyProduct()
{
Console.WriteLine("通过代理类访问真实实体对象的方法");
if (realSubject == null)
{
realSubject = new RealBuyPerson();
}
this.PreBuyProduct();
// 调用真实主题方法
realSubject.BuyProduct();
this.PostBuyProduct();
}
// 代理角色执行的一些操作
public void PreBuyProduct()
{
// 可能不知一个朋友叫这位朋友带东西,首先这位出国的朋友要对每一位朋友要带的东西列一个清单等
Console.WriteLine("我怕弄糊涂了,需要列一张清单,张三:要带相机,李四:要带Iphone...........");
}
// 买完东西之后,代理角色需要针对每位朋友需要的对买来的东西进行分类
public void PostBuyProduct()
{
Console.WriteLine("终于买完了,现在要对东西分一下,相机是张三的;Iphone是李四的..........");
}
}
优点:
- 代理模式能够将调用用于真正被调用的对象隔离,在一定程度上降低了系统的耦合度;
- 代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。代理对象可以在对目标对象发出请求之前进行一个额外的操作,例如权限检查等。
缺点:
- 由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢
- 实现代理类也需要额外的工作,从而增加了系统的实现复杂度。
外观设计模式
外观模式:隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的统一接口,这个接口组合了子系统的多个接口,使得子系统更容易被访问和使用
优点:1、隐藏了系统的复杂性,让客户端使用系统功能时变得简单
2、实现了客户端和子系统间的解耦
缺点:不符合开闭原则,如果客户端需要使用更多功能,不仅仅需要修改子系统,也必须修改外观层
在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ”门面“模式。下面就具体介绍下外观模式。
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直接通过外观类来调用内部子系统中方法,从而外观模式让客户和子系统之间避免了紧耦合。
介绍了外观模式的定义之后,让我们具体看看外观模式的由来以及实现,下面与学校中一个选课系统为例来解释外观模式,例如在选课系统中,有注册课程子系统和通知子系统,在不使用外观模式的情况下,客户端必须同时保存注册课程子系统和通知子系统两个引用,如果后期这两个子系统发生改变时,此时客户端的调用代码也要随之改变,这样就没有很好的可扩展性,下面看看不使用外观模式下选课系统的实现方式和客户端调用代码:
/// <summary>
/// 不使用外观模式的情况
/// 此时客户端与三个子系统都发送了耦合,使得客户端程序依赖与子系统
/// 为了解决这样的问题,我们可以使用外观模式来为所有子系统设计一个统一的接口
/// 客户端只需要调用外观类中的方法就可以了,简化了客户端的操作
/// 从而让客户和子系统之间避免了紧耦合
/// </summary>
class Client
{
static void Main(string[] args)
{
SubSystemA a = new SubSystemA();
SubSystemB b = new SubSystemB();
SubSystemC c = new SubSystemC();
a.MethodA();
b.MethodB();
c.MethodC();
Console.Read();
}
}
// 子系统A
public class SubSystemA
{
public void MethodA()
{
Console.WriteLine("执行子系统A中的方法A");
}
}
// 子系统B
public class SubSystemB
{
public void MethodB()
{
Console.WriteLine("执行子系统B中的方法B");
}
}
// 子系统C
public class SubSystemC
{
public void MethodC()
{
Console.WriteLine("执行子系统C中的方法C");
}
}
然而外观模式可以解决我们上面所说的问题,下面具体看看使用外观模式的实现:
/// <summary>
/// 以学生选课系统为例子演示外观模式的使用
/// 学生选课模块包括功能有:
/// 验证选课的人数是否已满
/// 通知用户课程选择成功与否
/// 客户端代码
/// </summary>
class Student
{
private static RegistrationFacade facade = new RegistrationFacade();
static void Main(string[] args)
{
if (facade.RegisterCourse("设计模式", "Learning Hard"))
{
Console.WriteLine("选课成功");
}
else
{
Console.WriteLine("选课失败");
}
Console.Read();
}
}
// 外观类
public class RegistrationFacade
{
private RegisterCourse registerCourse;
private NotifyStudent notifyStu;
public RegistrationFacade()
{
registerCourse = new RegisterCourse();
notifyStu = new NotifyStudent();
}
public bool RegisterCourse(string courseName, string studentName)
{
if (!registerCourse.CheckAvailable(courseName))
{
return false;
}
return notifyStu.Notify(studentName);
}
}
#region 子系统
// 相当于子系统A
public class RegisterCourse
{
public bool CheckAvailable(string courseName)
{
Console.WriteLine("正在验证课程 {0}是否人数已满", courseName);
return true;
}
}
// 相当于子系统B
public class NotifyStudent
{
public bool Notify(string studentName)
{
Console.WriteLine("正在向{0}发生通知", studentName);
return true;
}
}
#endregion
使用了外观模式之后,客户端只依赖与外观类,从而将客户端与子系统的依赖解耦了,如果子系统发生改变,此时客户端的代码并不需要去改变。外观模式的实现核心主要是——由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。然而这样的实现方式非常类似适配器模式,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口。
桥接设计模式
桥接设计模式:将抽象部分与它的实现部分分离,使它们都可以独立的变化
优点:1、桥接模式,相对于静态的继承⽽⾔,极⼤的减少了⼦类的个数,从⽽降低管理和维护成本
2、桥接模式提⾼了系统的可扩展性,在两个变化唯独中任意扩展⼀个维度,都不需要修改原有系统,符合开闭原则。 就像⼀座桥,把两个变化的维度连接了起来。
缺点:1、桥接模式的引⼊会增加系统的理解与设计难度,由于组合/聚合关系建⽴在抽象层,要求开发者针对抽象进⾏设计与编程。
2、桥接模式要求正确的识别出系统中两个独⽴变化的维度,引起对开发者的编程思想有较⾼的要求。
这里以电视遥控器的一个例子来引出桥接模式解决的问题,首先,我们每个牌子的电视机都有一个遥控器,此时我们能想到的一个设计是——把遥控器做为一个抽象类,抽象类中提供遥控器的所有实现,其他具体电视品牌的遥控器都继承这个抽象类,具体设计类图如下:
这样的实现使得每部不同型号的电视都有自己遥控器实现,这样的设计对于电视机的改变可以很好地应对,只需要添加一个派生类就搞定了,但随着时间的推移,用户需要改变遥控器的功能,如:用户可能后面需要对遥控器添加返回上一个台等功能时,此时上面的设计就需要修改抽象类RemoteControl的提供的接口了,此时可能只需要向抽象类中添加一个方法就可以解决了,但是这样带来的问题是我们改变了抽象的实现,如果用户需要同时改变电视机品型号和遥控器功能时,上面的设计就会导致相当大的修改,显然这样的设计并不是好的设计。然而使用桥接模式可以很好地解决这个问题,下面让我具体看看桥接模式是如何实现的。
桥接模式即将抽象部分与实现部分脱耦,使它们可以独立变化。对于上面的问题中,抽象化也就是RemoteControl类,实现部分也就是On()、Off()、NextChannel()等这样的方法(即遥控器的实现),上面的设计中,抽象化和实现部分在一起,桥接模式的目的就是使两者分离,根据面向对象的封装变化的原则,我们可以把实现部分的变化(也就是遥控器功能的变化)封装到另外一个类中,这样的一个思路也就是桥接模式的实现,大家可以对照桥接模式的实现代码来解决我们的分析思路。
上面定义部分已经给出了我们桥接模式的目的以及实现思路了,下面让我们具体看看桥接模式是如何解决引言部分设计的不足。
抽象化部分的代码:
/// <summary>
/// 抽象概念中的遥控器,扮演抽象化角色
/// </summary>
public class RemoteControl
{
// 字段
private TV implementor;
// 属性
public TV Implementor
{
get { return implementor; }
set { implementor = value; }
}
/// <summary>
/// 开电视机,这里抽象类中不再提供实现了,而是调用实现类中的实现
/// </summary>
public virtual void On()
{
implementor.On();
}
/// <summary>
/// 关电视机
/// </summary>
public virtual void Off()
{
implementor.Off();
}
/// <summary>
/// 换频道
/// </summary>
public virtual void SetChannel()
{
implementor.tuneChannel();
}
}
/// <summary>
/// 具体遥控器
/// </summary>
public class ConcreteRemote : RemoteControl
{
public override void SetChannel()
{
Console.WriteLine("---------------------");
base.SetChannel();
Console.WriteLine("---------------------");
}
}
遥控器的实现方法部分代码,即实现化部分代码,此时我们用另外一个抽象类TV封装了遥控器功能的变化,具体实现交给具体型号电视机去完成:
/// <summary>
/// 电视机,提供抽象方法
/// </summary>
public abstract class TV
{
public abstract void On();
public abstract void Off();
public abstract void tuneChannel();
}
/// <summary>
/// 长虹牌电视机,重写基类的抽象方法
/// 提供具体的实现
/// </summary>
public class ChangHong : TV
{
public override void On()
{
Console.WriteLine("长虹牌电视机已经打开了");
}
public override void Off()
{
Console.WriteLine("长虹牌电视机已经关掉了");
}
public override void tuneChannel()
{
Console.WriteLine("长虹牌电视机换频道");
}
}
/// <summary>
/// 三星牌电视机,重写基类的抽象方法
/// </summary>
public class Samsung : TV
{
public override void On()
{
Console.WriteLine("三星牌电视机已经打开了");
}
public override void Off()
{
Console.WriteLine("三星牌电视机已经关掉了");
}
public override void tuneChannel()
{
Console.WriteLine("三星牌电视机换频道");
}
}
采用桥接模式的客户端调用代码:
/// <summary>
/// 以电视机遥控器的例子来演示桥接模式
/// </summary>
class Client
{
static void Main(string[] args)
{
// 创建一个遥控器
RemoteControl remoteControl = new ConcreteRemote();
// 长虹电视机
remoteControl.Implementor = new ChangHong();
remoteControl.On();
remoteControl.SetChannel();
remoteControl.Off();
Console.WriteLine();
// 三星牌电视机
remoteControl.Implementor = new Samsung();
remoteControl.On();
remoteControl.SetChannel();
remoteControl.Off();
Console.Read();
}
}
上面桥接模式的实现中,遥控器的功能实现方法不在遥控器抽象类中去实现了,而是把实现部分用来另一个电视机类去封装它,然而遥控器中只包含电视机类的一个引用,同时这样的设计也非常符合现实生活中的情况(我认为的现实生活中遥控器的实现——遥控器中并不包含换台,打开电视机这样的功能的实现,遥控器只是包含了电视机上这些功能的引用,然后红外线去找到电视机上对应功能的的实现)。通过桥接模式,我们把抽象化和实现化部分分离开了,这样就可以很好应对这两方面的变化了。
桥接模式也经常用于具体的系统开发中,对于三层架构中就应用了桥接模式,三层架构中的业务逻辑层BLL中通过桥接模式与数据操作层解耦(DAL),其实现方式就是在BLL层中引用了DAL层中一个引用。这样数据操作的实现可以在不改变客户端代码的情况下动态进行更换,下面看一个简单的示例代码:
// 客户端调用
// 类似Web应用程序
class Client
{
static void Main(string[] args)
{
BusinessObject customers = new CustomersBusinessObject("ShangHai");
customers.Dataacces = new CustomersDataAccess();
customers.Add("小六");
Console.WriteLine("增加了一位成员的结果:");
customers.ShowAll();
customers.Delete("王五");
Console.WriteLine("删除了一位成员的结果:");
customers.ShowAll();
Console.WriteLine("更新了一位成员的结果:");
customers.Update("Learning_Hard");
customers.ShowAll();
Console.Read();
}
}
// BLL 层
public class BusinessObject
{
// 字段
private DataAccess dataacess;
private string city;
public BusinessObject(string city)
{
this.city = city;
}
// 属性
public DataAccess Dataacces
{
get { return dataacess; }
set { dataacess = value; }
}
// 方法
public virtual void Add(string name)
{
Dataacces.AddRecord(name);
}
public virtual void Delete(string name)
{
Dataacces.DeleteRecord(name);
}
public virtual void Update(string name)
{
Dataacces.UpdateRecord(name);
}
public virtual string Get(int index)
{
return Dataacces.GetRecord(index);
}
public virtual void ShowAll()
{
Console.WriteLine();
Console.WriteLine("{0}的顾客有:", city);
Dataacces.ShowAllRecords();
}
}
public class CustomersBusinessObject : BusinessObject
{
public CustomersBusinessObject(string city)
: base(city) { }
// 重写方法
public override void ShowAll()
{
Console.WriteLine("------------------------");
base.ShowAll();
Console.WriteLine("------------------------");
}
}
/// <summary>
/// 相当于三层架构中数据访问层(DAL)
/// </summary>
public abstract class DataAccess
{
// 对记录的增删改查操作
public abstract void AddRecord(string name);
public abstract void DeleteRecord(string name);
public abstract void UpdateRecord(string name);
public abstract string GetRecord(int index);
public abstract void ShowAllRecords();
}
public class CustomersDataAccess:DataAccess
{
// 字段
private List<string> customers =new List<string>();
public CustomersDataAccess()
{
// 实际业务中从数据库中读取数据再填充列表
customers.Add("Learning Hard");
customers.Add("张三");
customers.Add("李四");
customers.Add("王五");
}
// 重写方法
public override void AddRecord(string name)
{
customers.Add(name);
}
public override void DeleteRecord(string name)
{
customers.Remove(name);
}
public override void UpdateRecord(string updatename)
{
customers[0] = updatename;
}
public override string GetRecord(int index)
{
return customers[index];
}
public override void ShowAllRecords()
{
foreach (string name in customers)
{
Console.WriteLine(" " + name);
}
}
}
组合设计模式
组合设计模式:你可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用它们
你的程序需求如果有部分-整体的结构,并且你希望可以忽略单个对象和组合对象的不同,统⼀的使⽤组合结构中的所有的对象。 .Net在控件或者界⾯操作、界⾯展示等操作,都是使⽤的组合设计模式。 ⽐如说在winform程序开发中,System.Windows.Forms.Control类(Add、Remove)就应⽤了组合模式。
在软件开发过程中,我们经常会遇到处理简单对象和复合对象的情况,例如对操作系统中目录的处理就是这样的一个例子,因为目录可以包括单独的文件,也可以包括文件夹,文件夹又是由文件组成的,由于简单对象和复合对象在功能上区别,导致在操作过程中必须区分简单对象和复合对象,这样就会导致客户调用带来不必要的麻烦,然而作为客户,它们希望能够始终一致地对待简单对象和复合对象。然而组合模式就是解决这样的问题。下面让我们看看组合模式是怎样解决这个问题的。
组合模式允许你将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。下面我们用绘制的例子来详细介绍组合模式,图形可以由一些基本图形元素组成(如直线,圆等),也可以由一些复杂图形组成(由基本图形元素组合而成),为了使客户对基本图形和复杂图形的调用保持一致,我们使用组合模式来达到整个目的。
组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。
// 通过一些简单图形以及一些复杂图形构建图形树来演示组合模式
// 客户端调用
class Client
{
static void Main(string[] args)
{
ComplexGraphics complexGraphics = new ComplexGraphics("一个复杂图形和两条线段组成的复杂图形");
complexGraphics.Add(new Line("线段A"));
ComplexGraphics CompositeCG = new ComplexGraphics("一个圆和一条线组成的复杂图形");
CompositeCG.Add(new Circle("圆"));
CompositeCG.Add(new Circle("线段B"));
complexGraphics.Add(CompositeCG);
Line l = new Line("线段C");
complexGraphics.Add(l);
// 显示复杂图形的画法
Console.WriteLine("复杂图形的绘制如下:");
Console.WriteLine("---------------------");
complexGraphics.Draw();
Console.WriteLine("复杂图形绘制完成");
Console.WriteLine("---------------------");
Console.WriteLine();
// 移除一个组件再显示复杂图形的画法
complexGraphics.Remove(l);
Console.WriteLine("移除线段C后,复杂图形的绘制如下:");
Console.WriteLine("---------------------");
complexGraphics.Draw();
Console.WriteLine("复杂图形绘制完成");
Console.WriteLine("---------------------");
Console.Read();
}
}
/// <summary>
/// 图形抽象类,
/// </summary>
public abstract class Graphics
{
public string Name { get; set; }
public Graphics(string name)
{
this.Name = name;
}
public abstract void Draw();
public abstract void Add(Graphics g);
public abstract void Remove(Graphics g);
}
/// <summary>
/// 简单图形类——线
/// </summary>
public class Line : Graphics
{
public Line(string name)
: base(name)
{ }
// 重写父类抽象方法
public override void Draw()
{
Console.WriteLine("画 " + Name);
}
// 因为简单图形在添加或移除其他图形,所以简单图形Add或Remove方法没有任何意义
// 如果客户端调用了简单图形的Add或Remove方法将会在运行时抛出异常
// 我们可以在客户端捕获该类移除并处理
public override void Add(Graphics g)
{
throw new Exception("不能向简单图形Line添加其他图形");
}
public override void Remove(Graphics g)
{
throw new Exception("不能向简单图形Line移除其他图形");
}
}
/// <summary>
/// 简单图形类——圆
/// </summary>
public class Circle : Graphics
{
public Circle(string name)
: base(name)
{ }
// 重写父类抽象方法
public override void Draw()
{
Console.WriteLine("画 " + Name);
}
public override void Add(Graphics g)
{
throw new Exception("不能向简单图形Circle添加其他图形");
}
public override void Remove(Graphics g)
{
throw new Exception("不能向简单图形Circle移除其他图形");
}
}
/// <summary>
/// 复杂图形,由一些简单图形组成,这里假设该复杂图形由一个圆两条线组成的复杂图形
/// </summary>
public class ComplexGraphics : Graphics
{
private List<Graphics> complexGraphicsList = new List<Graphics>();
public ComplexGraphics(string name)
: base(name)
{ }
/// <summary>
/// 复杂图形的画法
/// </summary>
public override void Draw()
{
foreach (Graphics g in complexGraphicsList)
{
g.Draw();
}
}
public override void Add(Graphics g)
{
complexGraphicsList.Add(g);
}
public override void Remove(Graphics g)
{
complexGraphicsList.Remove(g);
}
}
由于基本图形对象不存在Add和Remove方法,上面实现中直接通过抛出一个异常的方式来解决这样的问题的,但是我们想以一种更安全的方式来解决——因为基本图形根本不存在这样的方法,我们是不是可以移除这些方法呢?为了移除这些方法,我们就不得不修改Graphics接口,我们把管理子对象的方法声明放在复合图形对象里面,这样简单对象Line、Circle使用这些方法时在编译时就会出错,这样的一种实现方式我们称为安全式的组合模式,然而上面的实现方式称为透明式的组合模式,下面让我们看看安全式的组合模式又是怎样实现的,具体实现代码如下:
/// 安全式的组合模式
/// 此方式实现的组合模式把管理子对象的方法声明在树枝构件ComplexGraphics类中
/// 这样如果叶子节点Line、Circle使用了Add或Remove方法时,就能在编译期间出现错误
/// 但这种方式虽然解决了透明式组合模式的问题,但是它使得叶子节点和树枝构件具有不一样的接口。
/// 所以这两种方式实现的组合模式各有优缺点,具体使用哪个,可以根据问题的实际情况而定
class Client
{
static void Main(string[] args)
{
ComplexGraphics complexGraphics = new ComplexGraphics("一个复杂图形和两条线段组成的复杂图形");
complexGraphics.Add(new Line("线段A"));
ComplexGraphics CompositeCG = new ComplexGraphics("一个圆和一条线组成的复杂图形");
CompositeCG.Add(new Circle("圆"));
CompositeCG.Add(new Circle("线段B"));
complexGraphics.Add(CompositeCG);
Line l = new Line("线段C");
complexGraphics.Add(l);
// 显示复杂图形的画法
Console.WriteLine("复杂图形的绘制如下:");
Console.WriteLine("---------------------");
complexGraphics.Draw();
Console.WriteLine("复杂图形绘制完成");
Console.WriteLine("---------------------");
Console.WriteLine();
// 移除一个组件再显示复杂图形的画法
complexGraphics.Remove(l);
Console.WriteLine("移除线段C后,复杂图形的绘制如下:");
Console.WriteLine("---------------------");
complexGraphics.Draw();
Console.WriteLine("复杂图形绘制完成");
Console.WriteLine("---------------------");
Console.Read();
}
}
/// <summary>
/// 图形抽象类,
/// </summary>
public abstract class Graphics
{
public string Name { get; set; }
public Graphics(string name)
{
this.Name = name;
}
public abstract void Draw();
// 移除了Add和Remove方法
// 把管理子对象的方法放到了ComplexGraphics类中进行管理
// 因为这些方法只在复杂图形中才有意义
}
/// <summary>
/// 简单图形类——线
/// </summary>
public class Line : Graphics
{
public Line(string name)
: base(name)
{ }
// 重写父类抽象方法
public override void Draw()
{
Console.WriteLine("画 " + Name);
}
}
/// <summary>
/// 简单图形类——圆
/// </summary>
public class Circle : Graphics
{
public Circle(string name)
: base(name)
{ }
// 重写父类抽象方法
public override void Draw()
{
Console.WriteLine("画 " + Name);
}
}
/// <summary>
/// 复杂图形,由一些简单图形组成,这里假设该复杂图形由一个圆两条线组成的复杂图形
/// </summary>
public class ComplexGraphics : Graphics
{
private List<Graphics> complexGraphicsList = new List<Graphics>();
public ComplexGraphics(string name)
: base(name)
{ }
/// <summary>
/// 复杂图形的画法
/// </summary>
public override void Draw()
{
foreach (Graphics g in complexGraphicsList)
{
g.Draw();
}
}
public void Add(Graphics g)
{
complexGraphicsList.Add(g);
}
public void Remove(Graphics g)
{
complexGraphicsList.Remove(g);
}
}
组合模式中涉及到三个角色:
- 抽象构件(Component)角色:这是一个抽象角色,上面实现中Graphics充当这个角色,它给参加组合的对象定义出了公共的接口及默认行为,可以用来管理所有的子对象(在透明式的组合模式是这样的)。在安全式的组合模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
- 树叶构件(Leaf)角色:树叶对象时没有下级子对象的对象,上面实现中Line和Circle充当这个角色,定义出参加组合的原始对象的行为
- 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象,上面实现中ComplexGraphics充当这个角色,树枝对象给出所有管理子对象的方法实现,如Add、Remove等。
优点:
- 组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
- 将”客户代码与复杂的对象容器结构“解耦。
- 可以更容易地往组合对象中加入新的构件。
缺点:使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。
注意的问题:
- 有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结构存储在父构件里面作为缓存。
- 客户端尽量不要直接调用树叶类中的方法(在我上面实现就是这样的,创建的是一个树枝的具体对象,应该使用Graphics complexGraphics = new ComplexGraphics("一个复杂图形和两条线段组成的复杂图形");),而是借用其父类(Graphics)的多态性完成调用,这样可以增加代码的复用性。
享元设计模式
当系统中⼤量使⽤某些相同或者相似的对象,这些对象会消耗⼤量的资源, 并且这些对象剔除外部状态后可以通过同⼀个对象来替代, 这时,我们可以使⽤享元设计模式来解决。
在软件开发过程,如果我们需要重复使用某个对象的时候,如果我们重复地使用new创建这个对象的话,这样我们在内存就需要多次地去申请内存空间了,这样可能会出现内存使用越来越多的情况,这样的问题是非常严重,然而享元模式可以解决这个问题,下面具体看看享元模式是如何去解决这个问题的。
在前面说了,享元模式可以解决上面的问题了,在介绍享元模式之前,让我们先要分析下如果去解决上面那个问题,上面的问题就是重复创建了同一个对象,如果让我们去解决这个问题肯定会这样想:“既然都是同一个对象,能不能只创建一个对象,然后下次需要创建这个对象的时候,让它直接用已经创建好了的对象就好了”,也就是说——让一个对象共享。不错,这个也是享元模式的实现精髓所在。
介绍完享元模式的精髓之后,让我们具体看看享元模式的正式定义:
享元模式——运用共享技术有效地支持大量细粒度的对象。享元模式可以避免大量相似类的开销,在软件开发中如果需要生成大量细粒度的类实例来表示数据,如果这些实例除了几个参数外基本上都是相同的,这时候就可以使用享元模式来大幅度减少需要实例化类的数量。如果能把这些参数(指的这些类实例不同的参数)移动类实例外面,在方法调用时将他们传递进来,这样就可以通过共享大幅度地减少单个实例的数目。(这个也是享元模式的实现要领),然而我们把类实例外面的参数称为享元对象的外部状态,把在享元对象内部定义称为内部状态。具体享元对象的内部状态与外部状态的定义为:
内部状态:在享元对象的内部并且不会随着环境的改变而改变的共享部分
外部状态:随环境改变而改变的,不可以共享的状态。
分析完享元模式的实现思路之后,相信大家实现享元模式肯定没什么问题了,下面以一个实际的应用来实现下享元模式。这个例子是:一个文本编辑器中会出现很多字面,使用享元模式去实现这个文本编辑器的话,会把每个字面做成一个享元对象。享元对象的内部状态就是这个字面,而字母在文本中的位置和字体风格等其他信息就是它的外部状态。下面就以这个例子来实现下享元模式,具体实现代码如下:
/// <summary>
/// 客户端调用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 定义外部状态,例如字母的位置等信息
int externalstate = 10;
// 初始化享元工厂
FlyweightFactory factory = new FlyweightFactory();
// 判断是否已经创建了字母A,如果已经创建就直接使用创建的对象A
Flyweight fa = factory.GetFlyweight("A");
if (fa != null)
{
// 把外部状态作为享元对象的方法调用参数
fa.Operation(--externalstate);
}
// 判断是否已经创建了字母B
Flyweight fb = factory.GetFlyweight("B");
if (fb != null)
{
fb.Operation(--externalstate);
}
// 判断是否已经创建了字母C
Flyweight fc = factory.GetFlyweight("C");
if (fc != null)
{
fc.Operation(--externalstate);
}
// 判断是否已经创建了字母D
Flyweight fd= factory.GetFlyweight("D");
if (fd != null)
{
fd.Operation(--externalstate);
}
else
{
Console.WriteLine("驻留池中不存在字符串D");
// 这时候就需要创建一个对象并放入驻留池中
ConcreteFlyweight d = new ConcreteFlyweight("D");
factory.flyweights.Add("D", d);
}
Console.Read();
}
}
/// <summary>
/// 享元工厂,负责创建和管理享元对象
/// </summary>
public class FlyweightFactory
{
// 最好使用泛型Dictionary<string,Flyweighy>
//public Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
public Hashtable flyweights = new Hashtable();
public FlyweightFactory()
{
flyweights.Add("A", new ConcreteFlyweight("A"));
flyweights.Add("B", new ConcreteFlyweight("B"));
flyweights.Add("C", new ConcreteFlyweight("C"));
}
public Flyweight GetFlyweight(string key)
{
// 更好的实现如下 //Flyweight flyweight = flyweights[key] as Flyweight; //if (flyweight == null) //{ // Console.WriteLine("驻留池中不存在字符串" + key); // flyweight = new ConcreteFlyweight(key); //} //return flyweight;
return flyweights[key] as Flyweight;
}
}
/// <summary>
/// 抽象享元类,提供具体享元类具有的方法
/// </summary>
public abstract class Flyweight
{
public abstract void Operation(int extrinsicstate);
}
// 具体的享元对象,这样我们不把每个字母设计成一个单独的类了,而是作为把共享的字母作为享元对象的内部状态
public class ConcreteFlyweight : Flyweight
{
// 内部状态
private string intrinsicstate ;
// 构造函数
public ConcreteFlyweight(string innerState)
{
this.intrinsicstate = innerState;
}
/// <summary>
/// 享元类的实例方法
/// </summary>
/// <param name="extrinsicstate">外部状态</param>
public override void Operation(int extrinsicstate)
{
Console.WriteLine("具体实现类: intrinsicstate {0}, extrinsicstate {1}", intrinsicstate, extrinsicstate);
}
}
在享元模式的实现中,我们没有像之前一样,把一个细粒度的类实例设计成一个单独的类,而是把它作为共享对象的内部状态放在共享类的内部定义,具体的解释注释中都有了,大家可以参考注释去进一步理解享元模式。
优点:
- 降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
缺点:
- 为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑更复杂,使系统复杂化。
- 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
中介者设计模式
中介者设计模式:中介者设计模式是一种行为型设计模式,能让你减少对象之间的混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作
在现实生活中,有很多中介者模式的身影,例如QQ游戏平台,聊天室、QQ群和短信平台,这些都是中介者模式在现实生活中的应用。
从生活中的例子可以看出,不论是QQ游戏还是QQ群,它们都是充当一个中间平台,QQ用户可以登录这个中间平台与其他QQ用户进行交流,如果没有这些中间平台,我们如果想与朋友进行聊天的话,可能就需要当面才可以了。电话、短信也同样是一个中间平台,有了这个中间平台,每个用户都不要直接依赖与其他用户,只需要依赖这个中间平台就可以了,一切操作都由中间平台去分发。了解完中介模式在生活中的模型后,下面给出中介模式的正式定义。
中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。
从生活中例子自然知道,中介者模式设计两个具体对象,一个是用户类,另一个是中介者类,根据针对接口编程原则,则需要把这两类角色进行抽象,所以中介者模式中就有了4类角色,它们分别是:抽象中介者角色,具体中介者角色、抽象同事类和具体同事类。中介者类是起到协调各个对象的作用,则抽象中介者角色中则需要保存各个对象的引用。
在现实生活中,中介者的存在是不可缺少的,如果没有了中介者,我们就不能与远方的朋友进行交流了。而在软件设计领域,为什么要使用中介者模式呢?如果不使用中介者模式的话,各个同事对象将会相互进行引用,如果每个对象都与多个对象进行交互时,将会形成如下图所示的网状结构。
从上图可以发现,如果不使用中介者模式的话,每个对象之间过度耦合,这样的既不利于类的复用也不利于扩展。如果引入了中介者模式,那么对象之间的关系将变成星型结构,采用中介者模式之后会形成如下图所示的结构:
从上图可以发现,使用中介者模式之后,任何一个类的变化,只会影响中介者和类本身,不像之前的设计,任何一个类的变化都会引起其关联所有类的变化。这样的设计大大减少了系统的耦合度。
介绍完中介者模式的定义和存在的必要性后,下面就以现实生活中打牌的例子来实现下中介者模式。在现实生活中,两个人打牌,如果某个人赢了都会影响到对方状态的改变。如果此时不采用中介者模式实现的话,则上面的场景的实现如下所示:
// 抽象牌友类
public abstract class AbstractCardPartner
{
public int MoneyCount { get; set; }
public AbstractCardPartner()
{
MoneyCount = 0;
}
public abstract void ChangeCount(int Count, AbstractCardPartner other);
}
// 牌友A类
public class ParterA : AbstractCardPartner
{
public override void ChangeCount(int Count, AbstractCardPartner other)
{
this.MoneyCount += Count;
other.MoneyCount -= Count;
}
}
// 牌友B类
public class ParterB : AbstractCardPartner
{
public override void ChangeCount(int Count, AbstractCardPartner other)
{
this.MoneyCount += Count;
other.MoneyCount -= Count;
}
}
class Program
{
// A,B两个人打牌
static void Main(string[] args)
{
AbstractCardPartner A = new ParterA();
A.MoneyCount = 20;
AbstractCardPartner B = new ParterB();
B.MoneyCount = 20;
// A 赢了则B的钱就减少
A.ChangeCount(5, B);
Console.WriteLine("A 现在的钱是:{0}", A.MoneyCount);// 应该是25
Console.WriteLine("B 现在的钱是:{0}", B.MoneyCount); // 应该是15
// B赢了A的钱也减少
B.ChangeCount(10, A);
Console.WriteLine("A 现在的钱是:{0}", A.MoneyCount); // 应该是15
Console.WriteLine("B 现在的钱是:{0}", B.MoneyCount); // 应该是25
Console.Read();
}
}
上面确实完美解决了上面场景中的问题,并且使用了抽象类使具体牌友A和牌友B都依赖于抽象类,从而降低了同事类之间的耦合度。但是这样的设计,如果其中牌友A发生变化时,此时就会影响到牌友B的状态,如果涉及的对象变多的话,这时候某一个牌友的变化将会影响到其他所有相关联的牌友状态。例如牌友A算错了钱,这时候牌友A和牌友B的钱数都不正确了,如果是多个人打牌的话,影响的对象就会更多。这时候就会思考——能不能把算钱的任务交给程序或者算数好的人去计算呢,这时候就有了我们QQ游戏中的欢乐斗地主等牌类游戏了。所以上面的设计,我们还是有进一步完善的方案的,即加入一个中介者对象来协调各个对象之间的关联,这也就是中介者模式的应用了,具体完善后的实现代码如下所示:
namespace MediatorPattern
{
// 抽象牌友类
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.AWin(Count);
}
}
// 牌友B类
public class ParterB : AbstractCardPartner
{
// 依赖与抽象中介者对象
public override void ChangeCount(int Count, AbstractMediator mediator)
{
mediator.BWin(Count);
}
}
// 抽象中介者类
public abstract class AbstractMediator
{
protected AbstractCardPartner A;
protected AbstractCardPartner B;
public AbstractMediator(AbstractCardPartner a, AbstractCardPartner b)
{
A = a;
B = b;
}
public abstract void AWin(int count);
public abstract void BWin(int count);
}
// 具体中介者类
public class MediatorPater : AbstractMediator
{
public MediatorPater(AbstractCardPartner a, AbstractCardPartner b)
: base(a, b)
{
}
public override void AWin(int count)
{
A.MoneyCount += count;
B.MoneyCount -= count;
}
public override void BWin(int count)
{
B.MoneyCount += count;
A.MoneyCount -= count;
}
}
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(A, B);
// A赢了
A.ChangeCount(5, mediator);
Console.WriteLine("A 现在的钱是:{0}", A.MoneyCount);// 应该是25
Console.WriteLine("B 现在的钱是:{0}", B.MoneyCount); // 应该是15
// B 赢了
B.ChangeCount(10, mediator);
Console.WriteLine("A 现在的钱是:{0}", A.MoneyCount);// 应该是15
Console.WriteLine("B 现在的钱是:{0}", B.MoneyCount); // 应该是25
Console.Read();
}
}
}
从上面实现代码可以看出,此时牌友A和牌友B都依赖于抽象的中介者类,这样如果其中某个牌友类变化只会影响到,只会影响到该变化牌友类本身和中介者类,从而解决前面实现代码出现的问题,具体的运行结果和前面实现结果一样。
在上面的实现代码中,抽象中介者类保存了两个抽象牌友类,如果新添加一个牌友类似时,此时就不得不去更改这个抽象中介者类。可以结合观察者模式来解决这个问题,即抽象中介者对象保存抽象牌友的类别,然后添加Register和UnRegister方法来对该列表进行管理,然后在具体中介者类中修改AWin和BWin方法,遍历列表,改变自己和其他牌友的钱数。这样的设计还是存在一个问题——即增加一个新牌友时,此时虽然解决了抽象中介者类不需要修改的问题,但此时还是不得不去修改具体中介者类,即添加CWin方法,我们可以采用状态模式来解决这个问题,关于状态模式的介绍将会在下一专题进行分享。
优点:
- 简化了对象之间的关系,将系统的各个对象之间的相互关系进行封装,将各个同事类解耦,使得系统变为松耦合。
- 提供系统的灵活性,使得各个同事对象独立而易于复用。
缺点:
- 中介者模式中,中介者角色承担了较多的责任,所以一旦这个中介者对象出现了问题,整个系统将会受到重大的影响。例如,QQ游戏中计算欢乐豆的程序出错了,这样会造成重大的影响。
- 新增加一个同事类时,不得不去修改抽象中介者类和具体中介者类,此时可以使用观察者模式和状态模式来解决这个问题。
中介者模式,定义了一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显式地相互引用,从而使其耦合性降低,而且可以独立地改变它们之间的交互。中介者模式一般应用于一组定义良好的对象之间需要进行通信的场合以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的情形下。
观察者设计模式
观察者设计模式:指多个对象间存在⼀对多的关系,当⼀个对象状态发⽣改变时,所有依赖于它的对象都能得到通知并被⾃动更新。 这种设计模式有成为发布-订阅模式。
在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题。
从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。
从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象,然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。
下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。有了这个分析,自然实现的代码如下所示:
// 腾讯游戏订阅号类
public class TenxunGame
{
// 订阅者对象
public Subscriber Subscriber {get;set;}
public String Symbol {get; set;}
public string Info {get ;set;}
public void Update()
{
if (Subscriber != null)
{
// 调用订阅者对象来通知订阅者
Subscriber.ReceiveAndPrintData(this);
}
}
}
// 订阅者类
public class Subscriber
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void ReceiveAndPrintData(TenxunGame txGame)
{
Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info);
}
}
// 客户端测试
class Program
{
static void Main(string[] args)
{
// 实例化订阅者和订阅号对象
Subscriber LearningHardSub = new Subscriber("LearningHard");
TenxunGame txGame = new TenxunGame();
txGame.Subscriber = LearningHardSub;
txGame.Symbol = "TenXun Game";
txGame.Info = "Have a new game published ....";
txGame.Update();
Console.ReadLine();
}
}
上面代码确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:
- TenxunGame类和Subscriber类之间形成了一种双向依赖关系,即TenxunGame调用了Subscriber的ReceiveAndPrintData方法,而Subscriber调用了TenxunGame类的属性。这样的实现,如果有其中一个类变化将引起另一个类的改变。
- 当出现一个新的订阅者时,此时不得不修改TenxunGame代码,即添加另一个订阅者的引用和在Update方法中调用另一个订阅者的方法。
上面的设计违背了“开放——封闭”原则,显然,这不是我们想要的。对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类,这样也就可以完美解决上面代码存在的问题了,具体的实现代码为:
// 订阅号抽象类
public abstract class TenXun
{
// 保存订阅者列表
private List<IObserver> observers = new List<IObserver>();
public string Symbol { get; set; }
public string Info { get; set; }
public TenXun(string symbol, string info)
{
this.Symbol = symbol;
this.Info = info;
}
#region 新增对订阅号列表的维护操作
public void AddObserver(IObserver ob)
{
observers.Add(ob);
}
public void RemoveObserver(IObserver ob)
{
observers.Remove(ob);
}
#endregion
public void Update()
{
// 遍历订阅者列表进行通知
foreach (IObserver ob in observers)
{
if (ob != null)
{
ob.ReceiveAndPrint(this);
}
}
}
}
// 具体订阅号类
public class TenXunGame : TenXun
{
public TenXunGame(string symbol, string info)
: base(symbol, info)
{
}
}
// 订阅者接口
public interface IObserver
{
void ReceiveAndPrint(TenXun tenxun);
}
// 具体的订阅者类
public class Subscriber : IObserver
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void ReceiveAndPrint(TenXun tenxun)
{
Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
}
}
// 客户端测试
class Program
{
static void Main(string[] args)
{
TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
// 添加订阅者
tenXun.AddObserver(new Subscriber("Learning Hard"));
tenXun.AddObserver(new Subscriber("Tom"));
tenXun.Update();
Console.ReadLine();
}
}
优点:
- 观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
- 观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
- 观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。
缺点:
- 如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
- 虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
- 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。
迭代器设计模式
迭代器设计模式:提供一种顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部展示
迭代器是针对集合对象而生的,对于集合对象而言,必然涉及到集合元素的添加删除操作,同时也肯定支持遍历集合元素的操作,我们此时可以把遍历操作也放在集合对象中,但这样的话,集合对象就承担太多的责任了,面向对象设计原则中有一条是单一职责原则,所以我们要尽可能地分离这些职责,用不同的类去承担不同的职责。迭代器模式就是用迭代器类来承担遍历集合元素的职责。
迭代器模式提供了一种方法顺序访问一个聚合对象(理解为集合对象)中各个元素,而又无需暴露该对象的内部表示,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
既然,迭代器模式承担了遍历集合对象的职责,则该模式自然存在2个类,一个是聚合类,一个是迭代器类。在面向对象涉及原则中还有一条是针对接口编程,所以,在迭代器模式中,抽象了2个接口,一个是聚合接口,另一个是迭代器接口,这样迭代器模式中就四个角色了,具体的类图如下所示:
从上图可以看出,迭代器模式由以下角色组成:
- 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口
- 具体迭代器角色(Concrete Iteraror):具体迭代器角色实现了迭代器接口,并需要记录遍历中的当前位置。
- 聚合角色(Aggregate):聚合角色负责定义获得迭代器角色的接口
- 具体聚合角色(Concrete Aggregate):具体聚合角色实现聚合角色接口。
// 抽象聚合类
public interface IListCollection
{
Iterator GetIterator();
}
// 迭代器抽象类
public interface Iterator
{
bool MoveNext();
Object GetCurrent();
void Next();
void Reset();
}
// 具体聚合类
public class ConcreteList : IListCollection
{
int[] collection;
public ConcreteList()
{
collection = new int[] { 2, 4, 6, 8 };
}
public Iterator GetIterator()
{
return new ConcreteIterator(this);
}
public int Length
{
get { return collection.Length; }
}
public int GetElement(int index)
{
return collection[index];
}
}
// 具体迭代器类
public class ConcreteIterator : Iterator
{
// 迭代器要集合对象进行遍历操作,自然就需要引用集合对象
private ConcreteList _list;
private int _index;
public ConcreteIterator(ConcreteList list)
{
_list = list;
_index = 0;
}
public bool MoveNext()
{
if (_index < _list.Length)
{
return true;
}
return false;
}
public Object GetCurrent()
{
return _list.GetElement(_index);
}
public void Reset()
{
_index = 0;
}
public void Next()
{
if (_index < _list.Length)
{
_index++;
}
}
}
// 客户端
class Program
{
static void Main(string[] args)
{
Iterator iterator;
IListCollection list = new ConcreteList();
iterator = list.GetIterator();
while (iterator.MoveNext())
{
int i = (int)iterator.GetCurrent();
Console.WriteLine(i.ToString());
iterator.Next();
}
Console.Read();
}
}
由于迭代器承担了遍历集合的职责,从而有以下的优点:
- 迭代器模式使得访问一个聚合对象的内容而无需暴露它的内部表示,即迭代抽象。
- 迭代器模式为遍历不同的集合结构提供了一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作
迭代器模式存在的缺陷:
- 迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常。所以使用foreach语句只能在对集合进行遍历,不能在遍历的同时更改集合中的元素。
访问者设计模式
访问者设计模式: 它是⼀种较为复杂的设计模式,它包含访问者和被访问者两个主要组成部分,这些被访问的元素通常具有 不同的类型,且不同的访问者可以对它们进⾏不同的访问操作。 提供⼀个作⽤于某个对象结构中的操作表示,它使得可以在不改变各元素的类的前提下,作⽤于这些元素的新操作。
访问者设计模式把数据结构和作⽤于结构上的操作,进⾏解耦。
访问者模式是封装一些施加于某种数据结构之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保存不变。访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操作之间的耦合度降低,使得操作集合可以相对自由地改变。
数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。
在讲诉访问者模式的实现时,我想先不用访问者模式的方式来实现某个场景。具体场景是——现在我想遍历每个元素对象,然后调用每个元素对象的Print方法来打印该元素对象的信息。如果此时不采用访问者模式的话,实现这个场景再简单不过了,具体实现代码如下所示:
namespace DonotUsevistorPattern
{
// 抽象元素角色
public abstract class Element
{
public abstract void Print();
}
// 具体元素A
public class ElementA : Element
{
public override void Print()
{
Console.WriteLine("我是元素A");
}
}
// 具体元素B
public class ElementB : Element
{
public override void Print()
{
Console.WriteLine("我是元素B");
}
}
// 对象结构
public class ObjectStructure
{
private ArrayList elements = new ArrayList();
public ArrayList Elements
{
get { return elements; }
}
public ObjectStructure()
{
Random ran = new Random();
for (int i = 0; i < 6; i++)
{
int ranNum = ran.Next(10);
if (ranNum > 5)
{
elements.Add(new ElementA());
}
else
{
elements.Add(new ElementB());
}
}
}
}
class Program
{
static void Main(string[] args)
{
ObjectStructure objectStructure = new ObjectStructure();
// 遍历对象结构中的对象集合,访问每个元素的Print方法打印元素信息
foreach (Element e in objectStructure.Elements)
{
e.Print();
}
Console.Read();
}
}
}
上面代码很准确了解决了我们刚才提出的场景,但是需求在时刻变化的,如果此时,我除了想打印元素的信息外,还想打印出元素被访问的时间,此时我们就不得不去修改每个元素的Print方法,再加入相对应的输入访问时间的输出信息。这样的设计显然不符合“开-闭”原则,即某个方法操作的改变,会使得必须去更改每个元素类。既然,这里变化的点是操作的改变,而每个元素的数据结构是不变的。所以此时就思考——能不能把操作于元素的操作和元素本身的数据结构分开呢?解开这两者的耦合度,这样如果是操作发现变化时,就不需要去更改元素本身了,但是如果是元素数据结构发现变化,例如,添加了某个字段,这样就不得不去修改元素类了。此时,我们可以使用访问者模式来解决这个问题,即把作用于具体元素的操作由访问者对象来调用。具体的实现代码如下所示:
namespace VistorPattern
{
// 抽象元素角色
public abstract class Element
{
public abstract void Accept(IVistor vistor);
public abstract void Print();
}
// 具体元素A
public class ElementA :Element
{
public override void Accept(IVistor vistor)
{
// 调用访问者visit方法
vistor.Visit(this);
}
public override void Print()
{
Console.WriteLine("我是元素A");
}
}
// 具体元素B
public class ElementB :Element
{
public override void Accept(IVistor vistor)
{
vistor.Visit(this);
}
public override void Print()
{
Console.WriteLine("我是元素B");
}
}
// 抽象访问者
public interface IVistor
{
void Visit(ElementA a);
void Visit(ElementB b);
}
// 具体访问者
public class ConcreteVistor :IVistor
{
// visit方法而是再去调用元素的Accept方法
public void Visit(ElementA a)
{
a.Print();
}
public void Visit(ElementB b)
{
b.Print();
}
}
// 对象结构
public class ObjectStructure
{
private ArrayList elements = new ArrayList();
public ArrayList Elements
{
get { return elements; }
}
public ObjectStructure()
{
Random ran = new Random();
for (int i = 0; i < 6; i++)
{
int ranNum = ran.Next(10);
if (ranNum > 5)
{
elements.Add(new ElementA());
}
else
{
elements.Add(new ElementB());
}
}
}
}
class Program
{
static void Main(string[] args)
{
ObjectStructure objectStructure = new ObjectStructure();
foreach (Element e in objectStructure.Elements)
{
// 每个元素接受访问者访问
e.Accept(new ConcreteVistor());
}
Console.Read();
}
}
}
从上面代码可知,使用访问者模式实现上面场景后,元素Print方法的访问封装到了访问者对象中了(我觉得可以把Print方法封装到具体访问者对象中。),此时客户端与元素的Print方法就隔离开了。此时,如果需要添加打印访问时间的需求时,此时只需要再添加一个具体的访问者类即可。此时就不需要去修改元素中的Print()方法了。
访问者模式具有以下优点:
- 访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。
- 访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与"中介者模式"。
- 访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。
访问者模式也有如下的缺点:
- 增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。
责任链设计模式
责任链设计模式:它是⼀种处理请求的模式,它让多个处理器,都有机会处理该请求,直到其中某个处理成功,责任链设计模式把 多个处理器串成链,然后让请求在链上传递。降低了请求者和请求处理者的耦合度。
在现实生活中,有很多请求并不是一个人说了就算的,例如面试时的工资,低于1万的薪水可能技术经理就可以决定了,但是1万~1万5的薪水可能技术经理就没这个权利批准,可能就需要请求技术总监的批准,所以在面试的完后,经常会有面试官说,你这个薪水我这边觉得你这技术可以拿这个薪水的,但是还需要技术总监的批准等的话。这个例子也就诠释了本文要介绍的内容。生活中的这个例子真是应用了责任链模式。
从生活中的例子可以发现,某个请求可能需要几个人的审批,即使技术经理审批完了,还需要上一级的审批。这样的例子,还有公司中的请假,少于3天的,直属Leader就可以批准,3天到7天之内就需要项目经理批准,多余7天的就需要技术总监的批准了。介绍了这么多生活中责任链模式的例子的,下面具体给出面向对象中责任链模式的定义。
责任链模式指的是——某个请求需要多个对象进行处理,从而避免请求的发送者和接收之间的耦合关系。将这些对象连成一条链子,并沿着这条链子传递该请求,直到有对象处理它为止。
有了上面的介绍,下面以公司采购东西为例子来实现责任链模式。公司规定,采购架构总价在1万之内,经理级别的人批准即可,总价大于1万小于2万5的则还需要副总进行批准,总价大于2万5小于10万的需要还需要总经理批准,而大于总价大于10万的则需要组织一个会议进行讨论。对于这样一个需求,最直观的方法就是设计一个方法,参数是采购的总价,然后在这个方法内对价格进行调整判断,然后针对不同的条件交给不同级别的人去处理,这样确实可以解决问题,但这样一来,我们就需要多重if-else语句来进行判断,但当加入一个新的条件范围时,我们又不得不去修改原来设计的方法来再添加一个条件判断,这样的设计显然违背了“开-闭”原则。这时候,可以采用责任链模式来解决这样的问题。具体实现代码如下所示。
namespace ChainofResponsibility
{
// 采购请求
public class PurchaseRequest
{
// 金额
public double Amount { get; set; }
// 产品名字
public string ProductName { get; set; }
public PurchaseRequest(double amount, string productName)
{
Amount = amount;
ProductName = productName;
}
}
// 审批人,Handler
public abstract class Approver
{
public Approver NextApprover { get; set; }
public string Name { get; set; }
public Approver(string name)
{
this.Name = name;
}
public abstract void ProcessRequest(PurchaseRequest request);
}
// ConcreteHandler
public class Manager : Approver
{
public Manager(string name)
: base(name)
{ }
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 10000.0)
{
Console.WriteLine("{0}-{1} approved the request of purshing {2}", this, Name, request.ProductName);
}
else if (NextApprover != null)
{
NextApprover.ProcessRequest(request);
}
}
}
// ConcreteHandler,副总
public class VicePresident : Approver
{
public VicePresident(string name)
: base(name)
{
}
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 25000.0)
{
Console.WriteLine("{0}-{1} approved the request of purshing {2}", this, Name, request.ProductName);
}
else if (NextApprover != null)
{
NextApprover.ProcessRequest(request);
}
}
}
// ConcreteHandler,总经理
public class President :Approver
{
public President(string name)
: base(name)
{ }
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 100000.0)
{
Console.WriteLine("{0}-{1} approved the request of purshing {2}", this, Name, request.ProductName);
}
else
{
Console.WriteLine("Request需要组织一个会议讨论");
}
}
}
class Program
{
static void Main(string[] args)
{
PurchaseRequest requestTelphone = new PurchaseRequest(4000.0, "Telphone");
PurchaseRequest requestSoftware = new PurchaseRequest(10000.0, "Visual Studio");
PurchaseRequest requestComputers = new PurchaseRequest(40000.0, "Computers");
Approver manager = new Manager("LearningHard");
Approver Vp = new VicePresident("Tony");
Approver Pre = new President("BossTom");
// 设置责任链
manager.NextApprover = Vp;
Vp.NextApprover = Pre;
// 处理请求
manager.ProcessRequest(requestTelphone);
manager.ProcessRequest(requestSoftware);
manager.ProcessRequest(requestComputers);
Console.ReadLine();
}
}
}
既然,原来的设计会因为价格条件范围的变化而导致不利于扩展,根据“封装变化”的原则,此时我们想的自然是能不能把价格范围细化到不同的类中呢?因为每个价格范围都决定某个批准者,这里就联想到创建多个批准类,这样每个类中只需要针对他自己这个范围的价格判断。这样也就是责任链的最后实现方式了。
责任链模式的优点不言而喻,主要有以下点:
- 降低了请求的发送者和接收者之间的耦合。
- 把多个条件判定分散到各个处理类中,使得代码更加清晰,责任更加明确。
责任链模式也具有一定的缺点,如:
- 在找到正确的处理对象之前,所有的条件判定都要执行一遍,当责任链过长时,可能会引起性能的问题
- 可能导致某个请求不被处理。
模板设计模式
模板设计模式:定义一个操作中的算法的算法的框架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构下,即可重新定义该算法的某些特定步骤。
提到模板,大家肯定不免想到生活中的“简历模板”、“论文模板”、“Word中模版文件”等,在现实生活中,模板的概念就是——有一个规定的格式,然后每个人都可以根据自己的需求或情况去更新它,例如简历模板,下载下来的简历模板的格式都是相同的,然而我们下载下来简历模板之后我们可以根据自己的情况填充不同的内容要完成属于自己的简历。在设计模式中,模板方法模式中模板和生活中模板概念非常类似,下面让我们就详细介绍模板方法的定义,大家可以根据生活中模板的概念来理解模板方法的定义。
模板方法模式——在一个抽象类中定义一个操作中的算法骨架(对应于生活中的大家下载的模板),而将一些步骤延迟到子类中去实现(对应于我们根据自己的情况向模板填充内容)。模板方法使得子类可以不改变一个算法的结构前提下,重新定义算法的某些特定步骤,模板方法模式把不变行为搬到超类中,从而去除了子类中的重复代码。
理解了模板方法的定义之后,自然实现模板方法也不是什么难事了,下面以生活中炒蔬菜为例来实现下模板方法模式。在现实生活中,做蔬菜的步骤都大致相同,如果我们针对每种蔬菜类定义一个烧的方法,这样在每个类中都有很多相同的代码,为了解决这个问题,我们一般的思路肯定是把相同的部分抽象出来到抽象类中去定义,具体子类来实现具体的不同部分,这个思路也正式模板方法的实现精髓所在,具体实现代码如下:
// 客户端调用
class Client
{
static void Main(string[] args)
{
// 创建一个菠菜实例并调用模板方法
Spinach spinach = new Spinach();
spinach.CookVegetabel();
Console.Read();
}
}
public abstract class Vegetabel
{
// 模板方法,不要把模版方法定义为Virtual或abstract方法,避免被子类重写,防止更改流程的执行顺序
public void CookVegetabel()
{
Console.WriteLine("抄蔬菜的一般做法");
this.pourOil();
this.HeatOil();
this.pourVegetable();
this.stir_fry();
}
// 第一步倒油
public void pourOil()
{
Console.WriteLine("倒油");
}
// 把油烧热
public void HeatOil()
{
Console.WriteLine("把油烧热");
}
// 油热了之后倒蔬菜下去,具体哪种蔬菜由子类决定
public abstract void pourVegetable();
// 开发翻炒蔬菜
public void stir_fry()
{
Console.WriteLine("翻炒");
}
}
// 菠菜
public class Spinach : Vegetabel
{
public override void pourVegetable()
{
Console.WriteLine("倒菠菜进锅中");
}
}
// 大白菜
public class ChineseCabbage : Vegetabel
{
public override void pourVegetable()
{
Console.WriteLine("倒大白菜进锅中");
}
}
在上面的实现中,具体子类中重写了导入蔬菜种类的方法,因为这个真是烧菜方法中不同的地方,所以由具体子类去实现它。
模板方法模式中涉及了两个角色:
- 抽象模板角色(Vegetable扮演这个角色):定义了一个或多个抽象操作,以便让子类实现,这些抽象操作称为基本操作。
- 具体模板角色(ChineseCabbage和Spinach扮演这个角色):实现父类所定义的一个或多个抽象方法。
优点:
- 实现了代码复用
- 能够灵活应对子步骤的变化,符合开放-封闭原则
缺点:因为引入了一个抽象类,如果具体实现过多的话,需要用户或开发人员需要花更多的时间去理清类之间的关系。
附:在.NET中模板方法的应用也很多,例如我们在开发自定义的Web控件或WinForm控件时,我们只需要重写某个控件的部分方法。
策略设计模式
策略设计模式:它定义了算法的家族,分别封装起来,让它们之间可以相互替换。此模式让算法的变化,不会影响到使用算法的客户。
在现实生活中,策略模式的例子也非常常见,例如,中国的所得税,分为企业所得税、外商投资企业或外商企业所得税和个人所得税,针对于这3种所得税,针对每种,所计算的方式不同,个人所得税有个人所得税的计算方式,而企业所得税有其对应计算方式。如果不采用策略模式来实现这样一个需求的话,可能我们会定义一个所得税类,该类有一个属性来标识所得税的类型,并且有一个计算税收的CalculateTax()方法,在该方法体内需要对税收类型进行判断,通过if-else语句来针对不同的税收类型来计算其所得税。这样的实现确实可以解决这个场景吗,但是这样的设计不利于扩展,如果系统后期需要增加一种所得税时,此时不得不回去修改CalculateTax方法来多添加一个判断语句,这样明白违背了“开放——封闭”原则。此时,我们可以考虑使用策略模式来解决这个问题,既然税收方法是这个场景中的变化部分,此时自然可以想到对税收方法进行抽象。
前面介绍了策略模式用来解决的问题,下面具体给出策略的定义。策略模式是针对一组算法,将每个算法封装到具有公共接口的独立的类中,从而使它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象负责。策略模式通常把一系列的算法包装到一系列的策略类里面。用一句话慨括策略模式就是——“将每个算法封装到不同的策略类中,使得它们可以互换”。
该模式涉及到三个角色:
- 环境角色(Context):持有一个Strategy类的引用
- 抽象策略角色(Strategy):这是一个抽象角色,通常由一个接口或抽象类来实现。此角色给出所有具体策略类所需实现的接口。
- 具体策略角色(ConcreteStrategy):包装了相关算法或行为。
下面就以所得税的例子来实现下策略模式,具体实现代码如下所示:
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();
}
}
}
在.NET Framework中也不乏策略模式的应用例子。例如,在.NET中,为集合类型ArrayList和List<T>提供的排序功能,其中实现就利用了策略模式,定义了IComparer接口来对比较算法进行封装,实现IComparer接口的类可以是顺序,或逆序地比较两个对象的大小,具体.NET中的实现可以使用反编译工具查看List<T>.Sort(IComparer<T>)的实现。其中List<T>就是承担着环境角色,而IComparer<T>接口承担着抽象策略角色,具体的策略角色就是实现了IComparer<T>接口的类,List<T>类本身实现了存在实现了该接口的类,我们可以自定义继承与该接口的具体策略类。
策略模式的主要优点有:
- 策略类之间可以自由切换。由于策略类都实现同一个接口,所以使它们之间可以自由切换。
- 易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码。
- 避免使用多重条件选择语句,充分体现面向对象设计思想。
策略模式的主要缺点有:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式会造成很多的策略类。
到这里,策略模式的介绍就结束了,策略模式主要是对方法的封装,把一系列方法封装到一系列的策略类中,从而使不同的策略类可以自由切换和避免在系统使用多重条件选择语句来选择针对不同情况来选择不同的方法。
命令设计模式
命令设计模式:它可以将请求转换为一个包含与请求相关的所有信息的独立对象,该转换让你能根据不同的请求将方法参数化、延迟请求执行,或者将其放入队列中,且能实现可撤销操作。
既然,命令模式是实现把发出命令的责任和执行命令的责任分割开,然而中间必须有某个对象来帮助发出命令者来传达命令,使得执行命令的接收者可以收到命令并执行命令。例如,开学了,院领导说计算机学院要进行军训,计算机学院的学生要跑1000米,院领导的话也就相当于一个命令,他不可能直接传达给到学生,他必须让教官来发出命令,并监督学生执行该命令。在这个场景中,发出命令的责任是属于学院领导,院领导充当与命令发出者的角色,执行命令的责任是属于学生,学生充当于命令接收者的角色,而教官就充当于命令的发出者或命令请求者的角色,然而命令模式的精髓就在于把每个命令抽象为对象。
命令模式设计五个角色:
- 客户角色:发出一个具体的命令并确定其接受者。
- 命令角色:声明了一个给所有具体命令类实现的抽象接口
- 具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。
- 请求者角色:负责调用命令对象执行命令。
- 接受者角色:负责具体行为的执行。
军训场景中,具体的命令即是学生跑1000米,这里学生是命令的接收者,教官是命令的请求者,院领导是命令的发出者,即客户端角色。要实现命令模式,则必须需要一个抽象命令角色来声明约定,这里以抽象类来来表示。命令的传达流程是:
命令的发出者必须知道具体的命令、接受者和传达命令的请求者,对应于程序也就是在客户端角色中需要实例化三个角色的实例对象了。
命令的请求者负责调用命令对象的方法来保证命令的执行,对应于程序也就是请求者对象需要有命令对象的成员,并在请求者对象的方法内执行命令。
具体命令就是跑1000米,这自然属于学生的责任,所以是具体命令角色的成员方法,而抽象命令类定义这个命令的抽象接口。
有了上面的分析之后,具体命令模式的实现代码如下所示:
// 教官,负责调用命令对象执行请求
public class Invoke
{
public Command _command;
public Invoke(Command command)
{
this._command = command;
}
public void ExecuteCommand()
{
_command.Action();
}
}
// 命令抽象类
public abstract class Command
{
// 命令应该知道接收者是谁,所以有Receiver这个成员变量
protected Receiver _receiver;
public Command(Receiver receiver)
{
this._receiver = receiver;
}
// 命令执行方法
public abstract void Action();
}
//
public class ConcreteCommand :Command
{
public ConcreteCommand(Receiver receiver)
: base(receiver)
{
}
public override void Action()
{
// 调用接收的方法,因为执行命令的是学生
_receiver.Run1000Meters();
}
}
// 命令接收者——学生
public class Receiver
{
public void Run1000Meters()
{
Console.WriteLine("跑1000米");
}
}
// 院领导
class Program
{
static void Main(string[] args)
{
// 初始化Receiver、Invoke和Command
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoke i = new Invoke(c);
// 院领导发出命令
i.ExecuteCommand();
}
}
命令模式使得命令发出的一个和接收的一方实现低耦合,从而有以下的优点:
- 命令模式使得新的命令很容易被加入到系统里。
- 可以设计一个命令队列来实现对请求的Undo和Redo操作。
- 可以较容易地将命令写入日志。
- 可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。
命令模式的缺点:
- 使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。
备忘录设计模式
备忘录设计模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态
备忘录模式中主要有三类角色:
- 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。
- 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。
- 管理者角色:负责保存备忘录对象。
下面以备份手机通讯录为例子来实现了备忘录模式,具体的实现代码如下所示:
// 联系人
public class ContactPerson
{
public string Name { get; set; }
public string MobileNum { get; set; }
}
// 发起人
public class MobileOwner
{
// 发起人需要保存的内部状态
public List<ContactPerson> ContactPersons { get; set; }
public MobileOwner(List<ContactPerson> persons)
{
ContactPersons = persons;
}
// 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{
// 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List<ContactPerson>(this.ContactPersons));
}
// 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{
// 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.contactPersonBack;
}
public void Show()
{
Console.WriteLine("联系人列表中有{0}个人,他们是:", ContactPersons.Count);
foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum);
}
}
}
// 备忘录
public class ContactMemento
{
// 保存发起人的内部状态
public List<ContactPerson> contactPersonBack;
public ContactMemento(List<ContactPerson> persons)
{
contactPersonBack = persons;
}
}
// 管理角色
public class Caretaker
{
public ContactMemento ContactM { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<ContactPerson> persons = new List<ContactPerson>()
{
new ContactPerson() { Name= "Learning Hard", MobileNum = "123445"},
new ContactPerson() { Name = "Tony", MobileNum = "234565"},
new ContactPerson() { Name = "Jock", MobileNum = "231455"}
};
MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show();
// 创建备忘录并保存备忘录对象
Caretaker caretaker = new Caretaker();
caretaker.ContactM = mobileOwner.CreateMemento();
// 更改发起人联系人列表
Console.WriteLine("----移除最后一个联系人--------");
mobileOwner.ContactPersons.RemoveAt(2);
mobileOwner.Show();
// 恢复到原始状态
Console.WriteLine("-------恢复联系人列表------");
mobileOwner.RestoreMemento(caretaker.ContactM);
mobileOwner.Show();
Console.Read();
}
}
上面代码只是保存了一个还原点,即备忘录中只保存了3个联系人的数据,但是,如果想备份多个还原点怎么办呢?即恢复到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("输入的索引大于集合长度!");
}
}
}
}
}
备忘录模式具有以下优点:
- 如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。
- 备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。
当然,备忘录模式也存在一定的缺点:
- 在实际的系统中,可能需要维护多个备份,需要额外的资源,这样对资源的消耗比较严重。
备忘录模式主要思想是——利用备忘录对象来对保存发起人的内部状态,当发起人需要恢复原来状态时,再从备忘录对象中进行获取,在实际开发过程也应用到这点,例如数据库中的事务处理。
状态设计模式
状态设计模式:当一个对象的内在状态发生改变的时候,允许改变该对象的行为,使这个对象看起来像是改变了其类
每个对象都有其对应的状态,而每个状态又对应一些相应的行为,如果某个对象有多个状态时,那么就会对应很多的行为。那么对这些状态的判断和根据状态完成的行为,就会导致多重条件语句,并且如果添加一种新的状态时,需要更改之前现有的代码。这样的设计显然违背了开闭原则。状态模式正是用来解决这样的问题的。状态模式将每种状态对应的行为抽象出来成为单独新的对象,这样状态的变化不再依赖于对象内部的行为。
状态者模式涉及以下三个角色:
- Account类:维护一个State类的一个实例,该实例标识着当前对象的状态。
- State类:抽象状态类,定义了一个具体状态类需要实现的行为约定。
- SilveStater、GoldState和RedState类:具体状态类,实现抽象状态类的每个行为。
下面,就以银行账户的状态来实现下状态者模式。银行账户根据余额可分为RedState、SilverState和GoldState。这些状态分别代表透支账号,新开账户和标准账户。账号余额在【-100.0,0.0】范围表示处于RedState状态,账号余额在【0.0 , 1000.0】范围表示处于SilverState,账号在【1000.0, 100000.0】范围表示处于GoldState状态。下面以这样的一个场景实现下状态者模式,具体实现代码如下所示:
namespace StatePatternSample
{
public class Account
{
public State State {get;set;}
public string Owner { get; set; }
public Account(string owner)
{
this.Owner = owner;
this.State = new SilverState(0.0, this);
}
public double Balance { get {return State.Balance; }} // 余额
// 存钱
public void Deposit(double amount)
{
State.Deposit(amount);
Console.WriteLine("存款金额为 {0:C}——", amount);
Console.WriteLine("账户余额为 =:{0:C}", this.Balance);
Console.WriteLine("账户状态为: {0}", this.State.GetType().Name);
Console.WriteLine();
}
// 取钱
public void Withdraw(double amount)
{
State.Withdraw(amount);
Console.WriteLine("取款金额为 {0:C}——",amount);
Console.WriteLine("账户余额为 =:{0:C}", this.Balance);
Console.WriteLine("账户状态为: {0}", this.State.GetType().Name);
Console.WriteLine();
}
// 获得利息
public void PayInterest()
{
State.PayInterest();
Console.WriteLine("Interest Paid --- ");
Console.WriteLine("账户余额为 =:{0:C}", this.Balance);
Console.WriteLine("账户状态为: {0}", this.State.GetType().Name);
Console.WriteLine();
}
}
// 抽象状态类
public abstract class State
{
// Properties
public Account Account { get; set; }
public double Balance { get; set; } // 余额
public double Interest { get; set; } // 利率
public double LowerLimit { get; set; } // 下限
public double UpperLimit { get; set; } // 上限
public abstract void Deposit(double amount); // 存款
public abstract void Withdraw(double amount); // 取钱
public abstract void PayInterest(); // 获得的利息
}
// Red State意味着Account透支了
public class RedState : State
{
public RedState(State state)
{
// Initialize
this.Balance = state.Balance;
this.Account = state.Account;
Interest = 0.00;
LowerLimit = -100.00;
UpperLimit = 0.00;
}
// 存款
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
// 取钱
public override void Withdraw(double amount)
{
Console.WriteLine("没有钱可以取了!");
}
public override void PayInterest()
{
// 没有利息
}
private void StateChangeCheck()
{
if (Balance > UpperLimit)
{
Account.State = new SilverState(this);
}
}
}
// Silver State意味着没有利息得
public class SilverState :State
{
public SilverState(State state)
: this(state.Balance, state.Account)
{
}
public SilverState(double balance, Account account)
{
this.Balance = balance;
this.Account = account;
Interest = 0.00;
LowerLimit = 0.00;
UpperLimit = 1000.00;
}
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
Balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
Balance += Interest * Balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (Balance < LowerLimit)
{
Account.State = new RedState(this);
}
else if (Balance > UpperLimit)
{
Account.State = new GoldState(this);
}
}
}
// Gold State意味着有利息状态
public class GoldState : State
{
public GoldState(State state)
{
this.Balance = state.Balance;
this.Account = state.Account;
Interest = 0.05;
LowerLimit = 1000.00;
UpperLimit = 1000000.00;
}
// 存钱
public override void Deposit(double amount)
{
Balance += amount;
StateChangeCheck();
}
// 取钱
public override void Withdraw(double amount)
{
Balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
Balance += Interest * Balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (Balance < 0.0)
{
Account.State = new RedState(this);
}
else if (Balance < LowerLimit)
{
Account.State = new SilverState(this);
}
}
}
class App
{
static void Main(string[] args)
{
// 开一个新的账户
Account account = new Account("Learning Hard");
// 进行交易
// 存钱
account.Deposit(1000.0);
account.Deposit(200.0);
account.Deposit(600.0);
// 付利息
account.PayInterest();
// 取钱
account.Withdraw(2000.00);
account.Withdraw(500.00);
// 等待用户输入
Console.ReadKey();
}
}
}
状态者模式的主要优点是:
- 将状态判断逻辑每个状态类里面,可以简化判断的逻辑。
- 当有新的状态出现时,可以通过添加新的状态类来进行扩展,扩展性好。
状态者模式的主要缺点是:
- 如果状态过多的话,会导致有非常多的状态类,加大了开销。
解释器设计模式
解释器设计模式:给定一个语言,定义它的文法中的一种表示,并定义一个解释器,这个解释器使用该表示,来解释语言中的句子