0%

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

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

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 策略模式的定义

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

  前面介绍了策略模式用来解决的问题,下面具体给出策略的定义。策略模式是针对一组算法,将每个算法封装到具有公共接口的独立的类中,从而使它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

2.2 策略模式的结构

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

  下面是策略模式的结构图:

  

  该模式涉及到三个角色:

  • 环境角色(Context):持有一个Strategy类的引用
  • 抽象策略角色(Strategy):这是一个抽象角色,通常由一个接口或抽象类来实现。此角色给出所有具体策略类所需实现的接口。
  • 具体策略角色(ConcreteStrategy):包装了相关算法或行为。

2.3 策略模式的实现

  下面就以所得税的例子来实现下策略模式,具体实现代码如下所示:

复制代码

1 namespace StrategyPattern 2 {
3 // 所得税计算策略
4 public interface ITaxStragety 5 {
6 double CalculateTax(double income); 7 }
8
9 // 个人所得税
10 public class PersonalTaxStrategy : ITaxStragety 11 { 12 public double CalculateTax(double income) 13 { 14 return income * 0.12; 15 } 16 } 17
18 // 企业所得税
19 public class EnterpriseTaxStrategy : ITaxStragety 20 { 21 public double CalculateTax(double income) 22 { 23 return (income - 3500) > 0 ? (income - 3500) * 0.045 : 0.0; 24 } 25 } 26
27 public class InterestOperation 28 { 29 private ITaxStragety m_strategy; 30 public InterestOperation(ITaxStragety strategy) 31 { 32 this.m_strategy = strategy; 33 } 34
35 public double GetTax(double income) 36 { 37 return m_strategy.CalculateTax(income); 38 } 39 } 40
41 class App 42 { 43 static void Main(string[] args) 44 { 45 // 个人所得税方式
46 InterestOperation operation = new InterestOperation(new PersonalTaxStrategy()); 47 Console.WriteLine(“个人支付的税为:{0}”, operation.GetTax(5000.00)); 48
49 // 企业所得税
50 operation = new InterestOperation(new EnterpriseTaxStrategy()); 51 Console.WriteLine(“企业支付的税为:{0}”, operation.GetTax(50000.00)); 52
53 Console.Read(); 54 } 55 } 56 }  

复制代码

   在.NET Framework中也不乏策略模式的应用例子。例如,在.NET中,为集合类型ArrayList和List提供的排序功能,其中实现就利用了策略模式,定义了IComparer接口来对比较算法进行封装,实现IComparer接口的类可以是顺序,或逆序地比较两个对象的大小,具体.NET中的实现可以使用反编译工具查看List.Sort(IComparer)的实现。其中List就是承担着环境角色,而IComparer接口承担着抽象策略角色,具体的策略角色就是实现了IComparer接口的类,List类本身实现了存在实现了该接口的类,我们可以自定义继承与该接口的具体策略类。

   在下面的情况下可以考虑使用策略模式:

  • 一个系统需要动态地在几种算法中选择一种的情况下。那么这些算法可以包装到一个个具体的算法类里面,并为这些具体的算法类提供一个统一的接口。
  • 如果一个对象有很多的行为,如果不使用合适的模式,这些行为就只好使用多重的if-else语句来实现,此时,可以使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象涉及的概念。

   策略模式的主要优点有:

  • 策略类之间可以自由切换。由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码。
  • 避免使用多重条件选择语句,充分体现面向对象设计思想。

  策略模式的主要缺点有:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这点可以考虑使用IOC容器和依赖注入的方式来解决,关于IOC容器和依赖注入(Dependency Inject)的文章可以参考:IoC 容器和Dependency Injection 模式
  • 策略模式会造成很多的策略类。

  到这里,策略模式的介绍就结束了,策略模式主要是对方法的封装,把一系列方法封装到一系列的策略类中,从而使不同的策略类可以自由切换和避免在系统使用多重条件选择语句来选择针对不同情况来选择不同的方法。在下一章将会大家介绍责任链模式。

   在上一篇博文分享了访问者模式,访问者模式的实现是把作用于某种数据结构上的操作封装到访问者中,使得操作和数据结构隔离。而今天要介绍的备忘者模式与命令模式有点相似,不同的是,命令模式保存的是发起人的具体命令(命令对应的是行为),而备忘录模式保存的是发起人的状态(而状态对应的数据结构,如属性)。下面具体来看看备忘录模式。

2.1 备忘录模式的定义

   从字面意思就可以明白,备忘录模式就是对某个类的状态进行保存下来,等到需要恢复的时候,可以从备忘录中进行恢复。生活中这样的例子经常看到,如备忘电话通讯录,备份操作操作系统,备份数据库等。

  备忘录模式的具体定义是:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。

2.2 备忘录模式的结构图

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

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

  • 发起人角色:记录当前时刻的内部状态,负责创建和恢复备忘录数据。
  • 备忘录角色:负责存储发起人对象的内部状态,在进行恢复时提供给发起人需要的状态。
  • 管理者角色:负责保存备忘录对象。

2.3 备忘录模式的实现

   下面以备份手机通讯录为例子来实现了备忘录模式,具体的实现代码如下所示:

复制代码

// 联系人
public class ContactPerson
{ public string Name { get; set; } public string MobileNum { get; set; }
} // 发起人
public class MobileOwner
{ // 发起人需要保存的内部状态
public List ContactPersons { get; set; } public MobileOwner(List persons)
{
ContactPersons = persons;
} // 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{ // 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List(this.ContactPersons));
} // 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{ // 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.contactPersonBack;
} public void Show()
{
Console.WriteLine(“联系人列表中有{0}个人,他们是:”, ContactPersons.Count); foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine(“姓名: {0} 号码为: {1}”, p.Name, p.MobileNum);
}
}
} // 备忘录
public class ContactMemento
{ // 保存发起人的内部状态
public List contactPersonBack; public ContactMemento(List persons)
{
contactPersonBack = persons;
}
} // 管理角色
public class Caretaker
{ public ContactMemento ContactM { get; set; }
} class Program
{ static void Main(string[] args)
{
List persons = new List()
{ new ContactPerson() { Name= “Learning Hard”, MobileNum = “123445”}, new ContactPerson() { Name = “Tony”, MobileNum = “234565”}, new ContactPerson() { Name = “Jock”, MobileNum = “231455”}
};
MobileOwner mobileOwner = new MobileOwner(persons);
mobileOwner.Show(); // 创建备忘录并保存备忘录对象
Caretaker caretaker = new Caretaker();
caretaker.ContactM = mobileOwner.CreateMemento(); // 更改发起人联系人列表
Console.WriteLine(“-—移除最后一个联系人——–”);
mobileOwner.ContactPersons.RemoveAt(2);
mobileOwner.Show(); // 恢复到原始状态
Console.WriteLine(“-——恢复联系人列表——“);
mobileOwner.RestoreMemento(caretaker.ContactM);
mobileOwner.Show();

        Console.Read();
    }
}

