一、引言
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 /// <summary> 2 /// 单例模式的实现 3 /// </summary> 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 /// <summary> 18 /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 19 /// </summary> 20 /// <returns></returns> 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 /// <summary> 2 /// 下面以不同系列房屋的建造为例子演示抽象工厂模式 3 /// 因为每个人的喜好不一样,我喜欢欧式的,我弟弟就喜欢现代的 4 /// 客户端调用 5 /// </summary> 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 /// <summary> 29 /// 抽象工厂类,提供创建不同类型房子的接口 30 /// </summary> 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 /// <summary> 41 /// 欧式风格房子的工厂,负责创建欧式风格的房子 42 /// </summary> 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 /// <summary> 71 /// 现在风格房子的工厂,负责创建现代风格的房子 72 /// </summary> 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 /// <summary> 101 /// 房顶抽象类,子类的房顶必须继承该类 102 /// </summary> 103 public abstract class Roof 104 { 105 /// <summary> 106 /// 创建房顶 107 /// </summary> 108 public abstract void Create(); 109 } 110 111 /// <summary> 112 /// 地板抽象类,子类的地板必须继承该类 113 /// </summary> 114 public abstract class Floor 115 { 116 /// <summary> 117 /// 创建地板 118 /// </summary> 119 public abstract void Create(); 120 } 121 122 /// <summary> 123 /// 窗户抽象类,子类的窗户必须继承该类 124 /// </summary> 125 public abstract class Window 126 { 127 /// <summary> 128 /// 创建窗户 129 /// </summary> 130 public abstract void Create(); 131 } 132 133 /// <summary> 134 /// 房门抽象类,子类的房门必须继承该类 135 /// </summary> 136 public abstract class Door 137 { 138 /// <summary> 139 /// 创建房门 140 /// </summary> 141 public abstract void Create(); 142 } 143 144 /// <summary> 145 /// 欧式地板类 146 /// </summary> 147 public class EuropeanFloor : Floor 148 { 149 public override void Create() 150 { 151 Console.WriteLine("创建欧式的地板"); 152 } 153 } 154 155 156 /// <summary> 157 /// 欧式的房顶 158 /// </summary> 159 public class EuropeanRoof : Roof 160 { 161 public override void Create() 162 { 163 Console.WriteLine("创建欧式的房顶"); 164 } 165 } 166 167 168 /// <summary> 169 ///欧式的窗户 170 /// </summary> 171 public class EuropeanWindow : Window 172 { 173 public override void Create() 174 { 175 Console.WriteLine("创建欧式的窗户"); 176 } 177 } 178 179 180 /// <summary> 181 /// 欧式的房门 182 /// </summary> 183 public class EuropeanDoor : Door 184 { 185 public override void Create() 186 { 187 Console.WriteLine("创建欧式的房门"); 188 } 189 } 190 191 /// <summary> 192 /// 现代的房顶 193 /// </summary> 194 public class ModernizationRoof : Roof 195 { 196 public override void Create() 197 { 198 Console.WriteLine("创建现代的房顶"); 199 } 200 } 201 202 /// <summary> 203 /// 现代的地板 204 /// </summary> 205 public class ModernizationFloor : Floor 206 { 207 public override void Create() 208 { 209 Console.WriteLine("创建现代的地板"); 210 } 211 } 212 213 /// <summary> 214 /// 现代的窗户 215 /// </summary> 216 public class ModernizationWindow : Window 217 { 218 public override void Create() 219 { 220 Console.WriteLine("创建现代的窗户"); 221 } 222 } 223 224 /// <summary> 225 /// 现代的房门 226 /// </summary> 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 /// <summary> 4 /// 该抽象类就是抽象接口的定义,该类型就相当于是Abstraction类型 5 /// </summary> 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 /// <summary> 22 /// 该抽象类就是实现接口的定义,该类型就相当于是Implementor类型 23 /// </summary> 24 public abstract class PlatformImplementor 25 { 26 //该方法就相当于Implementor类型的OperationImpl方法 27 public abstract void Process(); 28 } 29 30 /// <summary> 31 /// SqlServer2000版本的数据库,相当于RefinedAbstraction类型 32 /// </summary> 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 /// <summary> 45 /// SqlServer2005版本的数据库,相当于RefinedAbstraction类型 46 /// </summary> 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 /// <summary> 59 /// SqlServer2000版本的数据库针对Unix操作系统具体的实现,相当于ConcreteImplementorA类型 60 /// </summary> 61 public class SqlServer2000UnixImplementor : PlatformImplementor 62 { 63 public override void Process() 64 { 65 Console.WriteLine("SqlServer2000针对Unix的具体实现"); 66 } 67 } 68 69 /// <summary> 70 /// SqlServer2005版本的数据库针对Unix操作系统的具体实现,相当于ConcreteImplementorB类型 71 /// </summary> 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 /// <summary> 4 /// 该抽象类就是房子抽象接口的定义,该类型就相当于是Component类型,是饺子馅,需要装饰的,需要包装的 5 /// </summary> 6 public abstract class House 7 { 8 //房子的装修方法--该操作相当于Component类型的Operation方法 9 public abstract void Renovation(); 10 } 11 12 /// <summary> 13 /// 该抽象类就是装饰接口的定义,该类型就相当于是Decorator类型,如果需要具体的功能,可以子类化该类型 14 /// </summary> 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 /// <summary> 38 /// PatrickLiu的房子,我要按我的要求做房子,相当于ConcreteComponent类型,这就是我们具体的饺子馅,我个人比较喜欢韭菜馅 39 /// </summary> 40 public sealed class PatrickLiuHouse:House 41 { 42 public override void Renovation() 43 { 44 Console.WriteLine("装修PatrickLiu的房子"); 45 } 46 } 47 48 49 /// <summary> 50 /// 具有安全功能的设备,可以提供监视和报警功能,相当于ConcreteDecoratorA类型 51 /// </summary> 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 /// <summary> 64 /// 具有保温接口的材料,提供保温功能,相当于ConcreteDecoratorB类型 65 /// </summary> 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 /// <summary> 4 /// 俗话说:“好吃不如饺子,舒服不如倒着”。今天奶奶发话要吃他大孙子和孙媳妇包的饺子。今天还拿吃饺子这件事来说说命令模式的实现吧。 5 /// </summary> 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 /// <summary> 4 /// 俗话说:“好吃不如饺子,舒服不如倒着”。今天奶奶发话要吃他大孙子和孙媳妇包的饺子。今天还拿吃饺子这件事来说说命令模式的实现吧。 5 /// </summary> 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<ContactPerson> _personList; 18 19 20 public List<ContactPerson> 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<ContactPerson> 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<ContactPerson>(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<ContactPerson> ContactPersonListBack { get; private set; } 72 73 public ContactPersonMemento(List<ContactPerson> 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<ContactPerson> persons = new List<ContactPerson>() 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种面向对象的设计模式,终于写完了,今天是一个总结性的文章。各个模式都列了出来,每个模式的【动机】、【意图】和【结构图】也写了出来,可以方便大家查看。重新自己写了一遍,感觉很多都不一样了。理解更深刻了,学无止境,继续前进吧。