0%

一、引言

在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类)、AccessoriesPhone(挂件手机类)等,这样就会导致 ”子类爆炸“问题,为了解决这个问题,我们可以使用装饰者模式来动态地给一个对象添加额外的职责。下面让我们看看装饰者模式。

二、装饰者模式的详细介绍

2.1 定义

装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任,装饰者模式相比生成子类可以更灵活地增加功能。

2.2 装饰者模式实现

这里以手机和手机配件的例子来演示装饰者模式的实现,具体代码如下:

复制代码

///


/// 手机抽象类,即装饰者模式中的抽象组件类 ///

public abstract class Phone
{ public abstract void Print();
} ///
/// 苹果手机,即装饰着模式中的具体组件类 ///

public class ApplePhone:Phone
{ ///
/// 重写基类方法 ///

public override void Print()
{
Console.WriteLine(“开始执行具体的对象——苹果手机”);
}
} ///
/// 装饰抽象类,要让装饰完全取代抽象组件,所以必须继承自Photo ///

public abstract class Decorator:Phone
{ private Phone phone; public Decorator(Phone p)
{ this.phone = p;
} public override void Print()
{ if (phone != null)
{
phone.Print();
}
}
} ///
/// 贴膜,即具体装饰者 ///

public class Sticker : Decorator
{ public Sticker(Phone p)
: base(p)
{
} public override void Print()
{ base.Print(); // 添加新的行为
AddSticker();
} ///
/// 新的行为方法 ///

public void AddSticker()
{
Console.WriteLine(“现在苹果手机有贴膜了”);
}
} ///
/// 手机挂件 ///

public class Accessories : Decorator
{ public Accessories(Phone p)
: base(p)
{
} public override void Print()
{ base.Print(); // 添加新的行为
AddAccessories();
} ///
/// 新的行为方法 ///

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的手机外壳类,从而,装饰者模式扩展性也非常好。

2.3 装饰者模式的类图

实现完了装饰者模式之后,让我们看看装饰者模式实现中类之间的关系,具体见下图:

在装饰者模式中各个角色有:

  • 抽象构件(Phone)角色:给出一个抽象接口,以规范准备接受附加责任的对象。
  • 具体构件(AppPhone)角色:定义一个将要接收附加责任的类。
  • 装饰(Dicorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(Sticker和Accessories)角色:负责给构件对象 ”贴上“附加的责任。

三、装饰者模式的优缺点

看完装饰者模式的详细介绍之后,我们继续分析下它的优缺点。

优点:

  1. 装饰这模式和继承的目的都是扩展对象的功能,但装饰者模式比继承更灵活
  2. 通过使用不同的具体装饰类以及这些类的排列组合,设计师可以创造出很多不同行为的组合
  3. 装饰者模式有很好地可扩展性

缺点:装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变的更复杂。并且更多的对象会是的差错变得困难,特别是这些对象看上去都很像。

四、使用场景

下面让我们看看装饰者模式具体在哪些情况下使用,在以下情况下应当使用装饰者模式:

  1. 需要扩展一个类的功能或给一个类增加附加责任。
  2. 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能

五、.NET中装饰者模式的实现

在.NET 类库中也有装饰者模式的实现,该类就是System.IO.Stream,下面看看Stream类结构:

上图中,BufferedStream、CryptoStream和GZipStream其实就是两个具体装饰类,这里的装饰者模式省略了抽象装饰角色(Decorator)。下面演示下客户端如何动态地为MemoryStream动态增加功能的。

复制代码

MemoryStream memoryStream = new MemoryStream(new byte[] {95,96,97,98,99}); // 扩展缓冲的功能
BufferedStream buffStream = new BufferedStream(memoryStream); // 添加加密的功能
CryptoStream cryptoStream = new CryptoStream(memoryStream,new AesManaged().CreateEncryptor(),CryptoStreamMode.Write); // 添加压缩功能
GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, true);

复制代码

六、总结

到这里,装饰者模式的介绍就结束了,装饰者模式采用对象组合而非继承的方式实现了再运行时动态地扩展对象功能的能力,而且可以根据需要扩展多个功能,避免了单独使用继承带来的 ”灵活性差“和”多子类衍生问题“。同时它很好地符合面向对象设计原则中 ”优先使用对象组合而非继承“和”开放-封闭“原则。

本专题所有源码:设计模式之装饰者模式

0.C#设计模式–简单工厂模式

1.C#设计模式–工厂方法模式

2.C#设计模式–抽象工厂模式

3.C#设计模式–单例模式

4.C#设计模式–建造者模式

5.C#设计模式–原型模式

6.C#设计模式–设配器模式

7.C#设计模式–装饰器模式

8.C#设计模式–代理模式

9.C#设计模式–外观模式

10.C#设计模式–桥接模式

简单介绍:

  观察者模式(Observer Pattern)是设计模式中行为模式的一种,它解决了上述具有一对多依赖关系的对象的重用问题。此模式的参与者分为两大类,一类是被观察的目标,另一类是观察该目标的观察者们。正因为该模式是基于“一对多”的关系,所以该模式一般是应用于由一个目标对象和N个观察者对象组成(当然也可以扩展为有多个目标对象,但我们现在只讨论前者)的场合。当目标对象的状态发生改变或做出某种行为时,正在观察该目标对象的观察者们将自动地、连锁地作出相应的响应行为。

模式中具有的角色

  1. 抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  2. 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
  3. 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  4. 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

举例子:博客的例子就是一个观察者模式,比如你关注一些作者的博客,当作者有博客发布时候,你就会收到一条该作者发布的博客的消息。

抽象主题:Blog 博客

具体主题:MyBlog   的博客

抽象观察者:IObserver  

具体的观察者:Observer

观察者模式类图:

观察者模式C#代码举例

订阅博客抽象类

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_Observer
3 * Description:
4 * ClassName: Blog
5 * CLRVersion: 4.0.30319.18408
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_Observer
8 * MachineName: JIYONGFEI
9 * CreateTime: 2017/5/23 17:08:10 10 * UpdatedTime: 2017/5/23 17:08:10 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_Observer 18 { 19 ///


20 /// 订阅博客抽象类 21 ///

22 public abstract class Blog 23 { 24 ///
25 /// 保存订阅者列表 26 ///

27 private List observers = new List(); 28
29 ///
30 /// 博主名 31 ///

32 public string BlogName { get; set; } 33
34 ///
35 /// 博客标题 36 ///

37 public string BlogTitle { get; set; } 38
39 ///
40 /// 博客信息 41 ///

42 public string BlogInfo { get; set; } 43
44 ///
45 /// 博客构造函数 46 ///

47 /// 博客标题
48 /// 博客信息
49 public Blog(string name,string blogTitle,string blogInfo) 50 { 51 this.BlogName = name; 52 this.BlogTitle = blogTitle; 53 this.BlogInfo = blogInfo; 54 } 55
56 ///
57 /// 添加一个订阅者 58 ///

59 /// 具体的订阅者对象
60 public void AddObserver(IObserver observer) 61 { 62 if (observers.Contains(observer)) 63 { 64 return; 65 } 66 observers.Add(observer); 67 } 68
69 ///
70 /// 删除一个订阅者 71 ///

72 /// 具体的订阅者对象
73 public void RemoveObserver(IObserver observer) 74 { 75 if (observers.Contains(observer)) 76 { 77 observers.Remove(observer); 78 } 79 } 80
81 ///
82 /// 发布博客通知 83 ///

84 public void PublishBlog() 85 { 86 //遍历通知每一个订阅者
87 foreach (IObserver ob in observers) 88 { 89 if (ob != null) 90 { 91 ob.Receive(this); 92 } 93 } 94 } 95
96 } 97 }

复制代码

具体的博客类

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_Observer
3 * Description:
4 * ClassName: JiYFBlog
5 * CLRVersion: 4.0.30319.18408
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_Observer
8 * MachineName: JIYONGFEI
9 * CreateTime: 2017/5/23 17:21:23 10 * UpdatedTime: 2017/5/23 17:21:23 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_Observer 18 { 19
20 ///


21 /// 具体的订阅博客类 22 ///

23 public class JiYFBlog :Blog 24 { 25 public JiYFBlog(string name,string blogTitile, string blogInfo) 26 : base(name,blogTitile,blogInfo) 27 { 28
29 } 30 } 31 }

复制代码

订阅者接口类

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_Observer
3 * Description:
4 * ClassName: IObserver
5 * CLRVersion: 4.0.30319.18408
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_Observer
8 * MachineName: JIYONGFEI
9 * CreateTime: 2017/5/23 17:09:28 10 * UpdatedTime: 2017/5/23 17:09:28 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_Observer 18 { 19 ///


20 /// 订阅者接口 21 ///

22 public interface IObserver 23 { 24
25 void Receive(Blog blog); 26 } 27 }

复制代码

具体的订阅者类:

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_Observer
3 * Description:
4 * ClassName: Observer
5 * CLRVersion: 4.0.30319.18408
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_Observer
8 * MachineName: JIYONGFEI
9 * CreateTime: 2017/5/23 17:23:36 10 * UpdatedTime: 2017/5/23 17:23:36 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_Observer 18 { 19 ///


20 /// 具体的订阅者类 21 ///

22 public class Observer :IObserver 23 { 24 ///
25 /// 订阅者名字 26 ///

27 private string m_Name; 28 public string Name 29 { 30 get { return m_Name; } 31 set { m_Name = value; } 32 } 33
34 ///
35 /// 订阅者构造函数 36 ///

37 /// 订阅者名字
38 public Observer(string name) 39 { 40 this.m_Name = name; 41 } 42
43 ///
44 /// 订阅者接受函数 45 ///

46 ///
47 public void Receive(Blog blog) 48 { 49 Console.WriteLine(“订阅者:\“{0}\“观察到了:{1}发布的一篇博客,标题为:{2},内容为:{3}”,Name,blog.BlogName,blog.BlogTitle,blog.BlogInfo); 50 } 51 } 52 }

复制代码

测试代码:

复制代码

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5
6 namespace _11DesignPattern_Observer 7 {
8 class Client 9 { 10 static void Main(string[] args) 11 { 12 Console.WriteLine(“--全部订阅者–”); 13 //创建一个JiYF的博客
14 Blog jiyfBlog = new JiYFBlog(“JiYF笨小孩”,”丑小鸭”,”丑小鸭的故事”); 15
16 //创建订阅者
17 Observer obsZhangsan = new Observer(“张三”); 18 Observer obsLiSi = new Observer(“李四”); 19 Observer obsWangwu = new Observer(“王五”); 20
21 //添加对JiYF博客的订阅者
22 jiyfBlog.AddObserver(obsZhangsan); 23 jiyfBlog.AddObserver(obsLiSi); 24 jiyfBlog.AddObserver(obsWangwu); 25
26 //通知订阅者
27 jiyfBlog.PublishBlog(); 28
29 Console.WriteLine(“--移除订阅者张三–”); 30 jiyfBlog.RemoveObserver(obsZhangsan); 31 jiyfBlog.PublishBlog(); 32 Console.ReadLine(); 33 } 34 } 35 }

复制代码

运行结果:

 

通常在C#代码中,观察者模式一般采用委托来实现如下所示

委托实现观察者模式

抽象博客类:

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_ObserverDelegateTest
3 * Description:
4 * ClassName: Blog
5 * CLRVersion: 4.0.30319.42000
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_ObserverDelegateTest
8 * MachineName: JIYF_PC
9 * CreateTime: 2017/5/23 20:48:00 10 * UpdatedTime: 2017/5/23 20:48:00 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_ObserverDelegateTest 18 { 19 ///


20 /// 委托充当订阅者接口类 21 ///

22 ///
23 public delegate void NotifyEventHandler(object sender); 24
25 ///
26 /// 订阅博客基类 27 ///

28 public class Blog 29 { 30 public NotifyEventHandler NotifyEvent; 31 ///
32 /// 博主名 33 ///

34 public string BlogName { get; set; } 35
36 ///
37 /// 博客标题 38 ///

39 public string BlogTitle { get; set; } 40
41 ///
42 /// 博客信息 43 ///

44 public string BlogInfo { get; set; } 45
46 ///
47 /// 博客构造函数 48 ///

49 /// 博客标题
50 /// 博客信息
51 public Blog(string name, string blogTitle, string blogInfo) 52 { 53 this.BlogName = name; 54 this.BlogTitle = blogTitle; 55 this.BlogInfo = blogInfo; 56 } 57
58 ///
59 /// 绑定订阅事件 60 ///

61 /// 订阅者
62 public void AddObserver(NotifyEventHandler observer) 63 { 64 NotifyEvent += observer; 65 } 66
67 ///
68 /// 取消绑定订阅 69 ///

70 ///
71 public void RemoteObserver(NotifyEventHandler observer) 72 { 73 NotifyEvent -= observer; 74 } 75
76 ///
77 /// 发布博客通知 78 ///

79 public void PublishBlog() 80 { 81 if(NotifyEvent != null) 82 { 83 NotifyEvent(this); 84 } 85 } 86 } 87 }

复制代码

具体的博客类

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_ObserverDelegateTest
3 * Description:
4 * ClassName: JiYFBlog
5 * CLRVersion: 4.0.30319.42000
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_ObserverDelegateTest
8 * MachineName: JIYF_PC
9 * CreateTime: 2017/5/23 20:55:58 10 * UpdatedTime: 2017/5/23 20:55:58 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_ObserverDelegateTest 18 { 19 ///


20 /// 具体的订阅博客类 21 ///

22 public class JiYFBlog : Blog 23 { 24 public JiYFBlog(string name, string blogTitile, string blogInfo) 25 : base(name, blogTitile, blogInfo) 26 { 27
28 } 29 } 30 }

复制代码

订阅者类

复制代码

1 /*****************************************************
2 * ProjectName: _11DesignPattern_ObserverDelegateTest
3 * Description:
4 * ClassName: Observer
5 * CLRVersion: 4.0.30319.42000
6 * Author: JiYF
7 * NameSpace: _11DesignPattern_ObserverDelegateTest
8 * MachineName: JIYF_PC
9 * CreateTime: 2017/5/23 20:56:50 10 * UpdatedTime: 2017/5/23 20:56:50 11 *****************************************************/
12 using System; 13 using System.Collections.Generic; 14 using System.Linq; 15 using System.Text; 16
17 namespace _11DesignPattern_ObserverDelegateTest 18 { 19 ///


20 /// 具体的订阅者类 21 ///

22 public class Observer 23 { 24 ///
25 /// 订阅者名字 26 ///

27 private string m_Name; 28 public string Name 29 { 30 get { return m_Name; } 31 set { m_Name = value; } 32 } 33
34 ///
35 /// 订阅者构造函数 36 ///

37 /// 订阅者名字
38 public Observer(string name) 39 { 40 this.m_Name = name; 41 } 42
43 ///
44 /// 订阅者接受函数 45 ///

46 ///
47 public void Receive(object obj) 48 { 49 Blog blog = obj as Blog; 50 if (blog != null) 51 { 52 Console.WriteLine(“订阅者:\“{0}\“观察到了:{1}发布的一篇博客,标题为:{2},内容为:{3}”, Name, blog.BlogName, blog.BlogTitle, blog.BlogInfo); 53 } 54 } 55 } 56 }

复制代码

测试代码

复制代码

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5
6 namespace _11DesignPattern_ObserverDelegateTest 7 {
8 class Client 9 { 10 static void Main(string[] args) 11 { 12 Console.WriteLine(“--全部订阅者–”); 13 //创建一个JiYF的博客
14 Blog jiyfBlog = new Blog(“JiYF笨小孩”,”丑小鸭”,”丑小鸭的故事”); 15
16 //创建订阅者
17 Observer obsZhangsan = new Observer(“张三”); 18 Observer obsLiSi = new Observer(“李四”); 19 Observer obsWangwu = new Observer(“王五”); 20
21 //添加对JiYF博客的订阅者
22 jiyfBlog.AddObserver(new NotifyEventHandler(obsZhangsan.Receive)); 23 jiyfBlog.AddObserver(new NotifyEventHandler(obsLiSi.Receive)); 24 jiyfBlog.AddObserver(new NotifyEventHandler(obsWangwu.Receive)); 25
26 ////通知订阅者
27 jiyfBlog.PublishBlog(); 28
29 Console.WriteLine(“-–移除订阅者张三–”); 30 jiyfBlog.RemoteObserver(new NotifyEventHandler(obsZhangsan.Receive)); 31 jiyfBlog.PublishBlog(); 32 Console.ReadLine(); 33 } 34 } 35 }

复制代码

运行结果:

 

源代码工程文件下载

一、引言

     C#版本的23种设计模式已经写完了,现在也到了一个该总结的时候了。说起设计模式,我的话就比较多了。刚开始写代码的时候,有需求就写代码来解决需求,如果有新的需求,或者需求变了,我就想当然的修改自己的代码来满足新的需求,这样做感觉是理所当然的,也没感觉有什么不妥的地方。写了两年多代码,偶尔一次,听说了设计模式,据听说设计模式就是软件界的“独孤九剑”,学会之后就可以天下无敌,于是我就开始了我的进修之路。

       想当初,我就像很多的初学编程的人一样,在面试官面前很容易说出面向对象语言的三大特征:继承,多态和封装,其实,我根本不理解这三个词语的意思,或者说理解的很浅薄。当然也有很多概念是不清楚的,比如:OOPL(面向对象语言)是不是就是OO的全部,面向对象的设计模式和设计模式的到底有什么区别,等等相关问题。自从自己学了设计模式,很多概念变得清晰明了,做事有原则了,或者说有准则了,不像以前只是解决了问题就好,根本不管代码写的是否合理。写的代码可以很优美了,结构更合理了,自己开始重新思考代码这个东西。

     学习设计模式并不是一帆风顺的,尤其是刚开始的时候,由于自己很多概念不是很清楚,也走了很多弯路,但是通过自己的坚持,通过自己不断的看,不断的写,对模式的理解也越来越准确了。写代码不是一个很简单的事情,做任何事都是有原则的,写代码也一样,如果自己心中对做的事情没有准则,那就和无头的苍蝇一样,做与不做是一样的。写代码和写好代码是不一样的,如果要写好的代码,考虑的问题更多了,考虑稳定性,扩展性和耦合性,当然也要考虑上游和下游关联的问题,让你做事的时候知道怎么做,也知道为什么这么做,这就是学习设计模式带来的好处。

     好了,说了也不少了,我把我写的模式都罗列出来,每个模式都有链接,可以直接点击进入查看和阅读,希望对大家阅读有益。

系列导航:

创建型:

    C#设计模式(1)——单例模式(Singleton Pattern)

    C#设计模式(2)——工厂方法模式(Factory Pattern)

    C#设计模式(3)——抽象工厂模式(Abstract Pattern)

    C#设计模式(4)——建造模式(Builder Pattern)

    C#设计模式(5)——原型模式(Prototype Pattern)

结构型:

    C#设计模式(6)——适配器模式(Adapter Pattern)

    C#设计模式(7)——桥接模式(Bridge Pattern)

    C#设计模式(8)——装饰模式(Decorator Pattern)

    C#设计模式(9)——组合模式(Composite Pattern)

    C#设计模式(10)——外观模式(Facade Pattern)

    C#设计模式(11)——享元模式(Flyweight Pattern)

    C#设计模式(12)——代理模式(Proxy Pattern)

行为型:

    C#设计模式(13)——模板方法模式(Template Method)

    C#设计模式(14)——命令模式(Command Pattern)

    C#设计模式(15)——迭代器模式(Iterator Pattern)

    C#设计模式(16)——观察者模式(Observer Pattern)

    C#设计模式(17)——中介者模式(Mediator Pattern)

    C#设计模式(18)——状态模式(State Pattern)

    C#设计模式(19)——策略模式(Stragety Pattern)

    C#设计模式(20)——责任链模式(Chain of Responsibility Pattern)

    C#设计模式(21)——访问者模式(Vistor Pattern)

    C#设计模式(22)——备忘录模式(Memento Pattern)

    C#设计模式(23)——解释器模式(Interpreter Pattern)

二、 面向对象的设计原则