复制代码

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

  从上图可以看出,刚开始通讯录中有3个联系人,然后移除以后一个后变成2个联系人了,最后恢复原来的联系人列表后,联系人列表中又恢复为3个联系人了。

  上面代码只是保存了一个还原点,即备忘录中只保存了3个联系人的数据,但是,如果想备份多个还原点怎么办呢?即恢复到3个人后,又想恢复到前面2个人的状态,这时候可能你会想,这样没必要啊,到时候在删除不就好了。但是如果在实际应用中,可能我们发了很多时间去创建通讯录中只有2个联系人的状态,恢复到3个人的状态后,发现这个状态时错误的,还是原来2个人的状态是正确的,难道我们又去花之前的那么多时间去重复操作吗?这显然不合理,如果就思考,能不能保存多个还原点呢?保存多个还原点其实很简单,只需要保存多个备忘录对象就可以了。具体实现代码如下所示:

复制代码

namespace MultipleMementoPattern
{ // 联系人
public class ContactPerson
{ public string Name { get; set; } public string MobileNum { get; set; }
} // 发起人
public class MobileOwner
{ public List ContactPersons { get; set; } public MobileOwner(List persons)
{
ContactPersons = persons;
} // 创建备忘录,将当期要保存的联系人列表导入到备忘录中
public ContactMemento CreateMemento()
{ // 这里也应该传递深拷贝,new List方式传递的是浅拷贝,
// 因为ContactPerson类中都是string类型,所以这里new list方式对ContactPerson对象执行了深拷贝
// 如果ContactPerson包括非string的引用类型就会有问题,所以这里也应该用序列化传递深拷贝
return new ContactMemento(new List(this.ContactPersons));
} // 将备忘录中的数据备份导入到联系人列表中
public void RestoreMemento(ContactMemento memento)
{ if (memento != null)
{ // 下面这种方式是错误的,因为这样传递的是引用,
// 则删除一次可以恢复,但恢复之后再删除的话就恢复不了.
// 所以应该传递contactPersonBack的深拷贝,深拷贝可以使用序列化来完成
this.ContactPersons = memento.ContactPersonBack;
}
} public void Show()
{
Console.WriteLine(“联系人列表中有{0}个人,他们是:”, ContactPersons.Count); foreach (ContactPerson p in ContactPersons)
{
Console.WriteLine(“姓名: {0} 号码为: {1}”, p.Name, p.MobileNum);
}
}
} // 备忘录
public class ContactMemento
{ public List ContactPersonBack {get;set;} public ContactMemento(List persons)
{
ContactPersonBack = persons;
}
} // 管理角色
public class Caretaker
{ // 使用多个备忘录来存储多个备份点
public Dictionary<string, ContactMemento> ContactMementoDic { get; set; } public Caretaker()
{
ContactMementoDic = new Dictionary<string, ContactMemento>();
}
} class Program
{ static void Main(string[] args)
{
List persons = new List()
{ new ContactPerson() { Name= “Learning Hard”, MobileNum = “123445”}, new ContactPerson() { Name = “Tony”, MobileNum = “234565”}, new ContactPerson() { Name = “Jock”, MobileNum = “231455”}
};

        MobileOwner mobileOwner \= new MobileOwner(persons);
        mobileOwner.Show(); // 创建备忘录并保存备忘录对象
        Caretaker caretaker = new Caretaker();
        caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 更改发起人联系人列表
        Console.WriteLine("\----移除最后一个联系人--------");
        mobileOwner.ContactPersons.RemoveAt(2);
        mobileOwner.Show(); // 创建第二个备份
        Thread.Sleep(1000);
        caretaker.ContactMementoDic.Add(DateTime.Now.ToString(), mobileOwner.CreateMemento()); // 恢复到原始状态
        Console.WriteLine("\-------恢复联系人列表,请从以下列表选择恢复的日期------"); var keyCollection = caretaker.ContactMementoDic.Keys; foreach (string k in keyCollection)
        {
            Console.WriteLine("Key = {0}", k);
        } while (true)
        {
            Console.Write("请输入数字,按窗口的关闭键退出:"); int index = -1; try {
                index \= Int32.Parse(Console.ReadLine());
            } catch {
                Console.WriteLine("输入的格式错误"); continue;
            }
            
            ContactMemento contactMentor \= null; if (index < keyCollection.Count && caretaker.ContactMementoDic.TryGetValue(keyCollection.ElementAt(index), out contactMentor))
            {
                mobileOwner.RestoreMemento(contactMentor);
                mobileOwner.Show();
            } else {
                Console.WriteLine("输入的索引大于集合长度!");
            }
        }     
    }
}

}

复制代码

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

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

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

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

  • 如果某个操作错误地破坏了数据的完整性,此时可以使用备忘录模式将数据恢复成原来正确的数据。
  • 备份的状态数据保存在发起人角色之外,这样发起人就不需要对各个备份的状态进行管理。而是由备忘录角色进行管理,而备忘录角色又是由管理者角色管理,符合单一职责原则。

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

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

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

一、引言

在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在内存中分配了多个一样的类实例对象,然后如果采用工厂模式来创建这样的系统的话,随着产品类的不断增加,导致子类的数量不断增多,反而增加了系统复杂程度,所以在这里使用工厂模式来封装类创建过程并不合适,然而原型模式可以很好地解决这个问题,因为每个类实例都是相同的,当我们需要多个相同的类实例时,没必要每次都使用new运算符去创建相同的类实例对象,此时我们一般思路就是想——只创建一个类实例对象,如果后面需要更多这样的实例,可以通过对原来对象拷贝一份来完成创建,这样在内存中不需要创建多个相同的类实例,从而减少内存的消耗和达到类实例的复用。 然而这个思路正是原型模式的实现方式。下面就具体介绍下设计模式中的原型设计模式。

二、原型模式的详细介绍

在现实生活中,也有很多原型设计模式的例子,例如,细胞分裂的过程,一个细胞的有丝分裂产生两个相同的细胞;还有西游记中孙悟空变出后孙的本领和火影忍者中鸣人的隐分身忍术等。下面就以孙悟空为例子来演示下原型模式的实现。具体的实现代码如下:

复制代码

///火影忍者中鸣人的影分身和孙悟空的的变都是原型模式
class Client
{ static void Main(string[] args)
{ // 孙悟空 原型
MonkeyKingPrototype prototypeMonkeyKing = new ConcretePrototype(“MonkeyKing”); // 变一个
MonkeyKingPrototype cloneMonkeyKing = prototypeMonkeyKing.Clone() as ConcretePrototype;
Console.WriteLine(“Cloned1:\t”+cloneMonkeyKing.Id); // 变两个
MonkeyKingPrototype cloneMonkeyKing2 = prototypeMonkeyKing.Clone() as ConcretePrototype;
Console.WriteLine(“Cloned2:\t” + cloneMonkeyKing2.Id);
Console.ReadLine();
}
} ///


/// 孙悟空原型 ///

public abstract class MonkeyKingPrototype
{ public string Id { get; set; } public MonkeyKingPrototype(string id)
{ this.Id = id;
} // 克隆方法,即孙大圣说“变”
public abstract MonkeyKingPrototype Clone();
} ///
/// 创建具体原型 ///

public class ConcretePrototype : MonkeyKingPrototype
{ public ConcretePrototype(string id)
: base(id)
{ } ///
/// 浅拷贝 ///

///
public override MonkeyKingPrototype Clone()
{ // 调用MemberwiseClone方法实现的是浅拷贝,另外还有深拷贝
return (MonkeyKingPrototype)this.MemberwiseClone();
}
}

复制代码

上面原型模式的运行结果为(从运行结果可以看出,创建的两个拷贝对象的ID属性都是与原型对象ID属性一样的):

上面代码实现的浅拷贝的方式,浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么这两个对象将引用同一个字符串,而深拷贝是对对象实例中字段引用的对象也进行拷贝,如果一个对象有一个指向字符串的字段,并且我们对该对象进行了深拷贝的话,那么我们将创建一个对象和一个新的字符串,新的对象将引用新的字符串。也就是说,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响,而执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。

介绍完原型模式的实现代码之后,下面看下原型模式的类图,通过类图来理清原型模式实现中类之间的关系。具体类图如下:

三、原型模式的优缺点

原型模式的优点有:

  1. 原型模式向客户隐藏了创建新实例的复杂性
  2. 原型模式允许动态增加或较少产品类。
  3. 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
  4. 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构

原型模式的缺点有:

  1. 每个类必须配备一个克隆方法
  2. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

四、.NET中原型模式的实现

在.NET中可以很容易地通过实现ICloneable接口(这个接口就是原型,提供克隆方法,相当于与上面代码中MonkeyKingPrototype抽象类)中Clone()方法来实现原型模式,如果我们想我们自定义的类具有克隆的功能,首先定义类继承与ICloneable接口并实现Clone方法。在.NET中实现了原型模式的类如下图所示(图中只截取了部分,可以用Reflector反编译工具进行查看):

五、总结

到这里关于原型模式的介绍就结束了,原型模式用一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的方法来创建出更多的同类型对象,它与工厂方法模式的实现非常相似,其中原型模式中的Clone方法就类似工厂方法模式中的工厂方法,只是工厂方法模式的工厂方法是通过new运算符重新创建一个新的对象(相当于原型模式的深拷贝实现),而原型模式是通过调用MemberwiseClone方法来对原来对象进行拷贝,也就是复制,同时在原型模式优点中也介绍了与工厂方法的区别(第三点)。

本专题中所有源码:设计模式之原型模式

一、引言

在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如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);

复制代码

六、总结

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

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

本文源码寄方于github:https://github.com/w392807287/Design\_pattern\_of\_python

参考文献:

《大话设计模式》——吴强

《Python设计模式》——pythontip.com

《23种设计模式》——http://www.cnblogs.com/beijiguangyong/

设计模式是什么?

设计模式是经过总结、优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码。反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言。一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性)。最为重要的是,设计模式也是一把双刃剑,如果设计模式被用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦。然而如果设计模式在正确的时间被用在正确地地方,它将是你的救星。

起初,你会认为“模式”就是为了解决一类特定问题而特别想出来的明智之举。说的没错,看起来的确是通过很多人一起工作,从不同的角度看待问题进而形成的一个最通用、最灵活的解决方案。也许这些问题你曾经见过或是曾经解决过,但是你的解决方案很可能没有模式这么完备。

虽然被称为“设计模式”,但是它们同“设计“领域并非紧密联系。设计模式同传统意义上的分析、设计与实现不同,事实上设计模式将一个完整的理念根植于程序中,所以它可能出现在分析阶段或是更高层的设计阶段。很有趣的是因为设计模式的具体体现是程序代码,因此可能会让你认为它不会在具体实现阶段之前出现(事实上在进入具体实现阶段之前你都没有意识到正在使用具体的设计模式)。

可以通过程序设计的基本概念来理解模式:增加一个抽象层。抽象一个事物就是隔离任何具体细节,这么做的目的是为了将那些不变的核心部分从其他细节中分离出来。当你发现你程序中的某些部分经常因为某些原因改动,而你不想让这些改动的部分引发其他部分的改动,这时候你就需要思考那些不会变动的设计方法了。这么做不仅会使代码可维护性更高,而且会让代码更易于理解,从而降低开发成本。

这里列举了三种最基本的设计模式:

  1. 创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
  2. 结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
  3. 行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。

创建型

1. Factory Method(工厂方法)

2. Abstract Factory(抽象工厂)

3. Builder(建造者)

4. Prototype(原型)

5. Singleton(单例)

结构型

6. Adapter Class/Object(适配器)

7. Bridge(桥接)

8. Composite(组合)

9. Decorator(装饰)

10. Facade(外观)

11. Flyweight(享元)

12. Proxy(代理)

行为型

13. Interpreter(解释器)

14. Template Method(模板方法)

15. Chain of Responsibility(责任链)

16. Command(命令)

17. Iterator(迭代器)

18. Mediator(中介者)