       写代码也是有原则的,我们之所以使用设计模式,主要是为了适应变化,提高代码复用率,使软件更具有可维护性和可扩展性。如果我们能更好的理解这些设计原则,对我们理解面向对象的设计模式也是有帮助的,因为这些模式的产生是基于这些原则的。这些规则是:单一职责原则(SRP)、开放封闭原则(OCP)、里氏代替原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)、合成复用原则(CRP)和迪米特原则(LoD)。下面我们就分别介绍这几种设计原则。

   2.1、单一职责原则(SRP):

  (1)、SRP(Single Responsibilities Principle)的定义:就一个类而言,应该仅有一个引起它变化的原因。简而言之,就是功能要单一。

  (2)、如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。(敏捷软件开发)

  (3)、软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。

     小结:单一职责原则(SRP)可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。责任过多,引起它变化的原因就越多,这样就会导致职责依赖,大大损伤其内聚性和耦合度。

   2.2、开放关闭原则(OCP)

  (1)、OCP(Open-Close Principle)的定义:就是说软件实体(类,方法等等)应该可以扩展(扩展可以理解为增加),但是不能在原来的方法或者类上修改,也可以这样说,对增加代码开放,对修改代码关闭。

  (2)、OCP的两个特征: 对于扩展(增加)是开放的,因为它不影响原来的,这是新增加的。对于修改是封闭的,如果总是修改,逻辑会越来越复杂。

     小结:开放封闭原则(OCP)是面向对象设计的核心思想。遵循这个原则可以为我们面向对象的设计带来巨大的好处:可维护(维护成本小,做管理简单,影响最小)、可扩展(有新需求,增加就好)、可复用(不耦合,可以使用以前代码)、灵活性好(维护方便、简单)。开发人员应该仅对程序中出现频繁变化的那些部分做出抽象,但是不能过激,对应用程序中的每个部分都刻意地进行抽象同样也不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

   2.3、里氏代替原则(LSP)

      (1)、LSP(Liskov Substitution Principle)的定义:子类型必须能够替换掉它们的父类型。更直白的说,LSP是实现面向接口编程的基础。

      小结:任何基类可以出现的地方,子类一定可以出现,所以我们可以实现面向接口编程。 LSP是继承复用的基石,只有当子类可以替换掉基类,软件的功能不受到影响时,基类才能真正被复用,而子类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

   2.4、依赖倒置原则(DIP)

     (1)、DIP(Dependence Inversion Principle)的定义:抽象不应该依赖细节,细节应该依赖于抽象。简单说就是,我们要针对接口编程,而不要针对实现编程。

     (2)、高层模块不应该依赖低层模块,两个都应该依赖抽象,因为抽象是稳定的。抽象不应该依赖具体(细节),具体(细节)应该依赖抽象。

      小结:依赖倒置原则其实可以说是面向对象设计的标志,如果在我们编码的时候考虑的是面向接口编程,而不是简单的功能实现,体现了抽象的稳定性,只有这样才符合面向对象的设计。

   2.5 接口隔离原则(ISP)

     (1)、接口隔离原则(Interface Segregation Principle, ISP)指的是使用多个专门的接口比使用单一的总接口要好。也就是说不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。过于臃肿的接口是对接口的一种污染。

     (2)、使用多个专门的接口比使用单一的总接口要好。

     (3)、一个类对另外一个类的依赖性应当是建立在最小的接口上的。

     (4)、一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。

     (5)、“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。

       小结:接口隔离原则(ISP)告诉我们,在做接口设计的时候,要尽量设计的接口功能单一,功能单一,使它变化的因素就少,这样就更稳定,其实这体现了高内聚,低耦合的原则,这样做也避免接口的污染。

   2.6 组合复用原则(CRP)

     (1)、组合复用原则(Composite Reuse Principle, CRP)就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。新对象通过向这些对象的委派达到复用已用功能的目的。简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。

     (2)、要使用好组合复用原则,首先需要区分”Has—A”和“Is—A”的关系。 “Is—A”是指一个类是另一个类的“一种”,是属于的关系,而“Has—A”则不同,它表示某一个角色具有某一项责任。导致错误的使用继承而不是聚合的常见的原因是错误地把“Has—A”当成“Is—A”.例如:鸡是动物,这就是“Is-A”的表现,某人有一个手枪,People类型里面包含一个Gun类型,这就是“Has-A”的表现。

     小结:组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏替换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

   2.7 迪米特法则(Law of Demeter)

      (1)、迪米特法则(Law of Demeter,LoD)又叫最少知识原则(Least Knowledge Principle,LKP),指的是一个对象应当对其他对象有尽可能少的了解。也就是说,一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。

      (2)、关于迪米特法则其他的一些表述有:只与你直接的朋友们通信;不要跟“陌生人”说话。

      (3)、外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。

       小结:迪米特法则的初衷是降低类之间的耦合,实现类型之间的高内聚,低耦合,这样可以解耦。但是凡事都有度,过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

三、创建型模式

    创建型模式就是用来解决对象实例化和使用的客户端耦合的模式,可以让客户端和对象实例化都独立变化,做到相互不影响。创建型模式包括单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。

    3.1、单例模式(Singleton Pattern):解决的是实例化对象的个数的问题,该模式是把对象的数量控制为一个,该模式可以扩展,可以把实例对象扩展为N个对象,N>=2。比如对象池的实现。

       动机(Motivate):在软件系统构建的过程中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?如果指望使用者不使用构造器来重复创建对象,这是不对的。这应该是类设计者的责任,而不是使用者的责任。

       意图(Intent):保证一个类仅有一个实例,并提供一个该实例的全局访问点。

       具体结构图如下所示
      

       示例代码:

复制代码

1 ///


2 /// 单例模式的实现 3 ///

4 public sealed class Singleton 5 {
6 // 定义一个静态变量来保存类的实例
7 private static volatile Singleton uniqueInstance; 8
9 // 定义一个标识确保线程同步
10 private static readonly object locker = new object(); 11
12 // 定义私有构造函数,使外界不能创建该类实例
13 private Singleton() 14 { 15 } 16
17 ///
18 /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 19 ///

20 ///
21 public static Singleton GetInstance() 22 { 23 // 当第一个线程运行到这里时,此时会对locker对象 “加锁”, 24 // 当第二个线程运行该方法时,首先检测到locker对象为”加锁”状态,该线程就会挂起等待第一个线程解锁 25 // lock语句运行完之后(即线程运行完之后)会对该对象”解锁” 26 // 双重锁定只需要一句判断就可以了
27 if (uniqueInstance == null) 28 { 29 lock (locker) 30 { 31 // 如果类的实例不存在则创建,否则直接返回
32 if (uniqueInstance == null) 33 { 34 uniqueInstance = new Singleton(); 35 } 36 } 37 } 38 return uniqueInstance; 39 } 40 }

复制代码

   

3.2、工厂方法模式(Factory Method Pattern):一种工厂生产一种产品,工厂类和产品类是一一对应的,他们是平行的等级结构,强调的是“单个对象”的变化。

       动机(Motivate):在软件系统的构建过程中,经常面临着“某个对象”的创建工作:由于需求的变化,这个对象(的具体实现)经常面临着剧烈的变化,但是它却拥有比较稳定的接口。如何应对这种变化?如何提供一种“封装机制”来隔离出“这个易变对象”的变化,从而保持系统中“其他依赖对象的对象”不随着需求改变而改变?

       意图(Intent):定义一个创建对象的工厂接口,由其子类决定要实例化的类,将实际创建工作推迟到子类中。

       具体结构图如下所示:

    3.3、抽象工厂模式(Abstract Factory Pattern):该模式关注的是多批多系列相互依赖的产品的变化,比如:SQLConnection,SQLCommand,SqlDataReader,SqlDataAdapter,就是一批相互依赖的对象,他们变化可以产生OledbConnection,OledbCommand,OledbDataReader,OledbDataAdapter

       动机(Motivate):在软件系统中,经常面临着”一系统相互依赖的对象”的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种”封装机制”来避免客户程序和这种”多系列具体对象创建工作”的紧耦合?

       意图(Intent):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

       具体结构图如下所示

           

       代码实例

复制代码

1 ///


2 /// 下面以不同系列房屋的建造为例子演示抽象工厂模式 3 /// 因为每个人的喜好不一样,我喜欢欧式的,我弟弟就喜欢现代的 4 /// 客户端调用 5 ///

6 class Client 7 {
8 static void Main(string[] args)
9 {
10 // 哥哥的欧式风格的房子
11 AbstractFactory europeanFactory= new EuropeanFactory(); 12 europeanFactory.CreateRoof().Create();
13 europeanFactory.CreateFloor().Create();
14 europeanFactory.CreateWindow().Create();
15 europeanFactory.CreateDoor().Create();
16
17
18 //弟弟的现代风格的房子
19 AbstractFactory modernizationFactory = new ModernizationFactory(); 20 modernizationFactory.CreateRoof().Create();
21 modernizationFactory.CreateFloor().Create();
22 modernizationFactory.CreateWindow().Create();
23 modernizationFactory.CreateDoor().Create();
24 Console.Read();
25 }
26 }
27
28 ///
29 /// 抽象工厂类,提供创建不同类型房子的接口 30 ///

31 public abstract class AbstractFactory 32 {
33 // 抽象工厂提供创建一系列产品的接口,这里作为例子,只给出了房顶、地板、窗户和房门创建接口
34 public abstract Roof CreateRoof(); 35 public abstract Floor CreateFloor(); 36 public abstract Window CreateWindow(); 37 public abstract Door CreateDoor(); 38 }
39
40 ///
41 /// 欧式风格房子的工厂,负责创建欧式风格的房子 42 ///

43 public class EuropeanFactory : AbstractFactory 44 {
45 // 制作欧式房顶
46 public override Roof CreateRoof() 47 {
48 return new EuropeanRoof(); 49 }
50
51 // 制作欧式地板
52 public override Floor CreateFloor() 53 {
54 return new EuropeanFloor(); 55 }
56
57 // 制作欧式窗户
58 public override Window CreateWindow() 59 {
60 return new EuropeanWindow(); 61 }
62
63 // 制作欧式房门
64 public override Door CreateDoor() 65 {
66 return new EuropeanDoor(); 67 }
68 }
69
70 ///
71 /// 现在风格房子的工厂,负责创建现代风格的房子 72 ///

73 public class ModernizationFactory : AbstractFactory 74 {
75 // 制作现代房顶
76 public override Roof CreateRoof() 77 {
78 return new ModernizationRoof(); 79 }
80
81 // 制作现代地板
82 public override Floor CreateFloor() 83 {
84 return new ModernizationFloor(); 85 }
86
87 // 制作现代窗户
88 public override Window CreateWindow() 89 {
90 return new ModernizationWindow(); 91 }
92
93 // 制作现代房门
94 public override Door CreateDoor() 95 {
96 return new ModernizationDoor(); 97 }
98 }
99
100 ///
101 /// 房顶抽象类,子类的房顶必须继承该类 102 ///

103 public abstract class Roof 104 { 105 ///
106 /// 创建房顶 107 ///

108 public abstract void Create(); 109 } 110
111 ///
112 /// 地板抽象类,子类的地板必须继承该类 113 ///

114 public abstract class Floor 115 { 116 ///
117 /// 创建地板 118 ///

119 public abstract void Create(); 120 } 121
122 ///
123 /// 窗户抽象类,子类的窗户必须继承该类 124 ///

125 public abstract class Window 126 { 127 ///
128 /// 创建窗户 129 ///

130 public abstract void Create(); 131 } 132
133 ///
134 /// 房门抽象类,子类的房门必须继承该类 135 ///

136 public abstract class Door 137 { 138 ///
139 /// 创建房门 140 ///

141 public abstract void Create(); 142 } 143
144 ///
145 /// 欧式地板类 146 ///

147 public class EuropeanFloor : Floor 148 { 149 public override void Create() 150 { 151 Console.WriteLine(“创建欧式的地板”); 152 } 153 } 154
155
156 ///
157 /// 欧式的房顶 158 ///

159 public class EuropeanRoof : Roof 160 { 161 public override void Create() 162 { 163 Console.WriteLine(“创建欧式的房顶”); 164 } 165 } 166
167
168 ///
169 ///欧式的窗户 170 ///

171 public class EuropeanWindow : Window 172 { 173 public override void Create() 174 { 175 Console.WriteLine(“创建欧式的窗户”); 176 } 177 } 178
179
180 ///
181 /// 欧式的房门 182 ///

183 public class EuropeanDoor : Door 184 { 185 public override void Create() 186 { 187 Console.WriteLine(“创建欧式的房门”); 188 } 189 } 190
191 ///
192 /// 现代的房顶 193 ///

194 public class ModernizationRoof : Roof 195 { 196 public override void Create() 197 { 198 Console.WriteLine(“创建现代的房顶”); 199 } 200 } 201
202 ///
203 /// 现代的地板 204 ///

205 public class ModernizationFloor : Floor 206 { 207 public override void Create() 208 { 209 Console.WriteLine(“创建现代的地板”); 210 } 211 } 212
213 ///
214 /// 现代的窗户 215 ///

216 public class ModernizationWindow : Window 217 { 218 public override void Create() 219 { 220 Console.WriteLine(“创建现代的窗户”); 221 } 222 } 223
224 ///
225 /// 现代的房门 226 ///

227 public class ModernizationDoor : Door 228 { 229 public override void Create() 230 { 231 Console.WriteLine(“创建现代的房门”); 232 } 233 }

复制代码

   

3.4、建造者模式(Builder Pattern):该模式要解决的是由多个子部分对象构成的一个复杂对象的创建的问题,该复杂对象构成算法稳定,各个子部分对象易变化的情况。强调组装过程的稳定。

       动机(Motivate):在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?

       意图(Intent):将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

                                  将一个产品的表示形式与产品的组装过程分割开来,从而可以使同一个组装过程(这个构建过程是稳定的,也就是算法)生成具体不同的表现的产品对象。

       具体结构图如下所示

                

    3.5、原型模式(Prototype Pattern):通过制定实例类型来复制对象

       动机(Motivate):在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

       意图(Intent):使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。

       具体结构图如下所示:

四、结构型模式

    结构型模式主要研究的是类和对象的组合的问题。它包括两种类型,一是类结构型模式:指的是采用继承机制来组合实现功能;二是对象结构型模式:指的是通过组合对象的方式来实现新的功能。该系列模式包括:适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式和代理模式。

    4.1、适配器模式(Adapter Pattern):该模式主要关注的是接口转换的问题,将匹配的接口通过适配对接工作。

       动机(Motivate):在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

       意图(Intent):将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

                                  适配器模式意在转换接口,它能够使原本不能再一起工作的两个类一起工作,所以经常用来在类库的复用、代码迁移等方面。 适配器模式包括类适配器模式和对象适配器模式,

       具体结构图如下所示:

       类适配器模式:

       对象适配器模式:

    4.2、桥接模式(Bridge Pattern):该模式注重分离接口与其实现,接口是针对客户的,接口的内在实现是通过“实现层次”来完成的,支持多维度变化。

       动机(Motivate):在很多游戏场景中,会有这样的情况:【装备】本身会有的自己固有的逻辑,比如枪支,会有型号的问题,同时现在很多的游戏又在不同的介质平台上运行和使用,这样就使得游戏的【装备】具有了两个变化的维度——一个变化的维度为“平台的变化”,另一个变化的维度为“型号的变化”。如果我们要写代码实现这款游戏,难道我们针对每种平台都实现一套独立的【装备】吗?复用在哪里?如何应对这种“多维度的变化”?如何利用面向对象技术来使得【装备】可以轻松地沿着“平台”和“型号”两个方向变化,而不引入额外的复杂度?

       意图(Intent):将抽象部分与实现部分分离,使它们都可以独立地变化。

                       比如:就拿游戏装备来说,“手枪”,抽象部分是指手枪的型号,这个型号可以是G50,G55,针对不同平台,这些型号有不同的实现,针对这些不同平台的不同型号的实现,重新抽象,抽象的结构就是“实现的层次”,其实,到此,就形成了两个抽象层次,第一个层次,是枪支的型号的层次,另一个层次就是针对其实现的一个层次,这样就做到了抽象和实现的分离。

       具体结构图如下所示

          

       示例代码:

复制代码

1 namespace 桥接模式的实现 2 {
3 ///


4 /// 该抽象类就是抽象接口的定义,该类型就相当于是Abstraction类型 5 ///

6 public abstract class Database 7 {
8 //通过组合方式引用平台接口,此处就是桥梁,该类型相当于Implementor类型
9 protected PlatformImplementor _implementor; 10
11 //通过构造器注入,初始化平台实现
12 protected Database(PlatformImplementor implementor) 13 { 14 this._implementor = implementor; 15 } 16
17 //创建数据库–该操作相当于Abstraction类型的Operation方法
18 public abstract void Create(); 19 } 20
21 ///
22 /// 该抽象类就是实现接口的定义,该类型就相当于是Implementor类型 23 ///

24 public abstract class PlatformImplementor 25 { 26 //该方法就相当于Implementor类型的OperationImpl方法
27 public abstract void Process(); 28 } 29
30 ///
31 /// SqlServer2000版本的数据库,相当于RefinedAbstraction类型 32 ///

33 public class SqlServer2000 : Database 34 { 35 //构造函数初始化
36 public SqlServer2000(PlatformImplementor implementor) : base(implementor) { } 37
38 public override void Create() 39 { 40 this._implementor.Process(); 41 } 42 } 43
44 ///
45 /// SqlServer2005版本的数据库,相当于RefinedAbstraction类型 46 ///

47 public class SqlServer2005 : Database 48 { 49 //构造函数初始化
50 public SqlServer2005(PlatformImplementor implementor) : base(implementor) { } 51
52 public override void Create() 53 { 54 this._implementor.Process(); 55 } 56 } 57
58 ///
59 /// SqlServer2000版本的数据库针对Unix操作系统具体的实现,相当于ConcreteImplementorA类型 60 ///

61 public class SqlServer2000UnixImplementor : PlatformImplementor 62 { 63 public override void Process() 64 { 65 Console.WriteLine(“SqlServer2000针对Unix的具体实现”); 66 } 67 } 68
69 ///
70 /// SqlServer2005版本的数据库针对Unix操作系统的具体实现,相当于ConcreteImplementorB类型 71 ///

72 public sealed class SqlServer2005UnixImplementor : PlatformImplementor 73 { 74 public override void Process() 75 { 76 Console.WriteLine(“SqlServer2005针对Unix的具体实现”); 77 } 78 } 79
80 public class Program 81 { 82 static void Main() 83 { 84 PlatformImplementor SqlServer2000UnixImp = new SqlServer2000UnixImplementor(); 85 //还可以针对不同平台进行扩展,也就是子类化,这个是独立变化的
86
87 Database SqlServer2000Unix = new SqlServer2000(SqlServer2000UnixImp); 88 //数据库版本也可以进行扩展和升级,也进行独立的变化。 89
90 //以上就是两个维度的变化。 91
92 //就可以针对Unix执行操作了
93 SqlServer2000Unix.Create(); 94 } 95 } 96 }

复制代码

   

4.3、装饰模式(Decorator Pattern):该模式注重稳定接口,让接口不变,在此前提下为对象动态(关键点)的扩展功能。如果通过继承,各个功能点的组合就会形成过多的子类,维护起来就是麻烦。

       动机(Motivate):在房子装修的过程中,各种功能可以相互组合,来增加房子的功用。类似的,如果我们在软件系统中,要给某个类型或者对象增加功能,如果使用“继承”的方案来写代码,就会出现子类暴涨的情况。比如:IMarbleStyle是大理石风格的一个功能,IKeepWarm是保温的一个接口定义,IHouseSecurity是房子安全的一个接口,就三个接口来说,House是我们房子,我们的房子要什么功能就实现什么接口,如果房子要的是复合功能,接口不同的组合就有不同的结果,这样就导致我们子类膨胀严重,如果需要在增加功能,子类会成指数增长。这个问题的根源在于我们“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态特质(所谓静态特质,就是说如果想要某种功能,我们必须在编译的时候就要定义这个类,这也是强类型语言的特点。静态,就是指在编译的时候要确定的东西;动态,是指运行时确定的东西),使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀(多继承)。如何使“对象功能的扩展”能够根据需要来动态(即运行时)地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?

       意图(Intent):动态地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类更为灵活。

       具体结构图如下所示

              

       示例代码:

复制代码