19. Memento(备忘录)

20. Observer(观察者)

21. State(状态)

22. Strategy(策略)

23. Visitor(访问者)

创建型

1.Factory Method(工厂方法)

意图:

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使个类的实例化延迟到其子类。

适用性:

当一个类不知道它所必须创建的对象的类的时候。

当一个类希望由它的子类来指定它所创建的对象的时候。

当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

实现:

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

35

class ChinaGetter:

def __init__(``self``):

self``.trans = dict``(dog``=``u``"小狗"``, cat``=``u``"小猫"``)

def get(``self``, msgid):

try``:

return self``.trans[msgid]

except KeyError:

return str``(msgid)

class EnglishGetter:

def get(``self``, msgid):

return str``(msgid)

def get_localizer(language``=``"English"``):

languages = dict``(English``=``EnglishGetter, China``=``ChinaGetter)

return languages[language]()

e, g = get_localizer(``"English"``), get_localizer(``"China"``)

for msgid in "dog parrot cat bear"``.split():

print``(e.get(msgid), g.get(msgid))

2. Abstract Factory(抽象工厂)

意图:

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

 一个系统要独立于它的产品的创建、组合和表示时。

 一个系统要由多个产品系列中的一个来配置时。

 当你要强调一系列相关的产品对象的设计以便进行联合使用时。

 当你提供一个产品类库,而只想显示它们的接口而不是实现时。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

import random

class PetShop:

def __init__(``self``, animal_factory``=``None``):

self``.pet_factory = animal_factory

def show_pet(``self``):

pet = self``.pet_factory.get_pet()

print``(``"This is a lovely"``, str``(pet))

print``(``"It says"``, pet.speak())

print``(``"It eats"``, self``.pet_factory.get_food())

class Dog:

def speak(``self``):

return "woof"

def __str__(``self``):

return "Dog"

class Cat:

def speak(``self``):

return "meow"

def __str__(``self``):

return "Cat"

class DogFactory:

def get_pet(``self``):

return Dog()

def get_food(``self``):

return "dog food"

class CatFactory:

def get_pet(``self``):

return Cat()

def get_food(``self``):

return "cat food"

def get_factory():

return random.choice([DogFactory, CatFactory])()

if __name__ =``= "__main__"``:

shop = PetShop()

for i in range``(``3``):

shop.pet_factory = get_factory()

shop.show_pet()

print``(``"=" * 20``)

3. Builder(建造者)

意图:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用性:

当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

当构造过程必须允许被构造的对象有不同的表示时。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

class Director(``object``):

def __init__(``self``):

self``.builder = None

def construct_building(``self``):

self``.builder.new_building()

self``.builder.build_floor()

self``.builder.build_size()

def get_building(``self``):

return self``.builder.building

class Builder(``object``):

def __init__(``self``):

self``.building = None

def new_building(``self``):

self``.building = Building()

class BuilderHouse(Builder):

def build_floor(``self``):

self``.building.floor = 'One'

def build_size(``self``):

self``.building.size = 'Big'

class BuilderFlat(Builder):

def build_floor(``self``):

self``.building.floor = 'More than One'

def build_size(``self``):

self``.building.size = 'Small'

class Building(``object``):

def __init__(``self``):

self``.floor = None

self``.size = None

def __repr__(``self``):

return 'Floor: %s | Size: %s' % (``self``.floor, self``.size)

if __name__ =``= "__main__"``:

director = Director()

director.builder = BuilderHouse()

director.construct_building()

building = director.get_building()

print``(building)

director.builder = BuilderFlat()

director.construct_building()

building = director.get_building()

print``(building)

4. Prototype(原型)

意图:

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

适用性:

当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者为了避免创建一个与产品类层次平行的工厂类层次时;或者当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

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

35

36

37

38

39

40

41

42

43

import copy

class Prototype:

def __init__(``self``):

self``._objects = {}

def register_object(``self``, name, obj):

self``._objects[name] = obj

def unregister_object(``self``, name):

del self``._objects[name]

def clone(``self``, name, *``*``attr):

obj = copy.deepcopy(``self``._objects.get(name))

obj.__dict__.update(attr)

return obj

def main():

class A:

def __str__(``self``):

return "I am A"

a = A()

prototype = Prototype()

prototype.register_object(``'a'``, a)

b = prototype.clone(``'a'``, a``=``1``, b``=``2``, c``=``3``)

print``(a)

print``(b.a, b.b, b.c)

if __name__ =``= '__main__'``:

main()

5. Singleton(单例)

意图:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性:

当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。

当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

实现:

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

class Singleton(``object``):

def __new__(``cls``, *``args, *``*``kw):

if not hasattr``(``cls``, '_instance'``):

org = super``(Singleton, cls``)

cls``._instance = org.__new__(``cls``, *``args, *``*``kw)

return cls``._instance

if __name__ =``= '__main__'``:

class SingleSpam(Singleton):

def __init__(``self``, s):

self``.s = s

def __str__(``self``):

return self``.s

s1 = SingleSpam(``'spam'``)

print id``(s1), s1

s2 = SingleSpam(``'spa'``)

print id``(s2), s2

print id``(s1), s1

6. Adapter Class/Object(适配器)

意图:

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

适用性:

 你想使用一个已经存在的类,而它的接口不符合你的需求。

你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。

(仅适用于对象Adapter )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

import os

class Dog(``object``):

def __init__(``self``):

self``.name = "Dog"

def bark(``self``):

return "woof!"

class Cat(``object``):

def __init__(``self``):

self``.name = "Cat"

def meow(``self``):

return "meow!"

class Human(``object``):

def __init__(``self``):

self``.name = "Human"

def speak(``self``):

return "'hello'"

class Car(``object``):

def __init__(``self``):

self``.name = "Car"

def make_noise(``self``, octane_level):

return "vroom%s" % (``"!" * octane_level)

class Adapter(``object``):

def __init__(``self``, obj, adapted_methods):

self``.obj = obj

self``.__dict__.update(adapted_methods)

def __getattr__(``self``, attr):

return getattr``(``self``.obj, attr)

def main():

objects = []

dog = Dog()

objects.append(Adapter(dog, dict``(make_noise``=``dog.bark)))

cat = Cat()

objects.append(Adapter(cat, dict``(make_noise``=``cat.meow)))

human = Human()

objects.append(Adapter(human, dict``(make_noise``=``human.speak)))

car = Car()

car_noise = lambda``: car.make_noise(``3``)

objects.append(Adapter(car, dict``(make_noise``=``car_noise)))

for obj in objects:

print "A"``, obj.name, "goes"``, obj.make_noise()

if __name__ =``= "__main__"``:

main()

7. Bridge(桥接)

意图:

 将抽象部分与它的实现部分分离,使它们都可以独立地变化。

 适用性:

 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。

 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。

 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。

 (C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。

 有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh 称这种类层次结构为“嵌套的普化”(nested generalizations )。

 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是Coplien 的String 类[ Cop92 ],在这个类中多个对象可以共享同一个字符串表示(StringRep)。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

class DrawingAPI1(``object``):

def draw_circle(``self``, x, y, radius):

print``(``'API1.circle at {}:{} radius {}'``.``format``(x, y, radius))

class DrawingAPI2(``object``):

def draw_circle(``self``, x, y, radius):

print``(``'API2.circle at {}:{} radius {}'``.``format``(x, y, radius))

class CircleShape(``object``):

def __init__(``self``, x, y, radius, drawing_api):

self``._x = x

self``._y = y

self``._radius = radius

self``._drawing_api = drawing_api

def draw(``self``):

self``._drawing_api.draw_circle(``self``._x, self``._y, self``._radius)

def scale(``self``, pct):

self``._radius *``= pct

def main():

shapes = (

CircleShape(``1``, 2``, 3``, DrawingAPI1()),

CircleShape(``5``, 7``, 11``, DrawingAPI2())

)

for shape in shapes:

shape.scale(``2.5``)

shape.draw()

if __name__ =``= '__main__'``:

main()

8. Composite(组合)

意图:

 将对象组合成树形结构以表示“部分-整体”的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。 

适用性:

 你想表示对象的部分-整体层次结构。

你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

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

35

36

37

38

39

40

41

42

43

44

class Component:

def __init__(``self``,strName):

self``.m_strName = strName

def Add(``self``,com):

pass

def Display(``self``,nDepth):

pass

class Leaf(Component):

def Add(``self``,com):

print "leaf can't add"

def Display(``self``,nDepth):

strtemp = "-" * nDepth

strtemp``=``strtemp``+``self``.m_strName

print strtemp

class Composite(Component):

def __init__(``self``,strName):

self``.m_strName = strName

self``.c = []

def Add(``self``,com):

self``.c.append(com)

def Display(``self``,nDepth):

strtemp = "-"``*``nDepth

strtemp``=``strtemp``+``self``.m_strName

print strtemp

for com in self``.c:

com.Display(nDepth``+``2``)

if __name__ =``= "__main__"``:

p = Composite(``"Wong"``)

p.Add(Leaf(``"Lee"``))

p.Add(Leaf(``"Zhao"``))

p1 = Composite(``"Wu"``)

p1.Add(Leaf(``"San"``))

p.Add(p1)

p.Display(``1``);

9. Decorator(装饰)

意图: 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。 
适用性:

 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

 处理那些可以撤消的职责。

当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

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

class foo(``object``):

def f1(``self``):

print``(``"original f1"``)

def f2(``self``):

print``(``"original f2"``)

class foo_decorator(``object``):

def __init__(``self``, decoratee):

self``._decoratee = decoratee

def f1(``self``):

print``(``"decorated f1"``)

self``._decoratee.f1()

def __getattr__(``self``, name):

return getattr``(``self``._decoratee, name)

u = foo()

v = foo_decorator(u)

v.f1()

v.f2()

10. Facade(外观)

意图:

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

适用性:

当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。

客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。

当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

import time

SLEEP = 0.5

class TC1:

def run(``self``):

print``(``"###### In Test 1 ######"``)

time.sleep(SLEEP)

print``(``"Setting up"``)

time.sleep(SLEEP)

print``(``"Running test"``)

time.sleep(SLEEP)

print``(``"Tearing down"``)

time.sleep(SLEEP)

print``(``"Test Finished\n"``)

class TC2:

def run(``self``):

print``(``"###### In Test 2 ######"``)

time.sleep(SLEEP)

print``(``"Setting up"``)

time.sleep(SLEEP)

print``(``"Running test"``)

time.sleep(SLEEP)

print``(``"Tearing down"``)

time.sleep(SLEEP)

print``(``"Test Finished\n"``)

class TC3:

def run(``self``):

print``(``"###### In Test 3 ######"``)

time.sleep(SLEEP)

print``(``"Setting up"``)

time.sleep(SLEEP)

print``(``"Running test"``)

time.sleep(SLEEP)

print``(``"Tearing down"``)

time.sleep(SLEEP)

print``(``"Test Finished\n"``)

class TestRunner:

def __init__(``self``):

self``.tc1 = TC1()

self``.tc2 = TC2()

self``.tc3 = TC3()

self``.tests = [i for i in (``self``.tc1, self``.tc2, self``.tc3)]

def runAll(``self``):

[i.run() for i in self``.tests]

if __name__ =``= '__main__'``:

testrunner = TestRunner()

testrunner.runAll()

11. Flyweight(享元)

意图:

运用共享技术有效地支持大量细粒度的对象。

适用性:

一个应用程序使用了大量的对象。

完全由于使用大量的对象,造成很大的存储开销。

对象的大多数状态都可变为外部状态。

如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。 

应用程序不依赖于对象标识。由于Flyweight 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。

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

35

36

37

import weakref

class Card(``object``):

_CardPool = weakref.WeakValueDictionary()

def __new__(``cls``, value, suit):

obj = Card._CardPool.get(value + suit, None``)

if not obj:

obj = object``.__new__(``cls``)

Card._CardPool[value + suit] = obj

obj.value, obj.suit = value, suit

return obj

def __repr__(``self``):

return "<Card: %s%s>" % (``self``.value, self``.suit)

if __name__ =``= '__main__'``:

c1 = Card(``'9'``, 'h'``)

c2 = Card(``'9'``, 'h'``)

print``(c1, c2)

print``(c1 =``= c2)

print``(``id``(c1), id``(c2))

12. Proxy(代理)

意图:

为其他对象提供一种代理以控制对这个对象的访问。

适用性:

 在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一 些可以使用Proxy 模式常见情况: 

1) 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用NXProxy 类实现了这一目的。Coplien[Cop92] 称这种代理为“大使” (Ambassador )。 
2 )虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的ImageProxy 就是这样一种代理的例子。 
3) 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。例如,在Choices 操作系统[ CIRM93]中KemelProxies为操作系统对象提供 了访问保护。 
4 )智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括:对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为SmartPointers[Ede92 ] )。

 当第一次引用一个持久对象时,将它装入内存。

 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

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