1 namespace 装饰模式的实现 2 {
3 ///


4 /// 该抽象类就是房子抽象接口的定义,该类型就相当于是Component类型,是饺子馅,需要装饰的,需要包装的 5 ///

6 public abstract class House 7 {
8 //房子的装修方法–该操作相当于Component类型的Operation方法
9 public abstract void Renovation(); 10 } 11
12 ///
13 /// 该抽象类就是装饰接口的定义,该类型就相当于是Decorator类型,如果需要具体的功能,可以子类化该类型 14 ///

15 public abstract class DecorationStrategy:House //关键点之二,体现关系为Is-a,有这这个关系,装饰的类也可以继续装饰了
16 { 17 //通过组合方式引用Decorator类型,该类型实施具体功能的增加 18 //这是关键点之一,包含关系,体现为Has-a
19 protected House _house; 20
21 //通过构造器注入,初始化平台实现
22 protected DecorationStrategy(House house) 23 { 24 this._house=house; 25 } 26
27 //该方法就相当于Decorator类型的Operation方法
28 public override void Renovation() 29 { 30 if(this._house!=null) 31 { 32 this._house.Renovation(); 33 } 34 } 35 } 36
37 ///
38 /// PatrickLiu的房子,我要按我的要求做房子,相当于ConcreteComponent类型,这就是我们具体的饺子馅,我个人比较喜欢韭菜馅 39 ///

40 public sealed class PatrickLiuHouse:House 41 { 42 public override void Renovation() 43 { 44 Console.WriteLine(“装修PatrickLiu的房子”); 45 } 46 } 47
48
49 ///
50 /// 具有安全功能的设备,可以提供监视和报警功能,相当于ConcreteDecoratorA类型 51 ///

52 public sealed class HouseSecurityDecorator:DecorationStrategy 53 { 54 public HouseSecurityDecorator(House house):base(house){} 55
56 public override void Renovation() 57 { 58 base.Renovation(); 59 Console.WriteLine(“增加安全系统”); 60 } 61 } 62
63 ///
64 /// 具有保温接口的材料,提供保温功能,相当于ConcreteDecoratorB类型 65 ///

66 public sealed class KeepWarmDecorator:DecorationStrategy 67 { 68 public KeepWarmDecorator(House house):base(house){} 69
70 public override void Renovation() 71 { 72 base.Renovation(); 73 Console.WriteLine(“增加保温的功能”); 74 } 75 } 76
77 public class Program 78 { 79 static void Main() 80 { 81 //这就是我们的饺子馅,需要装饰的房子
82 House myselfHouse=new PatrickLiuHouse(); 83
84 DecorationStrategy securityHouse=new HouseSecurityDecorator(myselfHouse); 85 securityHouse.Renovation(); 86 //房子就有了安全系统了 87
88 //如果我既要安全系统又要保暖呢,继续装饰就行
89 DecorationStrategy securityAndWarmHouse=new HouseSecurityDecorator(securityHouse); 90 securityAndWarmHouse.Renovation(); 91 } 92 } 93 }

复制代码

复制代码

1 namespace 命令模式的实现 2 {
3 ///


4 /// 俗话说:“好吃不如饺子,舒服不如倒着”。今天奶奶发话要吃他大孙子和孙媳妇包的饺子。今天还拿吃饺子这件事来说说命令模式的实现吧。 5 ///

6 class Client 7 {
8 static void Main(string[] args)
9 { 10 //奶奶想吃猪肉大葱馅的饺子
11 PatrickLiuAndWife liuAndLai = new PatrickLiuAndWife();//命令接受者
12 Command command = new MakeDumplingsCommand(liuAndLai);//命令
13 PaPaInvoker papa = new PaPaInvoker(command); //命令请求者 14
15 //奶奶发布命令
16 papa.ExecuteCommand(); 17
18
19 Console.Read(); 20 } 21 } 22
23 //这个类型就是请求者角色–也就是我爸爸的角色,告诉奶奶要吃饺子
24 public sealed class PaPaInvoker 25 { 26 //我爸爸从奶奶那里接受到的命令
27 private Command _command; 28
29 //爸爸开始接受具体的命令
30 public PaPaInvoker(Command command) 31 { 32 this._command = command; 33 } 34
35 //爸爸给我们下达命令
36 public void ExecuteCommand() 37 { 38 _command.MakeDumplings(); 39 } 40 } 41
42 //该类型就是抽象命令角色–Commmand,定义了命令的抽象接口,任务是包饺子
43 public abstract class Command 44 { 45 //真正任务的接受者
46 protected PatrickLiuAndWife _worker; 47
48 protected Command(PatrickLiuAndWife worker) 49 { 50 _worker = worker; 51 } 52
53 //该方法就是抽象命令对象Command的Execute方法
54 public abstract void MakeDumplings(); 55 } 56
57 //该类型是具体命令角色–ConcreteCommand,这个命令完成制作“猪肉大葱馅”的饺子
58 public sealed class MakeDumplingsCommand : Command 59 { 60 public MakeDumplingsCommand(PatrickLiuAndWife worker) : base(worker) { } 61
62 //执行命令–包饺子
63 public override void MakeDumplings() 64 { 65 //执行命令—包饺子
66 _worker.Execute(“今天包的是农家猪肉和农家大葱馅的饺子”); 67 } 68 } 69
70 //该类型是具体命令接受角色Receiver,具体包饺子的行为是我们夫妻俩来完成的
71 public sealed class PatrickLiuAndWife 72 { 73 //这个方法相当于Receiver类型的Action方法
74 public void Execute(string job) 75 { 76 Console.WriteLine(job); 77 } 78 } 79 }

复制代码

   

4.4、组合模式(Composite Pattern):该模式着重解决统一接口的问题,将“一对多”的关系转化为“一对一”的关系,让使用叶子节点和树干节点保持接口一致。

       动机(Motivate):客户代码过多地依赖于对象容器(对象容器是对象的容器,细细评味)复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等方面的弊端。如何将“客户代码与复杂的对象容器内部结构”解耦?如何让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

       意图(Intent):将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

       具体结构图如下所示:

    4.5、外观模式(Facade Pattern):该模式注重简化接口,简化组件系统与外部客户程序的依赖关系,是Lod(迪米特原则,也叫:最少知识原则)原则的最好的体现,

       动机(Motivate):在软件系统开发的过程中,当组件的客户(即外部接口,或客户程序)和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。如何简化外部客户程序和系统间的交互接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦?

       意图(Intent):为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

       具体结构图如下所示:

    4.6、享元模式(Flyweight Pattern):该模式注重保留接口,在内部使用共享技术对对象存储进行优化

       动机(Motivate):在软件系统中,采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价。如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?

       意图(Intent):运用共享技术有效地支持大量细粒度的对象。

                                 在.NET类库中,String类的实现就使用了享元模式,String类采用字符串驻留池的来使字符串进行共享。

       具体结构图如下所示

           

    4.7、代理模式(Proxy Pattern):该模式注重假借代理接口,控制对真实对象的访问,通过增加间接层来实现灵活控制,比如可以在访问正真对象之前进行权限验证,生活中的明星的代理就是很好的例子,要想接触明星,先要和他的经纪人打交道。

       动机(Motivate):在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦。如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。

       意图(Intent):为其他对象提供一种代理以控制对这个对象的访问。

       具体结构图如下所示:

             

五、行为型模式

    行为型模式主要讨论的是在不同对象之间划分责任和算法的抽象化的问题。行为型模式又分为类的行为模式和对象的行为模式两种。

    类的行为模式——使用继承关系在几个类之间分配行为。

对象的行为模式——使用对象聚合的方式来分配行为。

    行为型模式包括11种模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、状态模式、策略模式、责任链模式、访问者模式、解释器模式和备忘录模式。

    5.1、模板方法模式(Template Method Pattern):该模式注重对算法结构的封装,定义算法骨架,并且稳定,但是支持算法子步骤的变化。

       动机(Motivate):在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?

       意图(Intent):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

       具体结构图如下所示

             

    5.2、命令模式(Command Pattern):该模式注重将请求封装为对象,通过将一组行为抽象为对象,实现行为请求者和行为实现者之间的解耦。也可以实现“撤销/重做”的功能,类似“宏”的功能实现也很容易。

       动机(Motivate):在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

       意图(Intent):将一个请求封装为一个对象,从而使你可用不同的请求对客户(客户程序,也是行为的请求者)进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。命令模式的实现可以提供命令的撤销和恢复功能。

       具体结构图如下所示

            

       代码实例

复制代码

1 namespace 命令模式的实现 2 {
3 ///


4 /// 俗话说:“好吃不如饺子,舒服不如倒着”。今天奶奶发话要吃他大孙子和孙媳妇包的饺子。今天还拿吃饺子这件事来说说命令模式的实现吧。 5 ///

6 class Client 7 {
8 static void Main(string[] args)
9 { 10 //奶奶想吃猪肉大葱馅的饺子
11 PatrickLiuAndWife liuAndLai = new PatrickLiuAndWife();//命令接受者
12 Command command = new MakeDumplingsCommand(liuAndLai);//命令
13 PaPaInvoker papa = new PaPaInvoker(command); //命令请求者 14
15 //奶奶发布命令
16 papa.ExecuteCommand(); 17
18
19 Console.Read(); 20 } 21 } 22
23 //这个类型就是请求者角色–也就是我爸爸的角色,告诉奶奶要吃饺子
24 public sealed class PaPaInvoker 25 { 26 //我爸爸从奶奶那里接受到的命令
27 private Command _command; 28
29 //爸爸开始接受具体的命令
30 public PaPaInvoker(Command command) 31 { 32 this._command = command; 33 } 34
35 //爸爸给我们下达命令
36 public void ExecuteCommand() 37 { 38 _command.MakeDumplings(); 39 } 40 } 41
42 //该类型就是抽象命令角色–Commmand,定义了命令的抽象接口,任务是包饺子
43 public abstract class Command 44 { 45 //真正任务的接受者
46 protected PatrickLiuAndWife _worker; 47
48 protected Command(PatrickLiuAndWife worker) 49 { 50 _worker = worker; 51 } 52
53 //该方法就是抽象命令对象Command的Execute方法
54 public abstract void MakeDumplings(); 55 } 56
57 //该类型是具体命令角色–ConcreteCommand,这个命令完成制作“猪肉大葱馅”的饺子
58 public sealed class MakeDumplingsCommand : Command 59 { 60 public MakeDumplingsCommand(PatrickLiuAndWife worker) : base(worker) { } 61
62 //执行命令–包饺子
63 public override void MakeDumplings() 64 { 65 //执行命令—包饺子
66 _worker.Execute(“今天包的是农家猪肉和农家大葱馅的饺子”); 67 } 68 } 69
70 //该类型是具体命令接受角色Receiver,具体包饺子的行为是我们夫妻俩来完成的
71 public sealed class PatrickLiuAndWife 72 { 73 //这个方法相当于Receiver类型的Action方法
74 public void Execute(string job) 75 { 76 Console.WriteLine(job); 77 } 78 } 79 }

复制代码

   

5.3、迭代器模式(Iterator Pattern):该模式注重封装对集合的操作,支持集合实例的变化,屏蔽集合对象内部复杂结构,提供客户程序对它的透明遍历。

       动机(Motivate):在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。

       意图(Intent): 提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

       具体结构图如下所示

            

    5.4、观察者模式(Observer Pattern):该模式注重的是变化通知,变化通知指目标对象发生变化,依赖的对象就能获得通知并进行相应操作,这是一种“一对多”的关系。

       动机(Motivate):在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。

       意图(Intent):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

       具体结构图如下所示

              

    5.5、中介者模式(Mediator Pattern):该模式注重封装对象间的交互,通过封装一系列对象之间的复杂交互,使他们不需要显式相互引用,实现解耦。

       动机(Motivate):在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断地变化。在这种情况下,我们可使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。

       意图(Intent):定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。

       具体的结构图如下所示

           

       代码实例

复制代码

1 namespace 中介者模式的实现 2 {
3 //抽象中介者角色
4 public interface Mediator 5 {
6 void Command(Department department); 7 }
8
9 //总经理–相当于具体中介者角色
10 public sealed class President : Mediator 11 {
12 //总经理有各个部门的管理权限
13 private Financial _financial; 14 private Market _market; 15 private Development _development; 16
17 public void SetFinancial(Financial financial) 18 {
19 this._financial = financial; 20 }
21 public void SetDevelopment(Development development) 22 {
23 this._development = development; 24 }
25 public void SetMarket(Market market) 26 {
27 this._market = market; 28 }
29
30 public void Command(Department department) 31 {
32 if (department.GetType() == typeof(Market))
33 {
34 _financial.Process();
35 }
36 }
37 }
38
39 //同事类的接口
40 public abstract class Department 41 {
42 //持有中介者(总经理)的引用
43 private Mediator mediator; 44
45 protected Department(Mediator mediator) 46 {
47 this.mediator = mediator; 48 }
49
50 public Mediator GetMediator 51 {
52 get { return mediator; } 53 private set { this.mediator = value; } 54 }
55
56 //做本部门的事情
57 public abstract void Process(); 58
59 //向总经理发出申请
60 public abstract void Apply(); 61 }
62
63 //开发部门
64 public sealed class Development : Department 65 {
66 public Development(Mediator m) : base(m) { }
67
68 public override void Process() 69 {
70 Console.WriteLine(“我们是开发部门,要进行项目开发,没钱了,需要资金支持!”);
71 }
72
73 public override void Apply() 74 {
75 Console.WriteLine(“专心科研,开发项目!”);
76 }
77 }
78
79 //财务部门
80 public sealed class Financial : Department 81 {
82 public Financial(Mediator m) : base(m) { }
83
84 public override void Process() 85 {
86 Console.WriteLine(“汇报工作!没钱了,钱太多了!怎么花?”);
87 }
88
89 public override void Apply() 90 {
91 Console.WriteLine(“数钱!”);
92 }
93 }
94
95 //市场部门
96 public sealed class Market : Department 97 {
98 public Market(Mediator mediator) : base(mediator) { }
99
100 public override void Process() 101 { 102 Console.WriteLine(“汇报工作!项目承接的进度,需要资金支持!”); 103 GetMediator.Command(this); 104 } 105
106 public override void Apply() 107 { 108 Console.WriteLine(“跑去接项目!”); 109 } 110 } 111
112
113 class Program 114 { 115 static void Main(String[] args) 116 { 117 President mediator = new President(); 118 Market market = new Market(mediator); 119 Development development = new Development(mediator); 120 Financial financial = new Financial(mediator); 121
122 mediator.SetFinancial(financial); 123 mediator.SetDevelopment(development); 124 mediator.SetMarket(market); 125
126 market.Process(); 127 market.Apply(); 128
129 Console.Read(); 130 } 131 } 132 }

复制代码

   

5.6、状态模式(State Pattern):该模式注重封装与状态相关的行为(定义状态类,会把和一个状态相关的操作都放到这个类里面),支持状态的变化,从而在其内部状态改变时改变它的行为。

       动机(Motivate):在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?

       意图(Intent):允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。

       具体结构图如下所示

              

       代码实例

复制代码

1 namespace 状态模式的实现 2 {
3 //环境角色—相当于Context类型
4 public sealed class Order 5 {
6 private State current; 7
8 public Order() 9 {
10 //工作状态初始化为上午工作状态
11 current = new WaitForAcceptance(); 12 IsCancel = false;
13 }
14 private double minute; 15 public double Minute 16 {
17 get { return minute; } 18 set { minute = value; } 19 }
20
21 public bool IsCancel { get; set; }
22
23 private bool finish; 24 public bool TaskFinished 25 {
26 get { return finish; } 27 set { finish = value; } 28 }
29 public void SetState(State s) 30 {
31 current = s; 32 }
33 public void Action() 34 {
35 current.Process(this);
36 }
37 }
38
39 //抽象状态角色—相当于State类型
40 public interface State 41 {
42 //处理订单
43 void Process(Order order); 44 }
45
46 //等待受理–相当于具体状态角色
47 public sealed class WaitForAcceptance : State 48 {
49 public void Process(Order order) 50 {
51 System.Console.WriteLine(“我们开始受理,准备备货!”);
52 if (order.Minute < 30 && order.IsCancel) 53 {
54 System.Console.WriteLine(“接受半个小时之内,可以取消订单!”);
55 order.SetState(new CancelOrder()); 56 order.TaskFinished = true;
57 order.Action();
58 }
59 order.SetState(new AcceptAndDeliver()); 60 order.TaskFinished = false;
61 order.Action();
62 }
63 }
64
65 //受理发货—相当于具体状态角色
66 public sealed class AcceptAndDeliver : State 67 {
68 public void Process(Order order) 69 {
70 System.Console.WriteLine(“我们货物已经准备好,可以发货了,不可以撤销订单!”);
71 if (order.Minute < 30 && order.IsCancel) 72 {
73 System.Console.WriteLine(“接受半个小时之内,可以取消订单!”);
74 order.SetState(new CancelOrder()); 75 order.TaskFinished = true;
76 order.Action();
77 }
78 if (order.TaskFinished==false)
79 {
80 order.SetState(new Success()); 81 order.Action();
82 }
83 }
84 }
85
86 //交易成功—相当于具体状态角色
87 public sealed class Success : State 88 {
89 public void Process(Order order) 90 {
91 System.Console.WriteLine(“订单结算”);
92 order.SetState(new ConfirmationReceipt()); 93 order.TaskFinished = true;
94 order.Action();
95 }
96 }
97
98 //确认收货—相当于具体状态角色
99 public sealed class ConfirmationReceipt : State 100 { 101 public void Process(Order order) 102 { 103 System.Console.WriteLine(“检查货物,没问题可以就可以签收!”); 104 order.SetState(new ConfirmationReceipt()); 105 order.TaskFinished = true; 106 order.Action(); 107 } 108 } 109
110 //取消订单—相当于具体状态角色
111 public sealed class CancelOrder : State 112 { 113 public void Process(Order order) 114 { 115 System.Console.WriteLine(“检查货物,有问题,取消订单!”); 116 order.SetState(new CancelOrder()); 117 order.TaskFinished = true; 118 order.Action(); 119 } 120 } 121
122
123 public class Client 124 { 125 public static void Main(String[] args) 126 { 127 //订单
128 Order order = new Order(); 129 order.Minute = 9; 130 order.Action(); 131 //可以取消订单
132 order.IsCancel = true; 133 order.Minute = 20; 134 order.Action(); 135 order.Minute = 33; 136 order.Action(); 137 order.Minute = 43; 138 order.Action(); 139
140 Console.Read(); 141 } 142 } 143 }

复制代码

   

5.7、策略模式(Stragety Pattern):该模式注重封装算法,这里面没有算法骨架,一种算法就是一种解决方案,一种方法策略。支持算法的变化,通过封装一系列算法,可以做到算法的替换来满足客户的需求。

       动机(Motivate): 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?

       意图(Intent):定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用它的客户而变化。

       具体结构图如下所示

          

    5.8、责任链模式(Chain of Responsibility Pattern):该模式注重封装对象责任,支持责任的变化,通过动态构建职责链,实现业务处理。在现实生活中,请假流程,采购流程等都是职责链模式很好的例子。

       动机(Motivate):在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少地带来请求发送者与接受者的紧耦合。如何使请求的发送者不需要指定具体的接受者,让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。

       意图(Intent):避免请求发送者与接收者耦合在一起,让多个对象都有可能接受请求,将这些对象连接成一条链,并且沿着这条链传递请求,知道有对象处理它为止。

       具体结构图如下所示

            

    5.9、访问者模式(Visitor Pattern):该模式注重封装对象操作变化,支持在运行时为类结构添加新的操作,在类层次结构中,在不改变各类的前提下定义作用于这些类实例的新的操作。

       动机(Motivate):在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

       意图(Intent):表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。

       具体结构图如下所示

            

       代码实例

复制代码

1 namespace Vistor 2 {
3 //抽象图形定义—相当于“抽象节点角色”Element
4 public abstract class Shape 5 {
6 //画图形
7 public abstract void Draw(); 8 //外界注入具体访问者
9 public abstract void Accept(ShapeVisitor visitor); 10 }
11
12 //抽象访问者 Visitor
13 public abstract class ShapeVisitor 14 {
15 public abstract void Visit(Rectangle shape); 16
17 public abstract void Visit(Circle shape); 18
19 public abstract void Visit(Line shape); 20
21 //这里有一点要说:Visit方法的参数可以写成Shape吗?就是这样 Visit(Shape shape),当然可以,但是ShapeVisitor子类Visit方法就需要判断当前的Shape是什么类型,是Rectangle类型,是Circle类型,或者是Line类型。
22 }
23
24 //具体访问者 ConcreteVisitor
25 public sealed class CustomVisitor : ShapeVisitor 26 {
27 //针对Rectangle对象
28 public override void Visit(Rectangle shape) 29 {
30 Console.WriteLine(“针对Rectangle新的操作!”);
31 }
32 //针对Circle对象
33 public override void Visit(Circle shape) 34 {
35 Console.WriteLine(“针对Circle新的操作!”);
36 }
37 //针对Line对象
38 public override void Visit(Line shape) 39 {
40 Console.WriteLine(“针对Line新的操作!”);
41 }
42 }
43
44 //矩形—-相当于“具体节点角色” ConcreteElement
45 public sealed class Rectangle : Shape 46 {
47 public override void Draw() 48 {
49 Console.WriteLine(“矩形我已经画好!”);
50 }
51
52 public override void Accept(ShapeVisitor visitor) 53 {
54 visitor.Visit(this);
55 }
56 }
57
58 //圆形—相当于“具体节点角色”ConcreteElement
59 public sealed class Circle : Shape 60 {
61 public override void Draw() 62 {
63 Console.WriteLine(“圆形我已经画好!”);
64 }
65
66 public override void Accept(ShapeVisitor visitor) 67 {
68 visitor.Visit(this);
69 }
70 }
71
72 //直线—相当于“具体节点角色” ConcreteElement
73 public sealed class Line : Shape 74 {
75 public override void Draw() 76 {
77 Console.WriteLine(“直线我已经画好!”);
78 }
79
80 public override void Accept(ShapeVisitor visitor) 81 {
82 visitor.Visit(this);
83 }
84 }
85
86 //结构对象角色
87 internal class AppStructure 88 {
89 private ShapeVisitor _visitor; 90
91 public AppStructure(ShapeVisitor visitor) 92 {
93 this._visitor = visitor; 94 }
95
96 public void Process(Shape shape) 97 {
98 shape.Accept(_visitor);
99 } 100 } 101
102 class Program 103 { 104 static void Main(string[] args) 105 { 106 //如果想执行新增加的操作
107 ShapeVisitor visitor = new CustomVisitor(); 108 AppStructure app = new AppStructure(visitor); 109
110 Shape shape = new Rectangle(); 111 shape.Draw();//执行自己的操作
112 app.Process(shape);//执行新的操作
113
114
115 shape = new Circle(); 116 shape.Draw();//执行自己的操作
117 app.Process(shape);//执行新的操作
118
119
120 shape = new Line(); 121 shape.Draw();//执行自己的操作
122 app.Process(shape);//执行新的操作
123
124
125 Console.ReadLine(); 126 } 127 } 128 }

复制代码

   

5.10、备忘录模式(Memento Pattern):该模式注重封装对象状态变化,支持状态保存、恢复。现实生活中的手机通讯录备忘录,操作系统备份,数据库备份等都是备忘录模式的应用。

       动机(Motivate):在软件构建过程中,某些对象的状态在转换的过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。如何实现对象状态的良好保存与恢复,但同时又不会因此而破坏对象本身的封装性?

       意图(Intent):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态(如果没有这个关键点,其实深拷贝就可以解决问题)。这样以后就可以将该对象恢复到原先保存的状态。

       具体的结构图如下所示

          

       代码实例

复制代码

1 namespace MementoPattern 2 {
3 // 联系人–需要备份的数据,是状态数据,没有操作
4 public sealed class ContactPerson 5 {
6 //姓名
7 public string Name { get; set; }
8
9 //电话号码
10 public string MobileNumber { get; set; }
11 }
12
13 // 发起人–相当于【发起人角色】Originator
14 public sealed class MobileBackOriginator 15 {
16 // 发起人需要保存的内部状态
17 private List _personList; 18
19
20 public List ContactPersonList 21 {
22 get
23 {
24 return this._personList;
25 }
26
27 set
28 {
29 this._personList = value; 30 }
31 }
32 //初始化需要备份的电话名单
33 public MobileBackOriginator(List personList) 34 {
35 if (personList != null)
36 {
37 this._personList = personList; 38 }
39 else
40 {
41 throw new ArgumentNullException(“参数不能为空!”);
42 }
43 }
44
45 // 创建备忘录对象实例,将当期要保存的联系人列表保存到备忘录对象中
46 public ContactPersonMemento CreateMemento() 47 {
48 return new ContactPersonMemento(new List(this._personList));
49 }
50
51 // 将备忘录中的数据备份还原到联系人列表中
52 public void RestoreMemento(ContactPersonMemento memento) 53 {
54 this.ContactPersonList = memento.ContactPersonListBack; 55 }
56
57 public void Show() 58 {
59 Console.WriteLine(“联系人列表中共有{0}个人,他们是:”, ContactPersonList.Count);
60 foreach (ContactPerson p in ContactPersonList) 61 {
62 Console.WriteLine(“姓名: {0} 号码: {1}”, p.Name, p.MobileNumber);
63 }
64 }
65 }
66
67 // 备忘录对象,用于保存状态数据,保存的是当时对象具体状态数据–相当于【备忘录角色】Memeto
68 public sealed class ContactPersonMemento 69 {
70 // 保存发起人创建的电话名单数据,就是所谓的状态
71 public List ContactPersonListBack { get; private set; }
72
73 public ContactPersonMemento(List personList) 74 {
75 ContactPersonListBack = personList; 76 }
77 }
78
79 // 管理角色,它可以管理【备忘录】对象,如果是保存多个【备忘录】对象,当然可以对保存的对象进行增、删等管理处理—相当于【管理者角色】Caretaker
80 public sealed class MementoManager 81 {
82 //如果想保存多个【备忘录】对象,可以通过字典或者堆栈来保存,堆栈对象可以反映保存对象的先后顺序
83 //比如:public Dictionary<string, ContactPersonMemento> ContactPersonMementoDictionary { get; set; }
84 public ContactPersonMemento ContactPersonMemento { get; set; }
85 }
86
87 class Program 88 {
89 static void Main(string[] args)
90 {
91 List persons = new List()
92 {
93 new ContactPerson() { Name=”黄飞鸿”, MobileNumber = “13533332222”},
94 new ContactPerson() { Name=”方世玉”, MobileNumber = “13966554433”},
95 new ContactPerson() { Name=”洪熙官”, MobileNumber = “13198765544”}
96 };
97
98 //手机名单发起人
99 MobileBackOriginator mobileOriginator = new MobileBackOriginator(persons); 100 mobileOriginator.Show(); 101
102 // 创建备忘录并保存备忘录对象
103 MementoManager manager = new MementoManager(); 104 manager.ContactPersonMemento = mobileOriginator.CreateMemento(); 105
106 // 更改发起人联系人列表
107 Console.WriteLine(“-—移除最后一个联系人——–”); 108 mobileOriginator.ContactPersonList.RemoveAt(2); 109 mobileOriginator.Show(); 110
111 // 恢复到原始状态
112 Console.WriteLine(“-——恢复联系人列表——“); 113 mobileOriginator.RestoreMemento(manager.ContactPersonMemento); 114 mobileOriginator.Show(); 115
116 Console.Read(); 117 } 118 } 119 }

复制代码

   

5.11、解释器模式(Interpreter Pattern):该模式注重封装特定领域变化,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。C#的编译器,中英翻译工具,正则表达式都是解释器应用的很好例子。

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

       意图(Intent):给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子。

       具体的结构图如下所示

                           

六、总结

    C#版本23种面向对象的设计模式,终于写完了,今天是一个总结性的文章。各个模式都列了出来,每个模式的【动机】、【意图】和【结构图】也写了出来,可以方便大家查看。重新自己写了一遍,感觉很多都不一样了。理解更深刻了,学无止境,继续前进吧。

  经过这段时间对设计模式的学习,自己的感触还是很多的,因为我现在在写代码的时候,经常会想想这里能不能用什么设计模式来进行重构。所以,学完设计模式之后,感觉它会慢慢地影响到你写代码的思维方式。这里对设计模式做一个总结,一来可以对所有设计模式进行一个梳理,二来可以做一个索引来帮助大家收藏。

  PS: 其实,很早之前我就看过所有的设计模式了,但是并没有写博客,但是不久就很快忘记了,也没有起到什么作用,这次以博客的形式总结出来,发现效果还是很明显的,因为通过这种总结的方式,我对它理解更深刻了,也记住的更牢靠了,也影响了自己平时实现功能的思维。所以,我鼓励大家可以通过做笔记的方式来把自己学到的东西进行梳理,这样相信可以理解更深,更好,我也会一直写下来,之后打算写WCF一系列文章。

  其实WCF内容很早也看过了,并且博客园也有很多前辈写的很好,但是,我觉得我还是需要自己总结,因为只有这样,知识才是自己的,别人写的多好,你看了之后,其实还是别人了,所以鼓励大家几点(对于这几点,也是对自己的一个提醒):

  1. 要动手实战别人博客中的例子;
  2. 实现之后进行总结,可以写博客也可以自己记录云笔记等;
  3. 想想能不能进行扩展,进行举一反三。

系列导航:

C#设计模式(1)——单例模式

C#设计模式(2)——简单工厂模式

C#设计模式(3)——工厂方法模式

C#设计模式(4)——抽象工厂模式

C#设计模式(5)——建造者模式(Builder Pattern)

C#设计模式(6)——原型模式(Prototype Pattern)

C#设计模式(7)——适配器模式(Adapter Pattern)

C#设计模式(8)——桥接模式(Bridge Pattern)

C#设计模式(9)——装饰者模式(Decorator Pattern)

  C#设计模式(10)——组合模式(Composite Pattern)

  C#设计模式(11)——外观模式(Facade Pattern)

  C#设计模式(12)——享元模式(Flyweight Pattern)

  C#设计模式(13)——代理模式(Proxy Pattern)

  C#设计模式(14)——模板方法模式(Template Method)

  C#设计模式(15)——命令模式(Command Pattern)

  C#设计模式(16)——迭代器模式(Iterator Pattern)

  C#设计模式(17)——观察者模式(Observer Pattern)

  C#设计模式(18)——中介者模式(Mediator Pattern)

  C#设计模式(19)——状态者模式(State Pattern)

  C#设计模式(20)——策略者模式(Stragety Pattern)

  C#设计模式(21)——责任链模式

  C#设计模式(22)——访问者模式(Vistor Pattern)

  C#设计模式(23)——备忘录模式(Memento Pattern)

  使用设计模式的根本原因是适应变化,提高代码复用率,使软件更具有可维护性和可扩展性。并且,在进行设计的时候,也需要遵循以下几个原则:单一职责原则、开放封闭原则、里氏代替原则、依赖倒置原则、接口隔离原则、合成复用原则和迪米特法则。下面就分别介绍了每种设计原则。

2.1 单一职责原则

  就一个类而言,应该只有一个引起它变化的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会影响到其他的职责,另外,把多个职责耦合在一起,也会影响复用性。

2.2 开闭原则(Open-Closed Principle)

  开闭原则即OCP(Open-Closed Principle缩写)原则,该原则强调的是:一个软件实体(指的类、函数、模块等)应该对扩展开放,对修改关闭。即每次发生变化时,要通过添加新的代码来增强现有类型的行为,而不是修改原有的代码。

  符合开闭原则的最好方式是提供一个固有的接口,然后让所有可能发生变化的类实现该接口,让固定的接口与相关对象进行交互。

2.3 里氏代替原则(Liskov Substitution Principle)

  Liskov Substitution Principle,LSP(里氏代替原则)指的是子类必须替换掉它们的父类型。也就是说,在软件开发过程中,子类替换父类后,程序的行为是一样的。只有当子类替换掉父类后,此时软件的功能不受影响时,父类才能真正地被复用,而子类也可以在父类的基础上添加新的行为。为了就来看看违反了LSP原则的例子,具体代码如下所示:

复制代码

public class Rectangle
{ public virtual long Width { get; set; } public virtual long Height { get; set; }
} // 正方形
public class Square : Rectangle
{ public override long Height
{ get { return base.Height;
} set { base.Height = value; base.Width = value;
}
} public override long Width
{ get { return base.Width;
} set { base.Width = value; base.Height = value;
}
}
} class Test
{ public void Resize(Rectangle r)
{ while (r.Height >= r.Width)
{
r.Width += 1;
}
} var r = new Square() { Width = 10, Height = 10 }; new Test().Resize(r);
}

复制代码

  上面的设计,正如上面注释的一样,在执行SmartTest的resize方法时,如果传入的是长方形对象,当高度大于宽度时,会自动增加宽度直到超出高度。但是如果传入的是正方形对象,则会陷入死循环。此时根本原因是,矩形不能作为正方形的父类,既然出现了问题,可以进行重构,使它们俩都继承于四边形类。重构后的代码如下所示:

复制代码

// 四边形
public abstract class Quadrangle
{ public virtual long Width { get; set; } public virtual long Height { get; set; }
} // 矩形
public class Rectangle : Quadrangle
{ public override long Height { get; set; } public override long Width { get; set; }

} // 正方形
public class Square : Quadrangle
{ public long \_side; public Square(long side)
    {
        \_side \= side;
    }
} class Test
{ public void Resize(Quadrangle r)
    { while (r.Height >= r.Width)
        {
            r.Width += 1;
        }
    } static void Main(string\[\] args)
    { var s = new Square(10); new Test().Resize(s);
    }
}

复制代码

2.4 依赖倒置原则

  依赖倒置(Dependence Inversion Principle, DIP)原则指的是抽象不应该依赖于细节,细节应该依赖于抽象,也就是提出的 “面向接口编程,而不是面向实现编程”。这样可以降低客户与具体实现的耦合。

2.5 接口隔离原则

  接口隔离原则(Interface Segregation Principle, ISP)指的是使用多个专门的接口比使用单一的总接口要好。也就是说不要让一个单一的接口承担过多的职责,而应把每个职责分离到多个专门的接口中,进行接口分离。过于臃肿的接口是对接口的一种污染。

2.6 合成复用原则

  合成复用原则(Composite Reuse Principle, CRP)就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分。新对象通过向这些对象的委派达到复用已用功能的目的。简单地说,就是要尽量使用合成/聚合,尽量不要使用继承。

  要使用好合成复用原则,首先需要区分”Has—A”和“Is—A”的关系。

  “Is—A”是指一个类是另一个类的“一种”,是属于的关系,而“Has—A”则不同,它表示某一个角色具有某一项责任。导致错误的使用继承而不是聚合的常见的原因是错误地把“Has—A”当成“Is—A”.例如:

实际上,雇员、经历、学生描述的是一种角色,比如一个人是“经理”必然是“雇员”。在上面的设计中,一个人无法同时拥有多个角色,是“雇员”就不能再是“学生”了,这显然不合理,因为现在很多在职研究生,即使雇员也是学生。

  上面的设计的错误源于把“角色”的等级结构与“人”的等级结构混淆起来了,误把“Has—A”当作”Is—A”。具体的解决方法就是抽象出一个角色类:

2.7 迪米特法则

  迪米特法则(Law of Demeter,LoD)又叫最少知识原则(Least Knowledge Principle,LKP),指的是一个对象应当对其他对象有尽可能少的了解。也就是说,一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。

  关于迪米特法则其他的一些表述有:只与你直接的朋友们通信;不要跟“陌生人”说话。

  外观模式(Facade Pattern)和中介者模式(Mediator Pattern)就使用了迪米特法则。

   创建型模式就是用来创建对象的模式,抽象了实例化的过程。所有的创建型模式都有两个共同点。第一,它们都将系统使用哪些具体类的信息封装起来;第二,它们隐藏了这些类的实例是如何被创建和组织的。创建型模式包括单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式。

  • 单例模式:解决的是实例化对象的个数的问题,比如抽象工厂中的工厂、对象池等,除了Singleton之外,其他创建型模式解决的都是 new 所带来的耦合关系。
  • 抽象工厂:创建一系列相互依赖对象,并能在运行时改变系列。
  • 工厂方法:创建单个对象,在Abstract Factory有使用到。
  • 原型模式:通过拷贝原型来创建新的对象。

  工厂方法,抽象工厂, 建造者都需要一个额外的工厂类来负责实例化“一个对象”,而Prototype则是通过原型(一个特殊的工厂类)来克隆“易变对象”。

  下面详细介绍下它们。

3.1  单例模式

   单例模式指的是确保某一个类只有一个实例,并提供一个全局访问点。解决的是实体对象个数的问题,而其他的建造者模式都是解决new所带来的耦合关系问题。其实现要点有:

  • 类只有一个实例。问:如何保证呢?答:通过私有构造函数来保证类外部不能对类进行实例化
  • 提供一个全局的访问点。问:如何实现呢?答:创建一个返回该类对象的静态方法

  单例模式的结构图如下所示:

3.2 工厂方法模式

   工厂方法模式指的是定义一个创建对象的工厂接口,由其子类决定要实例化的类,将实际创建工作推迟到子类中。它强调的是”单个对象“的变化。其实现要点有:

  • 定义一个工厂接口。问:如何实现呢?答:声明一个工厂抽象类
  • 由其具体子类创建对象。问:如何去实现呢?答:创建派生于工厂抽象类,即由具体工厂去创建具体产品,既然要创建产品,自然需要产品抽象类和具体产品类了。

  其具体的UML结构图如下所示:

  在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应关系。

3.3 抽象工厂模式

   抽象工厂模式指的是提供一个创建一系列相关或相互依赖对象的接口,使得客户端可以在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象,强调的是”系列对象“的变化。其实现要点有:

  • 提供一系列对象的接口。问:如何去实现呢?答:提供多个产品的抽象接口
  • 创建多个产品族中的多个产品对象。问:如何做到呢?答:每个具体工厂创建一个产品族中的多个产品对象,多个具体工厂就可以创建多个产品族中的多个对象了。

  具体的UML结构图如下所示:

  

3.4 建造者模式

   建造者模式指的是将一个产品的内部表示与产品的构造过程分割开来,从而可以使一个建造过程生成具体不同的内部表示的产品对象。强调的是产品的构造过程。其实现要点有:

  • 将产品的内部表示与产品的构造过程分割开来。问:如何把它们分割开呢?答:不要把产品的构造过程放在产品类中,而是由建造者类来负责构造过程,产品的内部表示放在产品类中,这样不就分割开了嘛。

  具体的UML结构图如下所示:

  

3.5 原型工厂模式

   原型模式指的是通过给出一个原型对象来指明所要创建的对象类型,然后用复制的方法来创建出更多的同类型对象。其实现要点有:

  • 给出一个原型对象。问:如何办到呢?答:很简单嘛,直接给出一个原型类就好了。
  • 通过复制的方法来创建同类型对象。问:又是如何实现呢?答:.NET可以直接调用MemberwiseClone方法来实现浅拷贝

  具体的UML结构图如下所示:

   结构型模式,顾名思义讨论的是类和对象的结构 ,主要用来处理类或对象的组合。它包括两种类型,一是类结构型模式,指的是采用继承机制来组合接口或实现;二是对象结构型模式,指的是通过组合对象的方式来实现新的功能。它包括适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式和代理模式。

  • 适配器模式注重转换接口,将不吻合的接口适配对接 
  • 桥接模式注重分离接口与其实现,支持多维度变化 
  • 组合模式注重统一接口,将“一对多”的关系转化为“一对一”的关系 
  • 装饰者模式注重稳定接口,在此前提下为对象扩展功能 
  • 外观模式注重简化接口,简化组件系统与外部客户程序的依赖关系 
  • 享元模式注重保留接口,在内部使用共享技术对对象存储进行优化 
  • 代理模式注重假借接口,增加间接层来实现灵活控制

4.1 适配器模式

   适配器模式意在转换接口,它能够使原本不能再一起工作的两个类一起工作,所以经常用来在类库的复用、代码迁移等方面。例如DataAdapter类就应用了适配器模式。适配器模式包括类适配器模式和对象适配器模式,具体结构如下图所示,左边是类适配器模式,右边是对象适配器模式。

4.2 桥接模式

   桥接模式旨在将抽象化与实现化解耦,使得两者可以独立地变化。意思就是说,桥接模式把原来基类的实现化细节再进一步进行抽象,构造到一个实现化的结构中,然后再把原来的基类改造成一个抽象化的等级结构,这样就可以实现系统在多个维度的独立变化,桥接模式的结构图如下所示。

4.3 装饰者模式

   装饰者模式又称包装(Wrapper)模式,它可以动态地给一个对象添加一些额外的功能,装饰者模式较继承生成子类的方式更加灵活。虽然装饰者模式能够动态地将职责附加到对象上,但它也会造成产生一些细小的对象,增加了系统的复杂度。具体的结构图如下所示。

4.4 组合模式

   组合模式又称为部分—整体模式。组合模式将对象组合成树形结构,用来表示整体与部分的关系。组合模式使得客户端将单个对象和组合对象同等对待。如在.NET中WinForm中的控件,TextBox、Label等简单控件继承与Control类,同时GroupBox这样的组合控件也是继承于Control类。组合模式的具体结构图如下所示。

4.5 外观模式

  在系统中,客户端经常需要与多个子系统进行交互,这样导致客户端会随着子系统的变化而变化,此时可以使用外观模式把客户端与各个子系统解耦。外观模式指的是为子系统中的一组接口提供一个一致的门面,它提供了一个高层接口,这个接口使子系统更加容易使用。如电信的客户专员,你可以让客户专员来完成冲话费,修改套餐等业务,而不需要自己去与各个子系统进行交互。具体类结构图如下所示:

4.6 享元模式

   在系统中,如何我们需要重复使用某个对象时,此时如果重复地使用new操作符来创建这个对象的话,这对系统资源是一个极大的浪费,既然每次使用的都是同一个对象,为什么不能对其共享呢?这也是享元模式出现的原因。

  享元模式运用共享的技术有效地支持细粒度的对象,使其进行共享。在.NET类库中,String类的实现就使用了享元模式,String类采用字符串驻留池的来使字符串进行共享。更多内容参考博文:http://www.cnblogs.com/artech/archive/2010/11/25/internedstring.html。享元模式的具体结构图如下所示。

4.7 代理模式

   在系统开发中,有些对象由于网络或其他的障碍,以至于不能直接对其访问,此时可以通过一个代理对象来实现对目标对象的访问。如.NET中的调用Web服务等操作。

  代理模式指的是给某一个对象提供一个代理,并由代理对象控制对原对象的访问。具体的结构图如下所示。

  

  注:外观模式、适配器模式和代理模式区别?

  解答:这三个模式的相同之处是,它们都是作为客户端与真实被使用的类或系统之间的一个中间层,起到让客户端间接调用真实类的作用,不同之处在于,所应用的场合和意图不同。

  代理模式与外观模式主要区别在于,代理对象无法直接访问对象,只能由代理对象提供访问,而外观对象提供对各个子系统简化访问调用接口,而适配器模式则不需要虚构一个代理者,目的是复用原有的接口。外观模式是定义新的接口,而适配器则是复用一个原有的接口。

  另外,它们应用设计的不同阶段,外观模式用于设计的前期,因为系统需要前期就需要依赖于外观,而适配器应用于设计完成之后,当发现设计完成的类无法协同工作时,可以采用适配器模式。然而很多情况下在设计初期就要考虑适配器模式的使用,如涉及到大量第三方应用接口的情况;代理模式是模式完成后,想以服务的方式提供给其他客户端进行调用,此时其他客户端可以使用代理模式来对模块进行访问。

  总之,代理模式提供与真实类一致的接口,旨在用来代理类来访问真实的类,外观模式旨在简化接口,适配器模式旨在转换接口。

   行为型模式是对在不同对象之间划分责任和算法的抽象化。行为模式不仅仅关于类和对象,还关于它们之间的相互作用。行为型模式又分为类的行为模式和对象的行为模式两种。

  • 类的行为模式——使用继承关系在几个类之间分配行为。
  • 对象的行为模式——使用对象聚合的方式来分配行为。

  行为型模式包括11种模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、状态模式、策略模式、责任链模式、访问者模式、解释器模式和备忘录模式。

  • 模板方法模式:封装算法结构,定义算法骨架,支持算法子步骤变化。
  • 命令模式:注重将请求封装为对象,支持请求的变化,通过将一组行为抽象为对象,实现行为请求者和行为实现者之间的解耦。
  • 迭代器模式:注重封装特定领域变化,支持集合的变化,屏蔽集合对象内部复杂结构,提供客户程序对它的透明遍历。
  • 观察者模式:注重封装对象通知,支持通信对象的变化,实现对象状态改变,通知依赖它的对象并更新。
  • 中介者模式:注重封装对象间的交互,通过封装一系列对象之间的复杂交互,使他们不需要显式相互引用,实现解耦。
  • 状态模式:注重封装与状态相关的行为,支持状态的变化,通过封装对象状态,从而在其内部状态改变时改变它的行为。
  • 策略模式:注重封装算法,支持算法的变化,通过封装一系列算法,从而可以随时独立于客户替换算法。
  • 责任链模式:注重封装对象责任,支持责任的变化,通过动态构建职责链,实现事务处理。
  • 访问者模式:注重封装对象操作变化,支持在运行时为类结构添加新的操作,在类层次结构中,在不改变各类的前提下定义作用于这些类实例的新的操作。
  • 备忘录模式:注重封装对象状态变化,支持状态保存、恢复。
  • 解释器模式:注重封装特定领域变化,支持领域问题的频繁变化,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。

5.1 模板方法模式

   在现实生活中,有论文模板,简历模板等。在现实生活中,模板的概念是给定一定的格式,然后其他所有使用模板的人可以根据自己的需求去实现它。同样,模板方法也是这样的。

  模板方法模式是在一个抽象类中定义一个操作中的算法骨架,而将一些具体步骤实现延迟到子类中去实现。模板方法使得子类可以不改变算法结构的前提下,重新定义算法的特定步骤,从而达到复用代码的效果。具体的结构图如下所示。

以生活中做菜为例子实现的模板方法结构图

5.2 命令模式

   命令模式属于对象的行为模式,命令模式把一个请求或操作封装到一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。具体的结构图如下所示。

5.3 迭代器模式

   迭代器模式是针对集合对象而生的,对于集合对象而言,必然涉及到集合元素的添加删除操作,也肯定支持遍历集合元素的操作,此时如果把遍历操作也放在集合对象的话,集合对象就承担太多的责任了,此时可以进行责任分离,把集合的遍历放在另一个对象中,这个对象就是迭代器对象。

  迭代器模式提供了一种方法来顺序访问一个集合对象中各个元素,而又无需暴露该对象的内部表示,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部元素。具体的结构图如下所示。

5.4 观察者模式

   在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。具体结构图如下所示:

5.5 中介者模式

   在现实生活中,有很多中介者模式的身影,例如QQ游戏平台,聊天室、QQ群和短信平台,这些都是中介者模式在现实生活中的应用。

  中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。具体的结构图如下所示:

5.6 状态模式

   每个对象都有其对应的状态,而每个状态又对应一些相应的行为,如果某个对象有多个状态时,那么就会对应很多的行为。那么对这些状态的判断和根据状态完成的行为,就会导致多重条件语句,并且如果添加一种新的状态时,需要更改之前现有的代码。这样的设计显然违背了开闭原则,状态模式正是用来解决这样的问题的。

  状态模式——允许一个对象在其内部状态改变时自动改变其行为,对象看起来就像是改变了它的类。具体的结构图如下所示:

5.7 策略模式

   在现实生活中,中国的所得税,分为企业所得税、外商投资企业或外商企业所得税和个人所得税,针对于这3种所得税,每种所计算的方式不同,个人所得税有个人所得税的计算方式,而企业所得税有其对应计算方式。如果不采用策略模式来实现这样一个需求的话,我们会定义一个所得税类,该类有一个属性来标识所得税的类型,并且有一个计算税收的CalculateTax()方法,在该方法体内需要对税收类型进行判断,通过if-else语句来针对不同的税收类型来计算其所得税。这样的实现确实可以解决这个场景,但是这样的设计不利于扩展,如果系统后期需要增加一种所得税时,此时不得不回去修改CalculateTax方法来多添加一个判断语句,这样明白违背了“开放——封闭”原则。此时,我们可以考虑使用策略模式来解决这个问题,既然税收方法是这个场景中的变化部分,此时自然可以想到对税收方法进行抽象,这也是策略模式实现的精髓所在。

  策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象负责。策略模式通常把一系列的算法包装到一系列的策略类里面。用一句话慨括策略模式就是——“将每个算法封装到不同的策略类中,使得它们可以互换”。下面是策略模式的结构图:

  

5.8 责任链模式

   在现实生活中,有很多请求并不是一个人说了就算的,例如面试时的工资,低于1万的薪水可能技术经理就可以决定了,但是1万~1万5的薪水可能技术经理就没这个权利批准,可能需要请求技术总监的批准。

  责任链模式——某个请求需要多个对象进行处理,从而避免请求的发送者和接收之间的耦合关系。将这些对象连成一条链子,并沿着这条链子传递该请求,直到有对象处理它为止。具体结构图如下所示:

5.9 访问者模式

   访问者模式是封装一些施加于某种数据结构之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保存不变。访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操作之间的耦合度降低,使得操作集合可以相对自由地改变。具体结构图如下所示:

5.10 备忘录模式

   生活中的手机通讯录备忘录,操作系统备份点,数据库备份等都是备忘录模式的应用。备忘录模式是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。具体的结构图如下所示:

5.11 解释器模式

   解释器模式是一个比较少用的模式,所以我自己也没有对该模式进行深入研究,在生活中,英汉词典的作用就是实现英文和中文互译,这就是解释器模式的应用。

  解释器模式是给定一种语言,定义它文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释器语言中的句子。具体的结构图如下所示:

   23种设计模式,其实前辈们总结出来解决问题的方式,它们追求的宗旨还是保证系统的低耦合高内聚,指导它们的原则无非就是封装变化,责任单一,面向接口编程等设计原则。之后,我会继续分享自己WCF的学习过程,尽管博客园中有很多WCF系列,之前觉得没必要写,觉得会用就行了,但是不写,总感觉知识不是自己的,感觉没有深入,所以还是想写这样一个系列,希望各位博友后面多多支持。

  PS: 很多论坛都看到初学者问,WCF现在还有没有必要深入学之类的问题,因为他们觉得这些技术可能会过时,说不定到时候微软又推出了一个新的SOA的实现方案了,那岂不是白花时间深入学了,所以就觉得没必要深入去学,知道用就可以了。对于这个问题,我之前也有这样同样的感觉,但是现在我觉得,尽管WCF技术可能会被替换,但深入了解一门技术,重点不是知道一些更高深API的调用啊,而是了解它的实现机制和思维方式,即使后面这个技术被替代了,其背后机制也肯定是相似的。所以深入了解了一个技术,你就会感觉新的技术熟悉,对其感觉放松。并且,你深入了解完一门技术之后,你面试时也敢说你很好掌握了这门技术,而不至于说平时使用的很多,一旦深入问时却不知道背后实现原理。这也是我要写WCF系列的原因。希望这点意见对一些初学者有帮助。

C#调试入门篇 - 缘生梦 - 博客园

Excerpt

DotNet程序的调试,是DotNet程序员必备的技能之一,开发出稳定的程序、解决程序的疑难杂症都需要很强大的调试能力。DotNet调试有很多方法和技巧。现在本文就介绍一下借助DebugView工具进行调试的方法,以及由DebugView引申出来的知识点。 DebugView DebugView是一


DotNet程序的调试,是DotNet程序员必备的技能之一,开发出稳定的程序、解决程序的疑难杂症都需要很强大的调试能力。DotNet调试有很多方法和技巧。现在本文就介绍一下借助DebugView工具进行调试的方法,以及由DebugView引申出来的知识点。

DebugView

DebugView是一个查看调试信息的非常棒的工具,支持Debug、Release模式编译的程序,甚至支持内核程序,而且能够定制各种过滤条件,让你只看到关心的输出信息,而且可以定制高亮显示的内容等等,非常方便。

debugview

捕捉Release模式的Win32程序输出的调试信息,需要选中Capture Global Win32选项:

debugview_release

过滤与高亮功能

debugview_过滤与高亮

可以通过include、exclude设置过滤条件,包含指定字符串的输出信息将会被过滤。还可以通过exclude条件过滤掉对应进程ID的调试信息。多个条件使用“;”分隔,而且支持“*”通配符。

远程调试

DebugView支持远程捕捉调试信息。首先在远程机器上通过如下命令启动DebugView:

1
DebugView.exe /a /t /g /s

这样,DebugView就会以服务的方式运行,如下图:

debugview服务

然后在本地机器上启动DebugView,并通过Connect连接到远程机器的DebugView,当远程机器中有调试信息输出时,本地就会捕获到,并展示出来:

debugview_connect

输出信息到DebugView的几种方式

DebugView的一些功能是不是让你心动了呢。俗话说心动不如行动,但是在行动之前,首先要知道C#如何将调试信息输出到DebugView中。

通过编程输出一些调试信息到DebugView中,一共有三种方式:

  • Debug.WriteLine
  • Debugger.Log
  • Kernal32.dll中的OutputDebugString方法

一、Debug.WriteLine

通过Debug.WriteLine可以将调试信息写入到DebugView中,如下:

1
Debug.WriteLine(<span>"</span><span>这是调试信息</span><span>"</span>);

效果如下:

Debug_DebugView

不过此方式只能在Debug模式下有效。具体原因下面会详细介绍。

二、Debugger.Log

Debug.WriteLine已经很好用了,唯一的缺点就是在Release模式下无效。那么在Release模式下就可以使用Debugger.Log方法,示例如下:

1
Debugger.Log(<span>0</span>, <span>null</span>, <span>"</span><span>这是Debugger.Log输出的调试信息</span><span>"</span>);

三、Kernel32.dll中的OutputDebugString方法

做C++开发的应该知道可以通过OutputDebugString这个API开实现输出调试信息到DebugView中吧。那么C++能做的,C#也能做。可以通过PInvoke的方式引入此方法,这个API属于Kernel32.dll,如下声明:

1
2
[DllImport(<span>"</span><span>kernel32.dll</span><span>"</span>, CharSet=<span>CharSet.Auto)]
</span><span>public</span> <span>static</span> <span>extern</span> <span>void</span> OutputDebugString(<span>string</span> message);

然后就可以通过调用此方法,将调试信息输出到DebugView中。

DebugView与日志框架比较

可能有人会说,DebugView能做的事情,我用log4Net,NLog等日志框架也能做,为什么要用DebugView呢?

问的好,那么我就根据平时使用DebugView的经验来给你一个用DebugView的理由:

  • DebugView使用非常方便。相比于日志框架庞大的体系,DebugView的使用可谓是十分的简单方便。DebugView只有几百K的大小,占用空间几乎可以忽略不计。从官网下载后,直接运行exe,几乎不需要任何配置就可以正常使用。而相比于DebugView,日志框架可以算的上庞然大物。而当你从官网获得log4Net后,需要进行各种繁杂的配置。甚至你要花上几天时间专门学习一下这套框架。由此可以看出DebugView的使用实在是方便的不能再方便。
  • DebugView是可视化工具,支持各种过滤和高亮。DebugView可以通过过滤条件来过滤不关心的信息,只显示相关的调试信息。而日志框架输出的是文件等文本信息,这些信息会包含程序运行过程中的所有信息,虽然可以通过配置文件来指定只输出哪一类信息,但是不如DebugView来的方便简单。
  • DebugView可以实时监视。DebugView中有“自动滚动”的功能,程序中输出的调试信息,基本上瞬间就会在DebugView中展示出来,当由于大量信息导致DebugView中的文本框满了后,DebugView可以通过自动滚动滚动条,让你随时都可以看到最新的一条信息,达到类似监视的效果。而日志框架由于其写文本的特性,很难达到这种效果,即使能达到,相信也是需要对日志框架相当清楚了解,才能完成这个效果。

这些理由应该足以让你使用DebugView了吧。使用DebugView的理由肯定还不止这些,如果你有更好的理由,还请分享出来。

当然,DebugView与日志框架,每个都有每个的用途。通过DebugView的方式,只适合短暂的调试,而正式发布的网站或者软件,需要一套记录程序长期以来的运行状态的工具,那么就非日志框架莫属了。所以DebugView与日志框架,要在合适的地方,发挥他们最大的功效。

声明

Log4Net等日志框架,功能足够强大,也足够丰富,相信上面说到的DebugView的功能,也可以通过日志框架来实现。但是和DebugView比较起来,会相对复杂一些。所以上面说到的使用DebugView的理由是基于方便性的比较,DebugView有足够的方便性来让你选择使用他。

ConditionalAttribute详解与条件编译

说到调试,那么肯定有开发人员遇到这种情况,开发产品的时候,遇到一些问题,就在代码中加入了大量的调试输出信息,比如通过Console.WriteLine、MessageBox.Show或者通过Ilog.Log记录日志,甚至临时改变逻辑来验证逻辑的正确性等。经过这些调试信息的帮助,终于解决了产品的问题。但此时又遇到了新的问题,产品最终发布的时候,肯定是不能有这些调试信息的,可是已经加了这么多调试信息,难道要全部删除掉吗。这显然不是一个好办法,这么多代码,手一抖,很容易就删除了不相关的代码,造成不可预估的后果。

做过C/C++开发的,可以从各种跨平台的开源库中看到,一堆一堆的#if….#else….#endif,这就是条件编译,这也是C/C++跨平台所依赖的最基本的东西,在Linux系统,编译这段代码,在Windows系统又编译那段代码,最终实现了代码级别的跨平台。

那么C#中有没有类似的功能呢,答案当然是有,而且有两种:

  • 通过给方法加上ConditionalAttribute特性
  • 使用#if..#else..#endif,来控制代码的编译

ConditionalAttribute特性

下面是ConditionalAttribute的构造函数:

1
2
3
<span>public</span><span> ConditionalAttribute(
</span><span>string</span><span> conditionString
)</span>

构造函数中的参数conditionString,是一个区分大小写的条件编译符号的字符串。

上面提到Debug.WriteLine时,说到这个功能只在Debug模式下才有用,Release下就不起作用了。

我们从MSDN中看一下Debug.WriteLine的说明:

1
2
3
4
[ConditionalAttribute(<span>"</span><span>DEBUG</span><span>"</span><span>)]
</span><span>public</span> <span>static</span> <span>void</span><span> WriteLine(
</span><span>string</span><span> message
)</span>

由此也就明白了Debug.WriteLine只能在Debug模式下使用,而在Release模式下无效的原因了。

条件编译#if..#else..#endif

C/C++中有#if..#else..#endif,C#中也有这些,他们都被称为预处理器。通过预定义的条件编译符号,来控制编译时编译哪部分代码。如下:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<span>public</span> <span>bool</span> HasPermission(<span>string</span><span> userName)
{
</span><span>#if</span> DEBUG
<span>//</span><span>Debug模式下,不需要做权限判断,直接返回true</span>
<span>return</span> <span>true</span><span>;
</span><span>#else</span>
<span>//</span><span>Release模式下,只有sa用户才有权限</span>
<span>if</span> (!<span>string</span>.IsNullOrEmpty(userName) &amp;&amp; userName == <span>"</span><span>sa</span><span>"</span><span>)
{
</span><span>return</span> <span>true</span><span>;
}
</span><span>else</span><span>
{
</span><span>return</span> <span>false</span><span>;
}
</span><span>#endif</span><span>
}</span>

复制代码

预定义的Debug宏在什么地方

说到条件编译,是不是只有DEBUG 和 RELEASE两种情况呢,如果是这种情况的话,那也就是说DEBUG和RELEASE两种情况是定义好了的,俗话说就是“做死了”,这是作死的节奏啊。不作死就不会死,至少VS在这点上还没有作死。

让我们来一步步揭开DEBUG的面纱。

既然是条件编译,那么就应该和编译选项有关。我们知道C#项目,有一个属性页,可以设置很多编译的选项,如下:

VS项目生成属性页

从图中看到,条件编译是用的DEBUG常量,或者称为DEBUG条件编译符号,是在这个编译生成选项中定义的,如果去掉这个定义,那么编译后的HasPermission方法就会根据用户名进行权限检查,程序中通过Debug.WriteLine输出的调试信息也会输出到DebugView中,也就相当于Release模式下的效果。

其实DEBUG常量与Debug、Release模式并无太大的关系,唯一的关系就是,VS生成的项目中,Debug模式下,默认会选中“定义DEBUG常量”,而Release模式下,默认不会选中。也就是说,Debug模式,默认会定义DEBUG常量,而Release不会,如下图:

Debug_Release默认配置

既然DEBUG常量与Debug模式无本质上的关联,那么为什么说到Debug,就认为DEBUG呢。道理其实很简单,世上本无路,走的人多了,便成了路。本来这个DEBUG常量只是Debug模式下的默认预定义的常量,只是因为大家习惯了,并且对它的这种预定义还比较认可,时间久了,就自然而然认为DEBUG就代表Debug模式。

虽然我们可以通过去掉DEBUG常量,来使条件编译在Debug模式下达到Release模式的效果,但是建议最好不要这样做,因为这就像是大家普遍都认可的一个约定,如果你一反常态,不遵守这个约定,对于程序,编译没有问题,但是后续维护肯定会相当麻烦,所以还请大侠手下留情。

使用自定义的编译常量

DEBUG常量作为一种普遍的约定,最好不要打破。如果有除DEBUG外的条件编译需要,可以使用自定义的编译常量。

自定义编译常量有两种方法:

  • 通过编译生成属性页中的条件编译输入框,定义自己的编译常量。
  • 在代码中使用#define预处理,来定义编译常量。

我们可以在条件编译的输入框中,定义自己的编译常量,多个常量之间用分号“;”隔开,而且支持中文常量,如下:

自定义条件编译符号

当然,我们也可以在代码中通过#define预处理来定义,比如:

1
2
<span>#define</span> 缘生梦
<span>#define</span> hbccdf

但是有一点需要注意,define定义常量必须在using 命名空间之前,否则会造成编译错误。

通过VS和Resharper改变颜色来查看哪些代码真正会被编译

引入条件编译后,我们可以通过VS很快知道哪些代码会被编译:

VS_debug_release颜色对比

虽然条件编译的代码可以很直观的看出来,但是Conditional修饰的方法就看不出来了,这时就要借助神器Resharper了,如下图:

resharper_debug_release颜色对比

从图中看出,release模式,由于没有定义DEBUG、缘生梦两个常量,所以,调用Test、Debug.WriteLine方法的地方就会变暗,这样很直观就知道这些代码不会被编译。再次说明一个道理,神器对于开发有很大的帮助作用。

通过反编译查看生成后的代码

上面总是说,有些代码会被编译,有的则不会,那么真正编译后的效果是怎样的,我们不妨使用另外一个比较强大的工具,反编译工具Reflector,来查看一下反编译后的代码:

debug_release_反编译代码对比

从图中的反编译后的代码可以看出,满足条件的代码会真正编译到生成的程序集里,而不满足的代码则不会生成到程序集里。

Conditional修饰的方法,会有方法的实现,但是没有方法的调用。不适合在方法里做一些变量的修改。

总结Conditional和条件编译

  • Conditional为方法级别的条件编译,只能修饰方法;#if..#else..#endif为行级别的条件编译,可以指定任意代码。
  • Conditional只能修饰无返回值的方法,而且不适合在方法中处理一些变量的值。#if..#else..#endif没有任何限制。
  • 可以通过条件编译符号输入框,或者代码中的#define定义条件编译常量,这些常量对于Conditional、#if..#else..#endif均有效。

TRACE常量

从上面多幅图中,可以看到,在Debug和Release模式下都会定义一个TRACE常量。

现在我们知道DEBUG常量是用来控制调用Debug.WriteLine的语句是否被编译。那么TRACE常量呢。

相信很多人也用过System.Diagnostics.Trace类,由DEBUG常量与Debug有关,可以想到TRACE常量与Trace有关,我们来看一下MSDN对于Trace的定义。

从MSDN中,看到Trace类的定义以及可以调用的方法与Debug类似,都有WriteLine方法。下面是Trace的WriteLine方法定义:

1
2
3
4
[ConditionalAttribute(<span>"</span><span>TRACE</span><span>"</span><span>)]
</span><span>public</span> <span>static</span> <span>void</span><span> WriteLine(
</span><span>string</span><span> message
)</span>

由此可以知道,TRACE常量是用来控制Trace类中WriteLine等方法是否被编译的作用。

Debug类与Trace类的区别

到现在为止,我们渐渐的了解了Debug类与Trace类,他们都可以通过WriteLine方法输出调试或跟踪信息,从他们的定义和暴露的方法中,可以看出他们非常相似,那么他们有什么区别呢。

有些人会根据使用的效果总结为:Debug类只能在Debug模式下执行,在Release模式下无效果,而Trace类在Debug和Release模式下都可以执行。

确实,在VS中新建一个项目,分别调用Debug.WriteLine和Trace.WriteLine方法,只有Trace.WriteLine输出的信息,在Release模式下有效。这也看似验证了上面的区别。

但这是他们两个真正的区别吗。我们一点一点来分析。

首先看这两个方法的定义:

MSDN_Debug_Trace对比

由图看到,每个方法都通过ConditionalAttribute关联了一个条件编译常量,Debug关联DEBUG常量,Trace关联TRACE常量。再来看一下这两个常量的定义:

常量定义_Debug_Release对比

从图中看到,TRACE在Debug和Release模式下都会定义,这样在Debug和Release模式下都会执行,而DEBUG只在Debug模式下才会定义,Debug.WriteLine只在Debug模式下执行。从而也验证了上面的结论。

但DEBUG与TRACE只是在默认的情况下的定义。当改变这些定义后,上面的结论就不再正确。

我们来做这样的实验:Debug模式下只定义TRACE常量,Release模式只定义DEBUG常量

常量测试_Debug_Release对比

然后在Debug和Release模式下分别执行Debug.WriteLine方法和Trace.WriteLine方法,Debug.WriteLine方法只在Release模式下有效,而Trace.WriteLine方法只在Debug模式下有效。上面的结论不再成立。

为了更好的验证一下我们的结论,对他们进行反编译:

反编译_Debug_Trace.WriteLine对比

由图中的反编译代码看到,除了关联的条件编译常量不同外,内部调用的方法均为TraceInternal.WriteLine,实现完全一样。

那么下面来总结一下Debug与Trace的区别:

  • Debug类关联DEBUG常量,Trace类关联TRACE常量。
  • 默认情况下,VS创建的项目Debug模式会定义DEBUG与TRACE,而Release模式只定义TRACE,使Debug只在Debug模式有效,而Trace在Debug和Release模式均有效。
  • 可以通过修改DEBUG、TRACE的默认定义,来改变Debug、Trace的默认行为。但是建议最好不要这样做,因为改变了这种默认的约定可能会出现意想不到的问题。

Debug、Debugger、Kernel32的联系

每个人大脑的空间都是有限的,零散的知识很容易忘掉,输出调试信息到DebugView中的三种方法,由于关联性不是很强,很容易会忘掉其中的一两种,那么既然实现相同的功能,他们之间有什么关联吗。

这就要从Debug的MSDN文档说起。

我们知道Debug编译的程序,运行的时候可以通过Debug.WriteLine方法输出调试i信息到DebugView,而MSDN中的解释

1
将后跟行结束符的消息写入 Listeners 集合中的跟踪侦听器。

并没有说是输出到DebugView中,而是写入到Listeners集合中的跟踪侦听器。为了弄明白原理,有必要深入的研究一下。这时就要依赖反编译工具Reflector了。

Debug类整体观赏

从整体看上去,Debug类的每一个方法都通过Conditional与DEBUG常量管理,也就是默认情况下,Debug类的所有方法在Debug模式下均不会编译。

我们再来具体看一下Debug.WriteLine方法:

1
2
3
4
5
[Conditional(<span>"</span><span>DEBUG</span><span>"</span><span>), __DynamicallyInvokable]
</span><span>public</span> <span>static</span> <span>void</span> WriteLine(<span>string</span><span> message)
{
TraceInternal.WriteLine(message);
}</span>

再来看一下TraceInternal方法:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
<span>public</span> <span>static</span> <span>void</span> WriteLine(<span>string</span><span> message)
{
</span><span>foreach</span> (TraceListener listener <span>in</span><span> Listeners)
{
listener.WriteLine(message);
</span><span>if</span><span> (AutoFlush)
{
listener.Flush();
}
}
</span><span>return</span><span>;
}</span>

复制代码

上面的代码是精简后的代码。从代码中看到会调用集合中的每一个listener的WriteLine方法。

那么这些listener的WriteLine又做了什么呢:

1
2
3
4
<span>public</span> <span>abstract</span> <span>class</span><span> TraceListener : MarshalByRefObject, IDisposable
{
</span><span>public</span> <span>abstract</span> <span>void</span> WriteLine(<span>string</span><span> message);
}</span>

原来TraceListener的WriteLine是抽象类的抽象方法,那么我们得到的listener是具体类的一个抽象,相当于接口,这是微软的一贯做法,再继续下去,就需要知道是哪些具体的TraceListener了。

继续上面的线索,我们是从Listeners属性中获取到的TraceListener,那么就去看Listeners的get实现:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<span>public</span> <span>static</span><span> TraceListenerCollection Listeners
{
</span><span>get</span><span>
{
InitializeSettings();
</span><span>if</span> (listeners == <span>null</span><span>)
{
</span><span>lock</span><span> (critSec)
{
</span><span>if</span> (listeners == <span>null</span><span>)
{
SystemDiagnosticsSection systemDiagnosticsSection </span>=<span> DiagnosticsConfiguration.SystemDiagnosticsSection;
</span><span>if</span> (systemDiagnosticsSection != <span>null</span><span>)
{
</span><span>//</span><span>从配置文件获取listener,但是由于此处没有设置配置文件,所以先不研究这个地方</span>
listeners =<span> systemDiagnosticsSection.Trace.Listeners.GetRuntimeObject();
}
</span><span>else</span><span>
{
</span><span>//</span><span>这里new了一个TraceListener的集合</span>
listeners = <span>new</span><span> TraceListenerCollection();
</span><span>//</span><span>这里我们看到了TraceListener的具体实现类DefaultTraceListener</span>
TraceListener listener = <span>new</span><span> DefaultTraceListener {
IndentLevel </span>=<span> indentLevel,
IndentSize </span>=<span> indentSize
};
listeners.Add(listener);
}
}
}
}
</span><span>return</span><span> listeners;
}
}</span>

复制代码

从代码中,找到了具体的实现类DefaultTraceListener,那么就快点看看他的WriteLine方法吧,有点迫不及待了。

实际上,WriteLine方法会调用内部的Write方法:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<span>private</span> <span>void</span> Write(<span>string</span> message, <span>bool</span><span> useLogFile)
{
</span><span>if</span> (<span>base</span><span>.NeedIndent)
{
</span><span>//</span><span>写缩进,实际是空格</span>
<span>this</span><span>.WriteIndent();
}
</span><span>if</span> ((message == <span>null</span>) || (message.Length &lt;= <span>16384</span><span>))
{
</span><span>//</span><span>输出消息</span>
<span>this</span><span>.internalWrite(message);
}
</span><span>else</span><span>
{
</span><span>//</span><span>当消息很长时,会通过internalWrite方法将消息多次输出</span>
<span>int</span> startIndex = <span>0</span><span>;
</span><span>while</span> (startIndex &lt; (message.Length - <span>16384</span><span>))
{
</span><span>this</span>.internalWrite(message.Substring(startIndex, <span>16384</span><span>));
startIndex </span>+= <span>16384</span><span>;
}
</span><span>this</span><span>.internalWrite(message.Substring(startIndex));
}
</span><span>if</span> (useLogFile &amp;&amp; (<span>this</span>.LogFileName.Length != <span>0</span><span>))
{
</span><span>//</span><span>输出到日志文件中</span>
<span>this</span>.WriteToLogFile(message, <span>false</span><span>);
}
}</span>

复制代码

与输出信息有关的有两个地方,一个是调用internalWrite,另外一个是WriteToLogFile。从WriteToLogFile是有执行条件的。感兴趣的可以研究一下。重点来看一下internalWrite方法。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<span>private</span> <span>void</span> internalWrite(<span>string</span><span> message)
{
</span><span>if</span><span> (Debugger.IsLogging())
{
</span><span>//</span><span>调用Debugger.Log方法,这个方法可以输出信息到DebugView中</span>
Debugger.Log(<span>0</span>, <span>null</span><span>, message);
}
</span><span>else</span> <span>if</span> (message == <span>null</span><span>)
{
SafeNativeMethods.OutputDebugString(</span><span>string</span><span>.Empty);
}
</span><span>else</span><span>
{
</span><span>//</span><span>调用Native方法</span>
<span> SafeNativeMethods.OutputDebugString(message);
}
}</span>

复制代码

internalWrite中调用了两个比较重要的方法。其中Debugger.Log方法是不是很熟悉呢。我们刚刚在上面总结了三种输出调试信息到DebugView的方法,其中就包含了Debugger.Log,而Debug.WriteLine方法中,又会调用到Debugger.Log方法,这样,这两个方法就建立起了联系。

再来看SafeNativeMethods.OutputDebugString,看到这个类的命名,以及对Win32API的了解,就能想到,这肯定是通过PInvoke等方法对Win32API的封装,看其定义:

1
2
[DllImport(<span>"</span><span>kernel32.dll</span><span>"</span>, CharSet=<span>CharSet.Auto)]
</span><span>public</span> <span>static</span> <span>extern</span> <span>void</span> OutputDebugString(<span>string</span> message);

果然,这就是对kernel32.dll中的OutputDebugString的封装调用,而这又是上面三种方法其中的一种。好了,这样Debug、Debugger、OutputDebugString就全都联系到了一起,他们之间有了联系,是不是就更容易记忆了。

配置TraceListener

从上面对Debug.WriteLine的一步步跟踪分析的过程中,我们看到了对于TraceListener的获取,一种方式是DefaultTraceListener,另外一种就是如下:

复制代码

1
2
3
4
5
6
SystemDiagnosticsSection systemDiagnosticsSection =<span> DiagnosticsConfiguration.SystemDiagnosticsSection;
</span><span>if</span> (systemDiagnosticsSection != <span>null</span><span>)
{
</span><span>//</span><span>从配置文件获取listener,但是由于此处没有设置配置文件,所以先不研究这个地方</span>
listeners =<span> systemDiagnosticsSection.Trace.Listeners.GetRuntimeObject();
}</span>

复制代码

这几行代码就是从配置文件的system.diagnostics中获取TraceListener。

关于配置文件具体的读取和解析过程,本文就不再详细介绍了,感兴趣的朋友可以自行研究,或者等到我的后面博文详细介绍。

那么现在主要说一下如何通过配置文件来设置TraceListener,如下:

复制代码

1
2
3
4
5
6
7
8
9
<span>&lt;</span><span>configuration</span><span>&gt;</span>
<span>&lt;</span><span>system.diagnostics</span><span>&gt;</span>
<span>&lt;</span><span>trace </span><span>autoflush</span><span>="true"</span><span> indentsize</span><span>="2"</span><span>&gt;</span>
<span>&lt;</span><span>listeners</span><span>&gt;</span>
<span>&lt;</span><span>add </span><span>name</span><span>="myListener"</span><span> type</span><span>="System.Diagnostics.ConsoleTraceListener"</span> <span>/&gt;</span>
<span>&lt;/</span><span>listeners</span><span>&gt;</span>
<span>&lt;/</span><span>trace</span><span>&gt;</span>
<span>&lt;/</span><span>system.diagnostics</span><span>&gt;</span>
<span>&lt;/</span><span>configuration</span><span>&gt;</span>

复制代码

同样,有配置文件,那么就可以通过代码来实现同样的功能,如下:

1
2
3
Debug.Listeners.Add(<span>new</span><span> ConsoleTraceListener());
Debug.AutoFlush </span>= <span>true</span><span>;
Debug.WriteLine(</span><span>"</span><span>这是Debug.WriteLine输出的调试信息</span><span>"</span>);

Debug编译与Release编译的区别

Debug与Release的区别,这个问题,相信很多人都会有疑问,也会有很多人有自己的答案,我听到过的答案有这些:

  • Release比Debug运行速度快,执行效率高。
  • Debug 是调试用的,可以打断点执行。Release是发布用的,不能打断点执行。
  • Debug编译的文件大,Release生成的程序集小。
  • Debug会生成pdb文件,Release不生成pdb文件。

这些答案看上去好像都对,但是为什么Debug与Release有这么大的区别呢,这就需要深入的思考一下。

在Debug类与Trace类的区别一节中,相信有些朋友已经明白,Debug编译与Release编译的区别其实是编译配置的不同,DEBUG、TRACE常量的定义就是其中不同的地方。那么Debug编译与Release编译还有什么不同呢。针对这个问题,我总结了一下,主要区别有下面几点:

  • 常量定义不同
  • 优化代码的不同
  • 包含的调试信息不同
  • 输出路径不同

接下来,我们详细的看一下。

常量定义不同

这个相信大家已经非常清楚了,Debug模式下会定义DEBUG、TRACE常量,而Release模式下只定义TRACE常量,如下图所示

常量定义_Debug_Release对比

优化代码不同

debug_release_优化代码选项对比

Debug模式下,默认不会进行代码的优化,而Release模式下,由于默认选中了“优化代码”选项,所以编译器会对生成的代码进行优化,以提高运行速度。

包含的调试信息不同

编译生成的属性页中有一个“高级”按钮,点击后会弹出一个对话框,然后可以对编译生成进行一些设置,如下

生成高级选项

Debug与Release的高级选项对比:

debug_release_生成高级选项对比

从图中看到,Debug模式下默认会生成全部的调试信息,而Release模式下只生成pdb文件。

输出路径不同

这一点详细大家都很清楚了,Debug模式下会输出到Debug目录,Release模式下会输出到Release目录,如图:

debug_release_输出目录对比

Debug编译与Release编译的区别总结

说了这么多,我们再来思考本节开始时说到的关于Debug编译与Release编译的区别的答案,可以得出这样的结论:

  • Release比Debug运行速度快,执行效率高。由于Release模式编译会对生成的代码进行优化,所以会使生成的程序集在运行的时候比Debug编译的程序集运行速度快。
  • Debug 是调试用的,可以打断点执行。Release是发布用的,不能打断点执行。由于Release模式对代码进行优化,生成的调试信息只包含pdb文件,所以导致调试起来会比较困难。
  • Debug编译的文件大,Release生成的程序集小。依然是由于Release模式对生成的代码进行了优化,所以就导致生成的程序集相对来说比较小。
  • Debug会生成pdb文件,Release不生成pdb文件。这个要看VS的默认选项,在2012中,Release模式默认会生成pdb文件的,其他版本的VS可能会有不同的情况。

知道这些区别后,我们就可以通过修改编译配置来使Debug程序达到Release的效果,使Release程序达到Debug的效果。当然,还是那句话,虽然可以实现这样的效果,但是建议绝对不要这样做,不然程序维护起来,肯定会遇到很多问题。

总结的重要性

有人说,互联网这么发达,遇到什么问题直接谷歌百度,内事不决问百度,外事不决问谷歌。但是即使你通过网络解决了问题,如果不消化吸收,经过总结变成自己的东西,那么下次还会遇到同样的问题。书到用时方恨少,一回首,已白了少年头。如果不总结,任他虐我千百遍,我却视他如初见,从网络上找到的方法、代码、解决方案,那是别人的知识,只有经过实践去验证,通过总结,消化吸收,才能将这些知识“据为己有”,为我所用。

那么至少从现在做起,把每天学到的知识,遇到的问题,得到的经验,总结下来,相信自己,这是大牛的节奏。

  之前一直在忙于工作上的事情,关于设计模式系列一直没更新,最近项目中发现,对于设计模式的了解是必不可少的,当然对于设计模式的应用那更是重要,可以说是否懂得应用设计模式在项目中是衡量一个程序员的技术水平,因为对于一个功能的实现,高级工程师和初级工程师一样都会实现,但是区别在于它们实现功能的可扩展和可维护性,也就是代码的是否“优美”、可读。但是,要更好地应用,首先就必须了解各种设计模式和其应用场景,所以我还是希望继续完成设计模式这个系列,希望通过这种总结的方式来加深自己设计模式的理解。

2.1 命令模式的定义

命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。

2.2 命令模式的结构

  既然,命令模式是实现把发出命令的责任和执行命令的责任分割开,然而中间必须有某个对象来帮助发出命令者来传达命令,使得执行命令的接收者可以收到命令并执行命令。例如,开学了,院领导说计算机学院要进行军训,计算机学院的学生要跑1000米,院领导的话也就相当于一个命令,他不可能直接传达给到学生,他必须让教官来发出命令,并监督学生执行该命令。在这个场景中,发出命令的责任是属于学院领导,院领导充当与命令发出者的角色,执行命令的责任是属于学生,学生充当于命令接收者的角色,而教官就充当于命令的发出者或命令请求者的角色,然而命令模式的精髓就在于把每个命令抽象为对象。从而命令模式的结构如下图所示:

从命令模式的结构图可以看出,它涉及到五个角色,它们分别是:

  • 客户角色:发出一个具体的命令并确定其接受者。
  • 命令角色:声明了一个给所有具体命令类实现的抽象接口
  • 具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。
  • 请求者角色:负责调用命令对象执行命令。
  • 接受者角色:负责具体行为的执行。

2.3 命令模式的实现

  现在,让我们以上面的军训的例子来实现一个命令模式,在实现之前,可以参考下命令模式的结构图来分析下实现过程。

  军训场景中,具体的命令即是学生跑1000米,这里学生是命令的接收者,教官是命令的请求者,院领导是命令的发出者,即客户端角色。要实现命令模式,则必须需要一个抽象命令角色来声明约定,这里以抽象类来来表示。命令的传达流程是:

  命令的发出者必须知道具体的命令、接受者和传达命令的请求者,对应于程序也就是在客户端角色中需要实例化三个角色的实例对象了。

  命令的请求者负责调用命令对象的方法来保证命令的执行,对应于程序也就是请求者对象需要有命令对象的成员,并在请求者对象的方法内执行命令。

  具体命令就是跑1000米,这自然属于学生的责任,所以是具体命令角色的成员方法,而抽象命令类定义这个命令的抽象接口。

  有了上面的分析之后,具体命令模式的实现代码如下所示:

复制代码

1
2 // 教官,负责调用命令对象执行请求
3 public class Invoke 4 {
5 public Command _command; 6
7 public Invoke(Command command) 8 {
9 this._command = command; 10 } 11
12 public void ExecuteCommand() 13 { 14 _command.Action(); 15 } 16 } 17
18 // 命令抽象类
19 public abstract class Command 20 { 21 // 命令应该知道接收者是谁,所以有Receiver这个成员变量
22 protected Receiver _receiver; 23
24 public Command(Receiver receiver) 25 { 26 this._receiver = receiver; 27 } 28
29 // 命令执行方法
30 public abstract void Action(); 31 } 32
33 //
34 public class ConcreteCommand :Command 35 { 36 public ConcreteCommand(Receiver receiver) 37 : base(receiver) 38 { 39 } 40
41 public override void Action() 42 { 43 // 调用接收的方法,因为执行命令的是学生
44 _receiver.Run1000Meters(); 45 } 46 } 47
48 // 命令接收者——学生
49 public class Receiver 50 { 51 public void Run1000Meters() 52 { 53 Console.WriteLine(“跑1000米”); 54 } 55 } 56
57 // 院领导
58 class Program 59 { 60 static void Main(string[] args) 61 { 62 // 初始化Receiver、Invoke和Command
63 Receiver r = new Receiver(); 64 Command c = new ConcreteCommand(r); 65 Invoke i = new Invoke(c); 66
67 // 院领导发出命令
68 i.ExecuteCommand(); 69 } 70 }

复制代码

 在ASP.NET的MVC模式中,有一种叫Front Controller的模式,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,这里面其实就是用到了Command模式。

 

Front Controller 的处理程序部分结构图

Front Controller的命令部分结构图

Handler 类负责处理各个 Web 请求,并将确定正确的 Command 对象这一职责委派给 CommandFactory 类。当 CommandFactory 返回 Command 对象后,Handler 将调用 Command 上的 Execute 方法来执行请求。具体的实现如下

1 // Handler类
2 public class Handler : IHttpHandler 3
4 {
5 public void ProcessRequest(HttpContext context) 6
7 {
8
9 Command command = CommandFactory.Make(context.Request.Params); 10
11 command.Execute(context);
12
13 }
14
15 public bool IsReusable 16
17 {
18 get
19
20 {
21 return true;
22 }
23 }
24 }
25
26 Command接口:
27 ///


28 /// Command 29 ///

30 public interface Command 31
32 {
33 void Execute(HttpContext context); 34 }
35
36 CommandFactory类:
37 ///
38 /// CommandFactory 39 ///

40 public class CommandFactory 41
42 {
43 public static Command Make(NameValueCollection parms) 44
45 {
46
47 string requestParm = parms[“requestParm”];
48
49 Command command = null;
50
51 //根据输入参数得到不同的Command对象
52
53 switch (requestParm) 54
55 {
56 case “1”:
57
58 command = new FirstPortal(); 59
60 break;
61
62 case “2”:
63
64 command = new SecondPortal(); 65
66 break;
67
68 default:
69
70 command = new FirstPortal(); 71
72 break;
73 }
74
75 return command; 76
77 }
78 }
79
80 RedirectCommand类:
81 public abstract class RedirectCommand : Command 82
83 {
84 //获得Web.Config中定义的key和url键值对,UrlMap类详见下载包中的代码
85
86 private UrlMap map = UrlMap.SoleInstance; 87
88 protected abstract void OnExecute(HttpContext context); 89
90 public void Execute(HttpContext context) 91
92 {
93 OnExecute(context);
94
95 //根据key和url键值对提交到具体处理的页面
96
97 string url = String.Format(“{0}?{1}”, map.Map[context.Request.Url.AbsolutePath], context.Request.Url.Query);
98
99 context.Server.Transfer(url); 100
101 } 102 } 103
104 FirstPortal类: 105 public class FirstPortal : RedirectCommand 106
107 { 108 protected override void OnExecute(HttpContext context) 109
110 { 111 //在输入参数中加入项portalId以便页面处理
112
113 context.Items[“portalId”] = “1”; 114
115 } 116 } 117
118 SecondPortal类: 119 public class SecondPortal : RedirectCommand 120
121 { 122 protected override void OnExecute(HttpContext context) 123
124 { 125 context.Items[“portalId”] = “2”; 126 } 127 }

View Code

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

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

   命令模式使得命令发出的一个和接收的一方实现低耦合,从而有以下的优点:

  • 命令模式使得新的命令很容易被加入到系统里。
  • 可以设计一个命令队列来实现对请求的Undo和Redo操作。
  • 可以较容易地将命令写入日志。
  • 可以把命令对象聚合在一起,合成为合成命令。合成命令式合成模式的应用。

  命令模式的缺点:

  • 使用命令模式可能会导致系统有过多的具体命令类。这会使得命令模式在这样的系统里变得不实际。

   命令模式的实现要点在于把某个具体的命令抽象化为具体的命令类,并通过加入命令请求者角色来实现将命令发送者对命令执行者的依赖分割开,在上面军训的例子中,如果不使用命令模式的话,则命令的发送者将对命令接收者是强耦合的关系,实现代码如下:

复制代码

1 // 院领导
2 class Program 3 {
4 static void Main(string[] args)
5 {
6 // 行为的请求者和行为的实现者之间呈现一种紧耦合关系
7 Receiver r = new Receiver(); 8
9 r.Run1000Meters(); 10 } 11 } 12
13 public class Receiver 14 { 15 // 操作
16 public void Run1000Meters() 17 { 18 Console.WriteLine(“跑1000米”); 19 } 20 }

复制代码

  到这里,本章的内容就介绍结束了,在下一章将继续为大家分享下我对迭代器模式的理解。

  在上篇博文中分享了我对命令模式的理解,命令模式主要是把行为进行抽象成命令,使得请求者的行为和接受者的行为形成低耦合。在一章中,将介绍一下迭代器模式。下面废话不多说了,直接进入本博文的主题。

  迭代器是针对集合对象而生的,对于集合对象而言,必然涉及到集合元素的添加删除操作,同时也肯定支持遍历集合元素的操作,我们此时可以把遍历操作也放在集合对象中,但这样的话,集合对象就承担太多的责任了,面向对象设计原则中有一条是单一职责原则,所以我们要尽可能地分离这些职责,用不同的类去承担不同的职责。迭代器模式就是用迭代器类来承担遍历集合元素的职责。

2.1 迭代器模式的定义

  迭代器模式提供了一种方法顺序访问一个聚合对象(理解为集合对象)中各个元素,而又无需暴露该对象的内部表示,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

2.2 迭代器模式的结构

  既然,迭代器模式承担了遍历集合对象的职责,则该模式自然存在2个类,一个是聚合类,一个是迭代器类。在面向对象涉及原则中还有一条是针对接口编程,所以,在迭代器模式中,抽象了2个接口,一个是聚合接口,另一个是迭代器接口,这样迭代器模式中就四个角色了,具体的类图如下所示:

  从上图可以看出,迭代器模式由以下角色组成:

  • 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口
  • 具体迭代器角色(Concrete Iteraror):具体迭代器角色实现了迭代器接口,并需要记录遍历中的当前位置。
  • 聚合角色(Aggregate):聚合角色负责定义获得迭代器角色的接口
  • 具体聚合角色(Concrete Aggregate):具体聚合角色实现聚合角色接口。

2.3 迭代器模式的实现

  介绍完迭代器模式之后,下面就具体看看迭代器模式的实现,具体实现代码如下:

复制代码

1 // 抽象聚合类
2 public interface IListCollection 3 {
4 Iterator GetIterator();
5 }
6
7 // 迭代器抽象类
8 public interface Iterator 9 {
10 bool MoveNext(); 11 Object GetCurrent();
12 void Next(); 13 void Reset(); 14 }
15
16 // 具体聚合类
17 public class ConcreteList : IListCollection 18 {
19 int[] collection;
20 public ConcreteList() 21 {
22 collection = new int[] { 2, 4, 6, 8 }; 23 }
24
25 public Iterator GetIterator() 26 {
27 return new ConcreteIterator(this);
28 }
29
30 public int Length 31 {
32 get { return collection.Length; } 33 }
34
35 public int GetElement(int index) 36 {
37 return collection[index]; 38 }
39 }
40
41 // 具体迭代器类
42 public class ConcreteIterator : Iterator 43 {
44 // 迭代器要集合对象进行遍历操作,自然就需要引用集合对象
45 private ConcreteList _list; 46 private int _index; 47
48 public ConcreteIterator(ConcreteList list) 49 {
50 _list = list; 51 _index = 0;
52 }
53
54
55 public bool MoveNext() 56 {
57 if (_index < _list.Length) 58 {
59 return true;
60 }
61 return false;
62 }
63
64 public Object GetCurrent() 65 {
66 return _list.GetElement(_index); 67 }
68
69 public void Reset() 70 {
71 _index = 0;
72 }
73
74 public void Next() 75 {
76 if (_index < _list.Length) 77 {
78 _index++;
79 }
80
81 }
82 }
83
84 // 客户端
85 class Program 86 {
87 static void Main(string[] args)
88 {
89 Iterator iterator;
90 IListCollection list = new ConcreteList(); 91 iterator = list.GetIterator(); 92
93 while (iterator.MoveNext()) 94 {
95 int i = (int)iterator.GetCurrent();
96 Console.WriteLine(i.ToString());
97 iterator.Next();
98 }
99
100 Console.Read(); 101 } 102 }

复制代码

自然,上面代码的运行结果也是对集合每个元素的输出,具体运行结果如下图所示:

  在.NET下,迭代器模式中的聚集接口和迭代器接口都已经存在了,其中IEnumerator接口扮演的就是迭代器角色,IEnumberable接口则扮演的就是抽象聚集的角色,只有一个GetEnumerator()方法,关于这两个接口的定义可以自行参考MSDN。在.NET 1.0中,.NET 类库中很多集合都已经实现了迭代器模式,大家可以用反编译工具Reflector来查看下mscorlib程序集下的System.Collections命名空间下的类,这里给出ArrayList的定义代码,具体实现代码可以自行用反编译工具查看,具体代码如下所示:

复制代码

1 public class ArrayList : IList, ICollection, IEnumerable, ICloneable 2 {
3 // Fields
4 private const int _defaultCapacity = 4;
5 private object[] _items;
6 private int _size; 7 [NonSerialized]
8 private object _syncRoot; 9 private int _version; 10 private static readonly object[] emptyArray; 11
12 public virtual IEnumerator GetEnumerator(); 13 public virtual IEnumerator GetEnumerator(int index, int count); 14
15 // Properties
16 public virtual int Capacity { get; set; } 17 public virtual int Count { get; } 18 …………..// 更多代码请自行用反编译工具Reflector查看
19 }

复制代码

  通过查看源码你可以发现,ArrayList中迭代器的实现与我们前面给出的示例代码非常相似。然而,在.NET 2.0中,由于有了yield return关键字,实现迭代器模式就更简单了,关于迭代器的更多内容可以参考我的这篇博文

  在下面的情况下可以考虑使用迭代器模式:

  • 系统需要访问一个聚合对象的内容而无需暴露它的内部表示。
  • 系统需要支持对聚合对象的多种遍历。
  • 系统需要为不同的聚合结构提供一个统一的接口。

  由于迭代器承担了遍历集合的职责,从而有以下的优点:

  • 迭代器模式使得访问一个聚合对象的内容而无需暴露它的内部表示,即迭代抽象。
  • 迭代器模式为遍历不同的集合结构提供了一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作

  迭代器模式存在的缺陷:

  • 迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常。所以使用foreach语句只能在对集合进行遍历,不能在遍历的同时更改集合中的元素。

  到这里,本博文的内容就介绍结束了,迭代器模式就是抽象一个迭代器类来分离了集合对象的遍历行为,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。在一篇博文中将为大家介绍观察者模式。

   在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题。

2.1 观察者模式的定义

  从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。

2.2 观察者模式的结构

  从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象,然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。有了上面的分析,下面观察者的结构图也就很容易理解了。具体结构图如下所示:

                                  图 观察者模式结构图

  可以看出,在观察者模式的结构图有以下角色:

  • 抽象主题角色(Subject):抽象主题把所有观察者对象的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
  • 抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
  • 具体主题角色(ConcreteSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
  • 具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。

2.3 观察者模式的实现

  下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。有了这个分析,自然实现的代码如下所示:

复制代码

1 // 腾讯游戏订阅号类
2 public class TenxunGame 3 {
4 // 订阅者对象
5 public Subscriber Subscriber {get;set;}
6
7 public String Symbol {get; set;}
8
9 public string Info {get ;set;} 10
11 public void Update() 12 { 13 if (Subscriber != null) 14 { 15 // 调用订阅者对象来通知订阅者
16 Subscriber.ReceiveAndPrintData(this); 17 } 18 } 19
20 } 21
22 // 订阅者类
23 public class Subscriber 24 { 25 public string Name { get; set; } 26 public Subscriber(string name) 27 { 28 this.Name = name; 29 } 30
31 public void ReceiveAndPrintData(TenxunGame txGame) 32 { 33 Console.WriteLine(“Notified {0} of {1}’s” + “ Info is: {2}”, Name, txGame.Symbol, txGame.Info); 34 } 35 } 36
37 // 客户端测试
38 class Program 39 { 40 static void Main(string[] args) 41 { 42 // 实例化订阅者和订阅号对象
43 Subscriber LearningHardSub = new Subscriber(“LearningHard”); 44 TenxunGame txGame = new TenxunGame(); 45
46 txGame.Subscriber = LearningHardSub; 47 txGame.Symbol = “TenXun Game”; 48 txGame.Info = “Have a new game published ….”; 49
50 txGame.Update(); 51
52 Console.ReadLine(); 53 } 54 }

复制代码

  上面代码确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:

  • TenxunGame类和Subscriber类之间形成了一种双向依赖关系,即TenxunGame调用了Subscriber的ReceiveAndPrintData方法,而Subscriber调用了TenxunGame类的属性。这样的实现,如果有其中一个类变化将引起另一个类的改变。
  • 当出现一个新的订阅者时,此时不得不修改TenxunGame代码,即添加另一个订阅者的引用和在Update方法中调用另一个订阅者的方法。

  上面的设计违背了“开放——封闭”原则,显然,这不是我们想要的。对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类,这样也就可以完美解决上面代码存在的问题了,具体的实现代码为:

复制代码

1 // 订阅号抽象类
2 public abstract class TenXun 3 {
4 // 保存订阅者列表
5 private List observers = new List();
6
7 public string Symbol { get; set; }
8 public string Info { get; set; }
9 public TenXun(string symbol, string info) 10 { 11 this.Symbol = symbol; 12 this.Info = info; 13 } 14
15 #region 新增对订阅号列表的维护操作
16 public void AddObserver(IObserver ob) 17 { 18 observers.Add(ob); 19 } 20 public void RemoveObserver(IObserver ob) 21 { 22 observers.Remove(ob); 23 } 24 #endregion
25
26 public void Update() 27 { 28 // 遍历订阅者列表进行通知
29 foreach (IObserver ob in observers) 30 { 31 if (ob != null) 32 { 33 ob.ReceiveAndPrint(this); 34 } 35 } 36 } 37 } 38
39 // 具体订阅号类
40 public class TenXunGame : TenXun 41 { 42 public TenXunGame(string symbol, string info) 43 : base(symbol, info) 44 { 45 } 46 } 47
48 // 订阅者接口
49 public interface IObserver 50 { 51 void ReceiveAndPrint(TenXun tenxun); 52 } 53
54 // 具体的订阅者类
55 public class Subscriber : IObserver 56 { 57 public string Name { get; set; } 58 public Subscriber(string name) 59 { 60 this.Name = name; 61 } 62
63 public void ReceiveAndPrint(TenXun tenxun) 64 { 65 Console.WriteLine(“Notified {0} of {1}’s” + “ Info is: {2}”, Name, tenxun.Symbol, tenxun.Info); 66 } 67 } 68
69 // 客户端测试
70 class Program 71 { 72 static void Main(string[] args) 73 { 74 TenXun tenXun = new TenXunGame(“TenXun Game”, “Have a new game published ….”); 75
76 // 添加订阅者
77 tenXun.AddObserver(new Subscriber(“Learning Hard”)); 78 tenXun.AddObserver(new Subscriber(“Tom”)); 79
80 tenXun.Update(); 81
82 Console.ReadLine(); 83 } 84 }

复制代码

  上面代码是我们进行重构后的实现,重构后的代码实现类图如下所示:

  从上图可以发现,这样的实现就是观察者模式的实现。这样,在任何时候,只要调用了TenXun类的Update方法,它就会通知所有的观察者对象,同时,可以看到,观察者模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。这里并不是直接给出观察者模式的实现,而是通过一步步重构的方式来引出观察者模式的实现,相信通过这个方式,大家可以更深刻地理解观察者模式所解决的问题和带来的好处。

   在.NET中,我们可以使用委托与事件来简化观察者模式的实现,上面的例子用事件和委托的实现如下代码所示:

复制代码

1 namespace ObserverInNET 2 {
3 class Program 4 {
5 // 委托充当订阅者接口类
6 public delegate void NotifyEventHandler(object sender); 7
8 // 抽象订阅号类
9 public class TenXun 10 { 11 public NotifyEventHandler NotifyEvent; 12
13 public string Symbol { get; set; } 14 public string Info { get; set; } 15 public TenXun(string symbol, string info) 16 { 17 this.Symbol = symbol; 18 this.Info = info; 19 } 20
21 #region 新增对订阅号列表的维护操作
22 public void AddObserver(NotifyEventHandler ob) 23 { 24 NotifyEvent += ob; 25 } 26 public void RemoveObserver(NotifyEventHandler ob) 27 { 28 NotifyEvent -= ob; 29 } 30
31 #endregion
32
33 public void Update() 34 { 35 if (NotifyEvent != null) 36 { 37 NotifyEvent(this); 38 } 39 } 40 } 41
42 // 具体订阅号类
43 public class TenXunGame : TenXun 44 { 45 public TenXunGame(string symbol, string info) 46 : base(symbol, info) 47 { 48 } 49 } 50
51 // 具体订阅者类
52 public class Subscriber 53 { 54 public string Name { get; set; } 55 public Subscriber(string name) 56 { 57 this.Name = name; 58 } 59
60 public void ReceiveAndPrint(Object obj) 61 { 62 TenXun tenxun = obj as TenXun; 63
64 if (tenxun != null) 65 { 66 Console.WriteLine(“Notified {0} of {1}’s” + “ Info is: {2}”, Name, tenxun.Symbol, tenxun.Info); 67 } 68 } 69 } 70
71 static void Main(string[] args) 72 { 73 TenXun tenXun = new TenXunGame(“TenXun Game”, “Have a new game published ….”); 74 Subscriber lh = new Subscriber(“Learning Hard”); 75 Subscriber tom = new Subscriber(“Tom”); 76
77 // 添加订阅者
78 tenXun.AddObserver(new NotifyEventHandler(lh.ReceiveAndPrint)); 79 tenXun.AddObserver(new NotifyEventHandler(tom.ReceiveAndPrint)); 80
81 tenXun.Update(); 82
83 Console.WriteLine(“-———————————-“); 84 Console.WriteLine(“移除Tom订阅者”); 85 tenXun.RemoveObserver(new NotifyEventHandler(tom.ReceiveAndPrint)); 86 tenXun.Update(); 87
88 Console.ReadLine(); 89 } 90 } 91 }

复制代码

  从上面代码可以看出,使用事件和委托实现的观察者模式中,减少了订阅者接口类的定义,此时,.NET中的委托正式充到订阅者接口类的角色。使用委托和事件,确实简化了观察者模式的实现,减少了一个IObserver接口的定义,上面代码的运行结果如下图所示:

   在下面的情况下可以考虑使用观察者模式:

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。从方面的这个词中可以想到,观察者模式肯定在AOP(面向方面编程)中有所体现,更多内容参考:Observern Pattern in AOP.
  • 当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。
  • 当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。

  观察者模式有以下几个优点:

  • 观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
  • 观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
  • 观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。

  观察者也存在以下一些缺点:

  • 如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
  • 虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
  • 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。

  到这里,观察者模式的分享就介绍了。观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。

  在现实生活中,有很多中介者模式的身影,例如QQ游戏平台,聊天室、QQ群和短信平台,这些都是中介者模式在现实生活中的应用,下面就具体分享下我对中介者模式的理解。

2.1 中介者模式的定义

  从生活中的例子可以看出,不论是QQ游戏还是QQ群,它们都是充当一个中间平台,QQ用户可以登录这个中间平台与其他QQ用户进行交流,如果没有这些中间平台,我们如果想与朋友进行聊天的话,可能就需要当面才可以了。电话、短信也同样是一个中间平台,有了这个中间平台,每个用户都不要直接依赖与其他用户,只需要依赖这个中间平台就可以了,一切操作都由中间平台去分发。了解完中介模式在生活中的模型后,下面给出中介模式的正式定义。

  中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。

2.2 中介者模式的结构

  从生活中例子自然知道,中介者模式设计两个具体对象,一个是用户类,另一个是中介者类,根据针对接口编程原则,则需要把这两类角色进行抽象,所以中介者模式中就有了4类角色,它们分别是:抽象中介者角色,具体中介者角色、抽象同事类和具体同事类。中介者类是起到协调各个对象的作用,则抽象中介者角色中则需要保存各个对象的引用。有了上面的分析,则就不难理解中介者模式的结构图了,具体结构图如下所示:

为什么要使用中介者模式

  在现实生活中,中介者的存在是不可缺少的,如果没有了中介者,我们就不能与远方的朋友进行交流了。而在软件设计领域,为什么要使用中介者模式呢?如果不使用中介者模式的话,各个同事对象将会相互进行引用,如果每个对象都与多个对象进行交互时,将会形成如下图所示的网状结构。

  从上图可以发现,如果不使用中介者模式的话,每个对象之间过度耦合,这样的既不利于类的复用也不利于扩展。如果引入了中介者模式,那么对象之间的关系将变成星型结构,采用中介者模式之后会形成如下图所示的结构:

  从上图可以发现,使用中介者模式之后,任何一个类的变化,只会影响中介者和类本身,不像之前的设计,任何一个类的变化都会引起其关联所有类的变化。这样的设计大大减少了系统的耦合度。

2.3 中介者模式的实现

  介绍完中介者模式的定义和存在的必要性后,下面就以现实生活中打牌的例子来实现下中介者模式。在现实生活中,两个人打牌,如果某个人赢了都会影响到对方状态的改变。如果此时不采用中介者模式实现的话,则上面的场景的实现如下所示:

复制代码

1 // 抽象牌友类
2 public abstract class AbstractCardPartner 3 {
4 public int MoneyCount { get; set; }
5
6 public AbstractCardPartner() 7 {
8 MoneyCount = 0;
9 } 10
11 public abstract void ChangeCount(int Count, AbstractCardPartner other); 12 } 13
14 // 牌友A类
15 public class ParterA : AbstractCardPartner 16 { 17 public override void ChangeCount(int Count, AbstractCardPartner other) 18 { 19 this.MoneyCount += Count; 20 other.MoneyCount -= Count; 21 } 22 } 23
24 // 牌友B类
25 public class ParterB : AbstractCardPartner 26 { 27 public override void ChangeCount(int Count, AbstractCardPartner other) 28 { 29 this.MoneyCount += Count; 30 other.MoneyCount -= Count; 31 } 32 } 33
34 class Program 35 { 36 // A,B两个人打牌
37 static void Main(string[] args) 38 { 39 AbstractCardPartner A = new ParterA(); 40 A.MoneyCount = 20; 41 AbstractCardPartner B = new ParterB(); 42 B.MoneyCount = 20; 43
44 // A 赢了则B的钱就减少
45 A.ChangeCount(5, B); 46 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是25
47 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是15 48
49 // B赢了A的钱也减少
50 B.ChangeCount(10, A); 51 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount); // 应该是15
52 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是25
53 Console.Read(); 54 } 55 }

复制代码

  上面确实完美解决了上面场景中的问题,并且使用了抽象类使具体牌友A和牌友B都依赖于抽象类,从而降低了同事类之间的耦合度。但是这样的设计,如果其中牌友A发生变化时,此时就会影响到牌友B的状态,如果涉及的对象变多的话,这时候某一个牌友的变化将会影响到其他所有相关联的牌友状态。例如牌友A算错了钱,这时候牌友A和牌友B的钱数都不正确了,如果是多个人打牌的话,影响的对象就会更多。这时候就会思考——能不能把算钱的任务交给程序或者算数好的人去计算呢,这时候就有了我们QQ游戏中的欢乐斗地主等牌类游戏了。所以上面的设计,我们还是有进一步完善的方案的,即加入一个中介者对象来协调各个对象之间的关联,这也就是中介者模式的应用了,具体完善后的实现代码如下所示:

复制代码

1 namespace MediatorPattern 2 {
3 // 抽象牌友类
4 public abstract class AbstractCardPartner 5 {
6 public int MoneyCount { get; set; }
7
8 public AbstractCardPartner() 9 { 10 MoneyCount = 0; 11 } 12
13 public abstract void ChangeCount(int Count, AbstractMediator mediator); 14 } 15
16 // 牌友A类
17 public class ParterA : AbstractCardPartner 18 { 19 // 依赖与抽象中介者对象
20 public override void ChangeCount(int Count, AbstractMediator mediator) 21 { 22 mediator.AWin(Count); 23 } 24 } 25
26 // 牌友B类
27 public class ParterB : AbstractCardPartner 28 { 29 // 依赖与抽象中介者对象
30 public override void ChangeCount(int Count, AbstractMediator mediator) 31 { 32 mediator.BWin(Count); 33 } 34 } 35
36 // 抽象中介者类
37 public abstract class AbstractMediator 38 { 39 protected AbstractCardPartner A; 40 protected AbstractCardPartner B; 41 public AbstractMediator(AbstractCardPartner a, AbstractCardPartner b) 42 { 43 A = a; 44 B = b; 45 } 46
47 public abstract void AWin(int count); 48 public abstract void BWin(int count); 49 } 50
51 // 具体中介者类
52 public class MediatorPater : AbstractMediator 53 { 54 public MediatorPater(AbstractCardPartner a, AbstractCardPartner b) 55 : base(a, b) 56 { 57 } 58
59 public override void AWin(int count) 60 { 61 A.MoneyCount += count; 62 B.MoneyCount -= count; 63 } 64
65 public override void BWin(int count) 66 { 67 B.MoneyCount += count; 68 A.MoneyCount -= count; 69 } 70 } 71
72 class Program 73 { 74 static void Main(string[] args) 75 { 76 AbstractCardPartner A = new ParterA(); 77 AbstractCardPartner B = new ParterB(); 78 // 初始钱
79 A.MoneyCount = 20; 80 B.MoneyCount = 20; 81
82 AbstractMediator mediator = new MediatorPater(A, B); 83
84 // A赢了
85 A.ChangeCount(5, mediator); 86 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是25
87 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是15 88
89 // B 赢了
90 B.ChangeCount(10, mediator); 91 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是15
92 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是25
93 Console.Read(); 94 } 95 } 96 }

复制代码

   从上面实现代码可以看出,此时牌友A和牌友B都依赖于抽象的中介者类,这样如果其中某个牌友类变化只会影响到,只会影响到该变化牌友类本身和中介者类,从而解决前面实现代码出现的问题,具体的运行结果和前面实现结果一样,运行结果如下图所示:

  在上面的实现代码中,抽象中介者类保存了两个抽象牌友类,如果新添加一个牌友类似时,此时就不得不去更改这个抽象中介者类。可以结合观察者模式来解决这个问题,即抽象中介者对象保存抽象牌友的类别,然后添加Register和UnRegister方法来对该列表进行管理,然后在具体中介者类中修改AWin和BWin方法,遍历列表,改变自己和其他牌友的钱数。这样的设计还是存在一个问题——即增加一个新牌友时,此时虽然解决了抽象中介者类不需要修改的问题,但此时还是不得不去修改具体中介者类,即添加CWin方法,我们可以采用状态模式来解决这个问题,关于状态模式的介绍将会在下一专题进行分享。

   一般在以下情况下可以考虑使用中介者模式:

  • 一组定义良好的对象,现在要进行复杂的相互通信。
  • 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

  中介者模式具有以下几点优点:

  • 简化了对象之间的关系,将系统的各个对象之间的相互关系进行封装,将各个同事类解耦,使得系统变为松耦合。
  • 提供系统的灵活性,使得各个同事对象独立而易于复用。

  然而,中介者模式也存在对应的缺点:

  • 中介者模式中,中介者角色承担了较多的责任,所以一旦这个中介者对象出现了问题,整个系统将会受到重大的影响。例如,QQ游戏中计算欢乐豆的程序出错了,这样会造成重大的影响。
  • 新增加一个同事类时,不得不去修改抽象中介者类和具体中介者类,此时可以使用观察者模式和状态模式来解决这个问题。

  中介者模式,定义了一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显式地相互引用,从而使其耦合性降低,而且可以独立地改变它们之间的交互。中介者模式一般应用于一组定义良好的对象之间需要进行通信的场合以及想定制一个分布在多个类中的行为,而又不想生成太多的子类的情形下。

  在上一篇文章介绍到可以使用状态者模式和观察者模式来解决中介者模式存在的问题,在本文中将首先通过一个银行账户的例子来解释状态者模式,通过这个例子使大家可以对状态者模式有一个清楚的认识,接着,再使用状态者模式来解决上一篇文章中提出的问题。

  每个对象都有其对应的状态,而每个状态又对应一些相应的行为,如果某个对象有多个状态时,那么就会对应很多的行为。那么对这些状态的判断和根据状态完成的行为,就会导致多重条件语句,并且如果添加一种新的状态时,需要更改之前现有的代码。这样的设计显然违背了开闭原则。状态模式正是用来解决这样的问题的。状态模式将每种状态对应的行为抽象出来成为单独新的对象,这样状态的变化不再依赖于对象内部的行为。

2.1 状态者模式的定义

  上面对状态模式做了一个简单的介绍,这里给出状态模式的定义。

  状态模式——允许一个对象在其内部状态改变时自动改变其行为,对象看起来就像是改变了它的类。

2.2 状态者模式的结构

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

  

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

  • Account类:维护一个State类的一个实例,该实例标识着当前对象的状态。
  • State类:抽象状态类,定义了一个具体状态类需要实现的行为约定。
  • SilveStater、GoldState和RedState类:具体状态类,实现抽象状态类的每个行为。

2.3 状态者模式的实现

  下面,就以银行账户的状态来实现下状态者模式。银行账户根据余额可分为RedState、SilverState和GoldState。这些状态分别代表透支账号,新开账户和标准账户。账号余额在【-100.0,0.0】范围表示处于RedState状态,账号余额在【0.0 , 1000.0】范围表示处于SilverState,账号在【1000.0, 100000.0】范围表示处于GoldState状态。下面以这样的一个场景实现下状态者模式,具体实现代码如下所示:

复制代码

1 namespace StatePatternSample 2 {
3 public class Account 4 {
5 public State State {get;set;}
6 public string Owner { get; set; }
7 public Account(string owner) 8 {
9 this.Owner = owner; 10 this.State = new SilverState(0.0, this);
11 }
12
13 public double Balance { get {return State.Balance; }} // 余额 14 // 存钱
15 public void Deposit(double amount) 16 {
17 State.Deposit(amount);
18 Console.WriteLine(“存款金额为 {0:C}——“, amount);
19 Console.WriteLine(“账户余额为 =:{0:C}”, this.Balance);
20 Console.WriteLine(“账户状态为: {0}”, this.State.GetType().Name);
21 Console.WriteLine();
22 }
23
24 // 取钱
25 public void Withdraw(double amount) 26 {
27 State.Withdraw(amount);
28 Console.WriteLine(“取款金额为 {0:C}——“,amount);
29 Console.WriteLine(“账户余额为 =:{0:C}”, this.Balance);
30 Console.WriteLine(“账户状态为: {0}”, this.State.GetType().Name);
31 Console.WriteLine();
32 }
33
34 // 获得利息
35 public void PayInterest() 36 {
37 State.PayInterest();
38 Console.WriteLine(“Interest Paid — “);
39 Console.WriteLine(“账户余额为 =:{0:C}”, this.Balance);
40 Console.WriteLine(“账户状态为: {0}”, this.State.GetType().Name);
41 Console.WriteLine();
42 }
43 }
44
45 // 抽象状态类
46 public abstract class State 47 {
48 // Properties
49 public Account Account { get; set; }
50 public double Balance { get; set; } // 余额
51 public double Interest { get; set; } // 利率
52 public double LowerLimit { get; set; } // 下限
53 public double UpperLimit { get; set; } // 上限
54
55 public abstract void Deposit(double amount); // 存款
56 public abstract void Withdraw(double amount); // 取钱
57 public abstract void PayInterest(); // 获得的利息
58 }
59
60 // Red State意味着Account透支了
61 public class RedState : State 62 {
63 public RedState(State state) 64 {
65 // Initialize
66 this.Balance = state.Balance; 67 this.Account = state.Account; 68 Interest = 0.00;
69 LowerLimit = -100.00;
70 UpperLimit = 0.00;
71 }
72
73 // 存款
74 public override void Deposit(double amount) 75 {
76 Balance += amount; 77 StateChangeCheck();
78 }
79 // 取钱
80 public override void Withdraw(double amount) 81 {
82 Console.WriteLine(“没有钱可以取了!”);
83 }
84
85 public override void PayInterest() 86 {
87 // 没有利息
88 }
89
90 private void StateChangeCheck() 91 {
92 if (Balance > UpperLimit) 93 {
94 Account.State = new SilverState(this);
95 }
96 }
97 }
98
99 // Silver State意味着没有利息得
100 public class SilverState :State 101 { 102 public SilverState(State state) 103 : this(state.Balance, state.Account) 104 { 105 } 106
107 public SilverState(double balance, Account account) 108 { 109 this.Balance = balance; 110 this.Account = account; 111 Interest = 0.00; 112 LowerLimit = 0.00; 113 UpperLimit = 1000.00; 114 } 115
116 public override void Deposit(double amount) 117 { 118 Balance += amount; 119 StateChangeCheck(); 120 } 121 public override void Withdraw(double amount) 122 { 123 Balance -= amount; 124 StateChangeCheck(); 125 } 126
127 public override void PayInterest() 128 { 129 Balance += Interest * Balance; 130 StateChangeCheck(); 131 } 132
133 private void StateChangeCheck() 134 { 135 if (Balance < LowerLimit) 136 { 137 Account.State = new RedState(this); 138 } 139 else if (Balance > UpperLimit) 140 { 141 Account.State = new GoldState(this); 142 } 143 } 144 } 145
146 // Gold State意味着有利息状态
147 public class GoldState : State 148 { 149 public GoldState(State state) 150 { 151 this.Balance = state.Balance; 152 this.Account = state.Account; 153 Interest = 0.05; 154 LowerLimit = 1000.00; 155 UpperLimit = 1000000.00; 156 } 157 // 存钱
158 public override void Deposit(double amount) 159 { 160 Balance += amount; 161 StateChangeCheck(); 162 } 163 // 取钱
164 public override void Withdraw(double amount) 165 { 166 Balance -= amount; 167 StateChangeCheck(); 168 } 169 public override void PayInterest() 170 { 171 Balance += Interest * Balance; 172 StateChangeCheck(); 173 } 174
175 private void StateChangeCheck() 176 { 177 if (Balance < 0.0) 178 { 179 Account.State = new RedState(this); 180 } 181 else if (Balance < LowerLimit) 182 { 183 Account.State = new SilverState(this); 184 } 185 } 186 } 187
188 class App 189 { 190 static void Main(string[] args) 191 { 192 // 开一个新的账户
193 Account account = new Account(“Learning Hard”); 194
195 // 进行交易 196 // 存钱
197 account.Deposit(1000.0); 198 account.Deposit(200.0); 199 account.Deposit(600.0); 200
201 // 付利息
202 account.PayInterest(); 203
204 // 取钱
205 account.Withdraw(2000.00); 206 account.Withdraw(500.00); 207
208 // 等待用户输入
209 Console.ReadKey(); 210 } 211 } 212 }

复制代码

  上面代码的运行结果如下图所示:

  从上图可以发现,进行存取款交易,会影响到Account内部的状态,由于状态的改变,从而影响到Account类行为的改变,而且这些操作都是发生在运行时的。

  在上一篇博文中,我曾介绍到中介者模式存在的问题,详细的问题描述可以参考上一篇博文。下面利用观察者模式和状态者模式来完善中介者模式,具体的实现代码如下所示:

1 // 抽象牌友类
2 public abstract class AbstractCardPartner 3 {
4 public int MoneyCount { get; set; }
5
6 public AbstractCardPartner() 7 {
8 MoneyCount = 0;
9 }
10
11 public abstract void ChangeCount(int Count, AbstractMediator mediator); 12 }
13
14 // 牌友A类
15 public class ParterA : AbstractCardPartner 16 {
17 // 依赖与抽象中介者对象
18 public override void ChangeCount(int Count, AbstractMediator mediator) 19 {
20 mediator.ChangeCount(Count);
21 }
22 }
23
24 // 牌友B类
25 public class ParterB : AbstractCardPartner 26 {
27 // 依赖与抽象中介者对象
28 public override void ChangeCount(int Count, AbstractMediator mediator) 29 {
30 mediator.ChangeCount(Count);
31 }
32 }
33
34 // 抽象状态类
35 public abstract class State 36 {
37 protected AbstractMediator meditor; 38 public abstract void ChangeCount(int count); 39 }
40
41 // A赢状态类
42 public class AWinState : State 43 {
44 public AWinState(AbstractMediator concretemediator) 45 {
46 this.meditor = concretemediator; 47 }
48
49 public override void ChangeCount(int count) 50 {
51 foreach (AbstractCardPartner p in meditor.list) 52 {
53 ParterA a = p as ParterA; 54 //
55 if (a != null)
56 {
57 a.MoneyCount += count; 58 }
59 else
60 {
61 p.MoneyCount -= count; 62 }
63 }
64 }
65 }
66
67 // B赢状态类
68 public class BWinState : State 69 {
70 public BWinState(AbstractMediator concretemediator) 71 {
72 this.meditor = concretemediator; 73 }
74
75 public override void ChangeCount(int count) 76 {
77 foreach (AbstractCardPartner p in meditor.list) 78 {
79 ParterB b = p as ParterB; 80 // 如果集合对象中时B对象,则对B的钱添加
81 if (b != null)
82 {
83 b.MoneyCount += count; 84 }
85 else
86 {
87 p.MoneyCount -= count; 88 }
89 }
90 }
91 }
92
93 // 初始化状态类
94 public class InitState : State 95 {
96 public InitState() 97 {
98 Console.WriteLine(“游戏才刚刚开始,暂时还有玩家胜出”);
99 } 100
101 public override void ChangeCount(int count) 102 { 103 //
104 return; 105 } 106 } 107
108 // 抽象中介者类
109 public abstract class AbstractMediator 110 { 111 public List list = new List(); 112
113 public State State { get; set; } 114
115 public AbstractMediator(State state) 116 { 117 this.State = state; 118 } 119
120 public void Enter(AbstractCardPartner partner) 121 { 122 list.Add(partner); 123 } 124
125 public void Exit(AbstractCardPartner partner) 126 { 127 list.Remove(partner); 128 } 129
130 public void ChangeCount(int count) 131 { 132 State.ChangeCount(count); 133 } 134 } 135
136 // 具体中介者类
137 public class MediatorPater : AbstractMediator 138 { 139 public MediatorPater(State initState) 140 : base(initState) 141 { } 142 } 143
144 class Program 145 { 146 static void Main(string[] args) 147 { 148 AbstractCardPartner A = new ParterA(); 149 AbstractCardPartner B = new ParterB(); 150 // 初始钱
151 A.MoneyCount = 20; 152 B.MoneyCount = 20; 153
154 AbstractMediator mediator = new MediatorPater(new InitState()); 155
156 // A,B玩家进入平台进行游戏
157 mediator.Enter(A); 158 mediator.Enter(B); 159
160 // A赢了
161 mediator.State = new AWinState(mediator); 162 mediator.ChangeCount(5); 163 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是25
164 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是15 165
166 // B 赢了
167 mediator.State = new BWinState(mediator); 168 mediator.ChangeCount(10); 169 Console.WriteLine(“A 现在的钱是:{0}”, A.MoneyCount);// 应该是25
170 Console.WriteLine(“B 现在的钱是:{0}”, B.MoneyCount); // 应该是15
171 Console.Read(); 172 } 173 }

View Code

   在以下情况下可以考虑使用状态者模式。

  • 当一个对象状态转换的条件表达式过于复杂时可以使用状态者模式。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简单化。
  • 当一个对象行为取决于它的状态,并且它需要在运行时刻根据状态改变它的行为时,就可以考虑使用状态者模式。

   状态者模式的主要优点是:

  • 将状态判断逻辑每个状态类里面,可以简化判断的逻辑。
  • 当有新的状态出现时,可以通过添加新的状态类来进行扩展,扩展性好。

  状态者模式的主要缺点是:

  • 如果状态过多的话,会导致有非常多的状态类,加大了开销。

  状态者模式是对对象状态的抽象,从而把对象中对状态复杂的判断逻辑已到各个状态类里面,从而简化逻辑判断。在下一篇文章将分享我对策略模式的理解。