35

36

import time

class SalesManager:

def work(``self``):

print``(``"Sales Manager working..."``)

def talk(``self``):

print``(``"Sales Manager ready to talk"``)

class Proxy:

def __init__(``self``):

self``.busy = 'No'

self``.sales = None

def work(``self``):

print``(``"Proxy checking for Sales Manager availability"``)

if self``.busy =``= 'No'``:

self``.sales = SalesManager()

time.sleep(``2``)

self``.sales.talk()

else``:

time.sleep(``2``)

print``(``"Sales Manager is busy"``)

if __name__ =``= '__main__'``:

p = Proxy()

p.work()

p.busy = 'Yes'

p.work()

13. Interpreter(解释器)

意图:

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

适用性:

当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式, 这样可以节省空间而且还可能节省时间。

效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。

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

class Context:

def __init__(``self``):

self``.``input``=``""

self``.output``=``""

class AbstractExpression:

def Interpret(``self``,context):

pass

class Expression(AbstractExpression):

def Interpret(``self``,context):

print "terminal interpret"

class NonterminalExpression(AbstractExpression):

def Interpret(``self``,context):

print "Nonterminal interpret"

if __name__ =``= "__main__"``:

context``= ""

c = []

c = c + [Expression()]

c = c + [NonterminalExpression()]

c = c + [Expression()]

c = c + [Expression()]

for a in c:

a.Interpret(context)

14. Template Method(模板方法)

意图:

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

适用性:

一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke 和Johnson所描述过的“重分解以一般化”的一个很好的例子[ OJ93 ]。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。

控制子类扩展。模板方法只在特定点调用“hook ”操作(参见效果一节),这样就只允许在这些点进行扩展。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

ingredients = "spam eggs apple"

line = '-' * 10

def iter_elements(getter, action):

for element in getter():

action(element)

print``(line)

def rev_elements(getter, action):

for element in getter()[::``-``1``]:

action(element)

print``(line)

def get_list():

return ingredients.split()

def get_lists():

return [``list``(x) for x in ingredients.split()]

def print_item(item):

print``(item)

def reverse_item(item):

print``(item[::``-``1``])

def make_template(skeleton, getter, action):

def template():

skeleton(getter, action)

return template

templates = [make_template(s, g, a)

for g in (get_list, get_lists)

for a in (print_item, reverse_item)

for s in (iter_elements, rev_elements)]

for template in templates:

template()

15. Chain of Responsibility(责任链)

意图:

 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

 适用性:

 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。

 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

 可处理一个请求的对象集合应被动态指定。

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

35

36

37

38

39

40

41

42

43

44

45

46

class Handler:

def successor(``self``, successor):

self``.successor = successor

class ConcreteHandler1(Handler):

def handle(``self``, request):

if request > 0 and request <``= 10``:

print``(``"in handler1"``)

else``:

self``.successor.handle(request)

class ConcreteHandler2(Handler):

def handle(``self``, request):

if request > 10 and request <``= 20``:

print``(``"in handler2"``)

else``:

self``.successor.handle(request)

class ConcreteHandler3(Handler):

def handle(``self``, request):

if request > 20 and request <``= 30``:

print``(``"in handler3"``)

else``:

print``(``'end of chain, no handler for {}'``.``format``(request))

class Client:

def __init__(``self``):

h1 = ConcreteHandler1()

h2 = ConcreteHandler2()

h3 = ConcreteHandler3()

h1.successor(h2)

h2.successor(h3)

requests = [``2``, 5``, 14``, 22``, 18``, 3``, 35``, 27``, 20``]

for request in requests:

h1.handle(request)

if __name__ =``= "__main__"``:

client = Client()

16. Command(命令)

意图:

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

适用性:

抽象出待执行的动作以参数化某对象,你可用过程语言中的回调(call back)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。

在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。

支持取消操作。Command的Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command 接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。

支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。

用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务( transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。

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

35

36

37

38

39

import os

class MoveFileCommand(``object``):

def __init__(``self``, src, dest):

self``.src = src

self``.dest = dest

def execute(``self``):

self``()

def __call__(``self``):

print``(``'renaming {} to {}'``.``format``(``self``.src, self``.dest))

os.rename(``self``.src, self``.dest)

def undo(``self``):

print``(``'renaming {} to {}'``.``format``(``self``.dest, self``.src))

os.rename(``self``.dest, self``.src)

if __name__ =``= "__main__"``:

command_stack = []

command_stack.append(MoveFileCommand(``'foo.txt'``, 'bar.txt'``))

command_stack.append(MoveFileCommand(``'bar.txt'``, 'baz.txt'``))

for cmd in command_stack:

cmd.execute()

for cmd in reversed``(command_stack):

cmd.undo()

17. Iterator(迭代器)

意图:

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

适用性:

访问一个聚合对象的内容而无需暴露它的内部表示。

支持对聚合对象的多种遍历。

为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。

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

def count_to(count):

numbers = [``"one"``, "two"``, "three"``, "four"``, "five"``]

for pos, number in zip``(``range``(count), numbers):

yield number

count_to_two = lambda``: count_to(``2``)

count_to_five = lambda``: count_to(``5``)

print``(``'Counting to two...'``)

for number in count_to_two():

print number

print " "

print``(``'Counting to five...'``)

for number in count_to_five():

print number

print " "

18. Mediator(中介者)

意图:

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

适用性:

一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。

一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。

想定制一个分布在多个类中的行为,而又不想生成太多的子类。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

import time

class TC:

def __init__(``self``):

self``._tm = tm

self``._bProblem = 0

def setup(``self``):

print``(``"Setting up the Test"``)

time.sleep(``1``)

self``._tm.prepareReporting()

def execute(``self``):

if not self``._bProblem:

print``(``"Executing the test"``)

time.sleep(``1``)

else``:

print``(``"Problem in setup. Test not executed."``)

def tearDown(``self``):

if not self``._bProblem:

print``(``"Tearing down"``)

time.sleep(``1``)

self``._tm.publishReport()

else``:

print``(``"Test not executed. No tear down required."``)

def setTM(``self``, TM):

self``._tm = tm

def setProblem(``self``, value):

self``._bProblem = value

class Reporter:

def __init__(``self``):

self``._tm = None

def prepare(``self``):

print``(``"Reporter Class is preparing to report the results"``)

time.sleep(``1``)

def report(``self``):

print``(``"Reporting the results of Test"``)

time.sleep(``1``)

def setTM(``self``, TM):

self``._tm = tm

class DB:

def __init__(``self``):

self``._tm = None

def insert(``self``):

print``(``"Inserting the execution begin status in the Database"``)

time.sleep(``1``)

import random

if random.randrange(``1``, 4``) =``= 3``:

return -``1

def update(``self``):

print``(``"Updating the test results in Database"``)

time.sleep(``1``)

def setTM(``self``, TM):

self``._tm = tm

class TestManager:

def __init__(``self``):

self``._reporter = None

self``._db = None

self``._tc = None

def prepareReporting(``self``):

rvalue = self``._db.insert()

if rvalue =``= -``1``:

self``._tc.setProblem(``1``)

self``._reporter.prepare()

def setReporter(``self``, reporter):

self``._reporter = reporter

def setDB(``self``, db):

self``._db = db

def publishReport(``self``):

self``._db.update()

rvalue = self``._reporter.report()

def setTC(``self``, tc):

self``._tc = tc

if __name__ =``= '__main__'``:

reporter = Reporter()

db = DB()

tm = TestManager()

tm.setReporter(reporter)

tm.setDB(db)

reporter.setTM(tm)

db.setTM(tm)

while (``True``):

tc = TC()

tc.setTM(tm)

tm.setTC(tc)

tc.setup()

tc.execute()

tc.tearDown()

19. Memento(备忘录)

意图:

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

适用性:

必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。

如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

import copy

def Memento(obj, deep``=``False``):

state = (copy.copy, copy.deepcopy)[``bool``(deep)](obj.__dict__)

def Restore():

obj.__dict__.clear()

obj.__dict__.update(state)

return Restore

class Transaction:

deep = False

def __init__(``self``, *``targets):

self``.targets = targets

self``.Commit()

def Commit(``self``):

self``.states = [Memento(target, self``.deep) for target in self``.targets]

def Rollback(``self``):

for st in self``.states:

st()

class transactional(``object``):

def __init__(``self``, method):

self``.method = method

def __get__(``self``, obj, T):

def transaction(``*``args, *``*``kwargs):

state = Memento(obj)

try``:

return self``.method(obj, *``args, *``*``kwargs)

except``:

state()

raise

return transaction

class NumObj(``object``):

def __init__(``self``, value):

self``.value = value

def __repr__(``self``):

return '<%s: %r>' % (``self``.__class__.__name__, self``.value)

def Increment(``self``):

self``.value +``= 1

@transactional

def DoStuff(``self``):

self``.value = '1111' 

self``.Increment()

if __name__ =``= '__main__'``:

n = NumObj(``-``1``)

print``(n)

t = Transaction(n)

try``:

for i in range``(``3``):

n.Increment()

print``(n)

t.Commit()

print``(``'-- commited'``)

for i in range``(``3``):

n.Increment()

print``(n)

n.value +``= 'x' 

print``(n)

except``:

t.Rollback()

print``(``'-- rolled back'``)

print``(n)

print``(``'-- now doing stuff ...'``)

try``:

n.DoStuff()

except``:

print``(``'-> doing stuff failed!'``)

import traceback

traceback.print_exc(``0``)

pass

print``(n)

20. Observer(观察者)

意图:

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

适用性:

当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。

当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

class Subject(``object``):

def __init__(``self``):

self``._observers = []

def attach(``self``, observer):

if not observer in self``._observers:

self``._observers.append(observer)

def detach(``self``, observer):

try``:

self``._observers.remove(observer)

except ValueError:

pass

def notify(``self``, modifier``=``None``):

for observer in self``._observers:

if modifier !``= observer:

observer.update(``self``)

class Data(Subject):

def __init__(``self``, name``=``''):

Subject.__init__(``self``)

self``.name = name

self``._data = 0

@property

def data(``self``):

return self``._data

@data``.setter

def data(``self``, value):

self``._data = value

self``.notify()

class HexViewer:

def update(``self``, subject):

print``(``'HexViewer: Subject %s has data 0x%x' %

(subject.name, subject.data))

class DecimalViewer:

def update(``self``, subject):

print``(``'DecimalViewer: Subject %s has data %d' %

(subject.name, subject.data))

def main():

data1 = Data(``'Data 1'``)

data2 = Data(``'Data 2'``)

view1 = DecimalViewer()

view2 = HexViewer()

data1.attach(view1)

data1.attach(view2)

data2.attach(view2)

data2.attach(view1)

print``(``"Setting Data 1 = 10"``)

data1.data = 10

print``(``"Setting Data 2 = 15"``)

data2.data = 15

print``(``"Setting Data 1 = 3"``)

data1.data = 3

print``(``"Setting Data 2 = 5"``)

data2.data = 5

print``(``"Detach HexViewer from data1 and data2."``)

data1.detach(view2)

data2.detach(view2)

print``(``"Setting Data 1 = 10"``)

data1.data = 10

print``(``"Setting Data 2 = 15"``)

data2.data = 15

if __name__ =``= '__main__'``:

main()

21. State(状态)

意图:

 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

 适用性:

 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。

 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常, 有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

class State(``object``):

def scan(``self``):

self``.pos +``= 1

if self``.pos =``= len``(``self``.stations):

self``.pos = 0

print``(``"Scanning... Station is"``, self``.stations[``self``.pos], self``.name)

class AmState(State):

def __init__(``self``, radio):

self``.radio = radio

self``.stations = [``"1250"``, "1380"``, "1510"``]

self``.pos = 0

self``.name = "AM"

def toggle_amfm(``self``):

print``(``"Switching to FM"``)

self``.radio.state = self``.radio.fmstate

class FmState(State):

def __init__(``self``, radio):

self``.radio = radio

self``.stations = [``"81.3"``, "89.1"``, "103.9"``]

self``.pos = 0

self``.name = "FM"

def toggle_amfm(``self``):

print``(``"Switching to AM"``)

self``.radio.state = self``.radio.amstate

class Radio(``object``):

def __init__(``self``):

self``.amstate = AmState(``self``)

self``.fmstate = FmState(``self``)

self``.state = self``.amstate

def toggle_amfm(``self``):

self``.state.toggle_amfm()

def scan(``self``):

self``.state.scan()

if __name__ =``= '__main__'``:

radio = Radio()

actions = [radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2

actions = actions * 2

for action in actions:

action()

22. Strategy(策略)

意图:

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

 适用性:

 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。

 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时[H087] ,可以使用策略模式。

 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。

 一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

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

35

36

37

38

39

import types

class StrategyExample:

def __init__(``self``, func``=``None``):

self``.name = 'Strategy Example 0'        

if func is not None``:

self``.execute = types.MethodType(func, self``)

def execute(``self``):

print``(``self``.name)

def execute_replacement1(``self``):

print``(``self``.name + ' from execute 1'``)

def execute_replacement2(``self``):

print``(``self``.name + ' from execute 2'``)

if __name__ =``= '__main__'``:

strat0 = StrategyExample()

strat1 = StrategyExample(execute_replacement1)

strat1.name = 'Strategy Example 1'    

strat2 = StrategyExample(execute_replacement2)

strat2.name = 'Strategy Example 2'

strat0.execute()

strat1.execute()

strat2.execute()

23. Visitor(访问者)

意图:

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

 适用性:

 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke和Johnson所描述过的“重分解以一般化”的一个很好的例子[OJ93]。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。

 控制子类扩展。模板方法只在特定点调用“hook ”操作(参见效果一节),这样就只允许在这些点进行扩展。

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

35

36

37

38

39

40

41

42

43

class Node(``object``):

pass

class A(Node):

pass

class B(Node):

pass

class C(A, B):

pass

class Visitor(``object``):

def visit(``self``, node, *``args, *``*``kwargs):

meth = None

for cls in node.__class__.__mro__:

meth_name = 'visit_'``+``cls``.__name__

meth = getattr``(``self``, meth_name, None``)

if meth:

break

if not meth:

meth = self``.generic_visit

return meth(node, *``args, *``*``kwargs)

def generic_visit(``self``, node, *``args, *``*``kwargs):

print``(``'generic_visit '``+``node.__class__.__name__)

def visit_B(``self``, node, *``args, *``*``kwargs):

print``(``'visit_B '``+``node.__class__.__name__)

a = A()

b = B()

c = C()

visitor = Visitor()

visitor.visit(a)

visitor.visit(b)

visitor.visit(c)

以上  

欢迎多来访问博客:http://liqiongyu.com/blog

微信公众号:

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式
例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式有 3 个特点:

  • 单例类只有一个实例对象
  • 该单例对象必须由单例类自行创建
  • 单例类对外提供一个访问该单例的全局访问点

单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

单例模式的结构

单例模式的主要角色如下:

  • 单例类(Singleton):包含一个实例且能自行创建这个实例的类。
  • 访问类(Client):使用单例的类。

单例模式的实现

Singleton 模式通常有两种实现形式:懒汉式单例、饿汉式单例。

第 1 种:懒汉式单例

该模式的特点是类加载时没有生成单例,只有当第一次调用 Getlnstance 方法时才去创建这个单例。代码如下:

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
35



public class Singleton
{

private static Singleton instance;


private static readonly object locker = new object();


private Singleton(){ }





public static Singleton GetInstance()
{

if (instance == null)
{
lock (locker)
{

if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}

第 2 种:饿汉式单例

该模式的特点是类一旦加载就创建一个单例,保证在调用 GetInstance 方法之前单例已经存在了。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20



public class Singleton
{

private static Singleton instance=new Singleton();


private Singleton() { }





public static Singleton GetInstance()
{
return instance;
}
}

前面分析了单例模式的结构与特点,以下是它通常适用的场景的特点:

  • 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
  • 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。

单例模式可扩展为多例(Multition)模式,这种模式可生成多个实例并保存在 List 中,客户需要时可随机获取,其结构图如图所示:

多例模式可分为以下两种:

  • 有上限多例模式:多例类的实例数目有上限,并把实例的上限当做逻辑的一部分创造到了多例类的内部,这种多例模式叫做有上限多例模式。
  • 无上限多例模式:多例类的实例数目并不需要有上限,实例数目并没有上限的多例模式就叫做无上限多例模式。

原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。

由于 C# 提供了 ICloneable 接口,用 C# 实现原型模式很简单。

模式的结构

原型模式包含以下主要角色:

  • 原型(Prototype):声明一个克隆自身的接口,该角色一般有抽象类(Prototype)、接口(ICloneable)两种实现方式(GOF使用的是抽象类,在C#中个人推荐使用接口)。
  • 具体原型类(ConcretePrototype):实现原型(抽象类或接口)的 Clone() 方法,它是可被复制的对象。
  • 访问类(Client):使用具体原型类中的 Clone() 方法来复制新的对象。

使用接口作为Prototype时,其结构图如图所示(后面全部是接口方式的原型模式实现):

使用抽象类作为Prototype时,其结构图如图所示(来自GOF):

模式的实现

C# 中已经定义了 ICloneable 接口,具体原型类只要实现 ICloneable 接口就可实现对象的克隆(Object有 MemberwiseClone() 方法默认浅克隆),克隆是浅克隆还是深克隆取决于具体原型类 Clone() 的实现。其代码如下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

public interface ICloneable
{
Object Clone();
}


public abstract class Prototype
{
public abstract Object Clone();
}


public class ConcretePrototype : ICloneable
{
private int id;
public int Id
{
get { return id; }
}
public ConcretePrototype(int id)
{
this.id = id;
}
public Object Clone()
{
return new ConcretePrototype(id);
}







}


class Program
{
static void Main(string[] args)
{
ConcretePrototype cp1 = new ConcretePrototype(1);
ConcretePrototype cp2 = (ConcretePrototype)cp1.Clone();
Console.WriteLine("cp1的Id为:{0}",cp1.Id);
Console.WriteLine("cp2的Id为:{0}", cp2.Id);
Console.ReadKey();
}
}

运行结果:

1
2
cp1的Id为:1
cp2的Id为:1

原型模式通常适用于以下场景:

  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 对象的创建过程比较麻烦,但复制比较简单的时候。

原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 Dictionary(或HashMap) 保存多个复制的原型,Client 类可以通过管理器的 Get(String id) 方法从中获取复制的原型。其结构如图所示: