0%

一、引言

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

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

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

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

系列导航:

创建型:
    C#设计模式(1)——单例模式(Singleton Pattern)
    C#设计模式(2)——工厂方法模式(Factory Pattern)
    C#设计模式(3)——抽象工厂模式(Abstract Pattern)
    C#设计模式(4)——建造者模式(Builder Pattern)
    C#设计模式(5)——原型模式(Prototype Pattern)

结构型:
    C#设计模式(6)——适配器模式(Adapter Pattern)
    C#设计模式(7)——桥接模式(Bridge Pattern)
    C#设计模式(8)——装饰者模式(Decorator Pattern)
    C#设计模式(9)——组合模式(Composite Pattern)
    C#设计模式(10)——外观模式(Facade Pattern)
    C#设计模式(11)——享元模式(Flyweight Pattern)
    C#设计模式(12)——代理模式(Proxy Pattern)

行为型:
    C#设计模式(13)——模板方法模式(Template Method)
    C#设计模式(14)——命令模式(Command Pattern)
    C#设计模式(15)——迭代器模式(Iterator Pattern)
    C#设计模式(16)——观察者模式(Observer Pattern)
    C#设计模式(17)——中介者模式(Mediator Pattern)
    C#设计模式(18)——状态模式(State Pattern)
    C#设计模式(19)——策略模式(Stragety Pattern)
    C#设计模式(20)——责任链模式(Chain of Responsibility Pattern)
    C#设计模式(21)——访问者模式(Vistor Pattern)
    C#设计模式(22)——备忘录模式(Memento Pattern)
    C#设计模式(23)——解释器模式(Interpreter Pattern)

二、 面向对象的设计原则

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

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

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

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

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

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

   2.2、开放关闭原则(OCP)

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

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

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

   2.3、里氏代替原则(LSP)

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

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

   2.4、依赖倒置原则(DIP)

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

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

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

   2.5 接口隔离原则(ISP)

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

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

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

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

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

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

   2.6 组合复用原则(CRP)

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

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

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

   2.7 迪米特法则(Law of Demeter)

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

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

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

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

三、创建型模式

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

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

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

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

       具体结构图如下所示
      

       示例代码:

复制代码

复制代码

1 ///


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

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

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

复制代码

复制代码

   

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

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

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

       具体结构图如下所示:

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

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

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

       具体结构图如下所示

           

       代码实例

复制代码

复制代码

1 ///


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

复制代码

复制代码

   

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

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

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

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

       具体结构图如下所示

                

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

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

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

       具体结构图如下所示:

四、结构型模式

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

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

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

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

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

       具体结构图如下所示:

       类适配器模式:

       对象适配器模式:

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

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

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

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

       具体结构图如下所示

          

       示例代码:

复制代码

复制代码

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


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

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

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

33 public class SqlServer2000:Database 34 { 35 public override void Create() 36 { 37 this._implementor.Process(); 38 } 39 } 40
41 ///
42 /// SqlServer2005版本的数据库,相当于RefinedAbstraction类型 43 ///

44 public class SqlServer2005:Database 45 { 46 public override void Create() 47 { 48 this._implementor.Process(); 49 } 50 } 51
52 ///
53 /// SqlServer2000版本的数据库针对Unix操作系统具体的实现,相当于ConcreteImplementorA类型 54 ///

55 public class SqlServer2000UnixImplementor:PlatformImplementor 56 { 57 public override void Process() 58 { 59 //SqlServer2000针对Unix的具体实现;
60 } 61 } 62
63 ///
64 /// SqlServer2005版本的数据库针对Unix操作系统的具体实现,相当于ConcreteImplementorB类型 65 ///

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

复制代码

复制代码

   

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

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

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

       具体结构图如下所示

              

       示例代码:

复制代码

复制代码

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


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

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

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

34 public class PatrickLiuHouse:House 35 { 36 public override void Renovation() 37 { 38 Console.WriteLine(“PatrickLiu的房子”); 39 } 40 } 41
42
43 ///
44 /// 具有安全功能的设备,可以提供监视和报警功能,相当于ConcreteDecoratorA类型 45 ///

46 public class HouseSecurityDecorator:DecorationStrategy 47 { 48 public override void Process() 49 { 50 Console.WriteLine(“增加安全系统”); 51 } 52 } 53
54 ///
55 /// 具有保温接口的材料,提供保温功能,相当于ConcreteDecoratorB类型 56 ///

57 public sealed class KeepWarmDecorator:DecorationStrategy 58 { 59 public override void Process() 60 { 61 Console.WriteLine(“增加保温的功能”); 62 } 63 } 64
65 public class Program 66 { 67 static void Main() 68 { 69 //这就是我们的饺子馅,需要装饰的房子
70 House myselfHouse=new PatrickLiuHouse(); 71
72 DecorationStrategy securityHouse=new HouseSecurityDecorator(myselfHouse); 73 securityHouse.Process(); 74 //房子就有了安全系统了 75
76 //如果我既要安全系统又要保暖呢,继续装饰就行
77 DecorationStrategy securityAndWarmHouse=new HouseSecurityDecorator(securityHouse); 78 securityAndWarmHouse.Process(); 79 } 80 } 81 }

复制代码

复制代码

   

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

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

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

       具体结构图如下所示:

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

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

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

       具体结构图如下所示:

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

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

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

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

       具体结构图如下所示

           

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

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

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

       具体结构图如下所示:

             

五、行为型模式

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

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

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

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

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

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

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

       具体结构图如下所示

             

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

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

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

       具体结构图如下所示

            

       代码实例

复制代码

复制代码

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


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

6 class Client 7 {
8 static void Main(string[] args)
9 { 10 //奶奶想吃猪肉大葱馅的饺子
11 PatrickLiuAndWife liuAndLai=new PatrickLiuAndWife();//命令接受者
12 Command command=new MakeDumplingsCommand(liuAndLai);//命令
13 PaPaInvoker papa=new PaPaInvoker(command); //命令请求者 14
15 //奶奶发布命令
16 papa.MakeDumplings(); 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 ConcreteCommand(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 override 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) 18 {
19 this.financial=financial;
20 }
21 public void SetDevelopment(development) 22 {
23 this.development=development;
24 }
25 public SetMarket(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 System.Console.WriteLine(“我们是开发部门,要进行项目开发,没钱了,需要资金支持!”);
71 }
72
73 public override void Apply() 74 {
75 System.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 System.Console.WriteLine(“汇报工作!没钱了,钱太多了!怎么花?”);
87 }
88
89 public override void Apply() 90 {
91 System.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 System.Console.WriteLine(“汇报工作!项目承接的进度,需要资金支持!”); 103 GetMediator().Command(this); 104 } 105
106 public override void Apply() 107 { 108 System.Console.WriteLine(“跑去接项目!”); 109 } 110 } 111
112
113 public class Client 114 { 115 public 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.WriteLine(); 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.WriteLine(); 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 App(ShapeVisitor visitor) 92 {
93 this._visitor=visitor;
94 }
95
96 public void Process(Shape shape) 97 {
98 shape.Accept(_visitor)
99 } 100 } 101
102 class Program 103 { 104 static void Main(string[] args) 105 { 106 //如果想执行新增加的操作
107 ShapeVisitor visitor=new CustomVisitor(); 108 AppStructure app=new AppStructure(visitor); 109
110 Shape shape=new Rectangle(); 111 shape.Draw();//执行自己的操作
112 app.Process(shape);//执行新的操作
113
114
115 shape=new Circle(); 116 shape.Draw();//执行自己的操作
117 app.Process(shape);//执行新的操作
118
119
120 shape=new Line(); 121 shape.Draw();//执行自己的操作
122 app.Process(shape);//执行新的操作
123
124
125 Console.ReadLine(); 126 } 127 } 128 }

复制代码

复制代码

   

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

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

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

       具体的结构图如下所示

          

       代码实例

复制代码

复制代码

1 namespace MementoPattern 2 {
3 // 联系人–需要备份的数据,是状态数据,没有操作
4 public sealed class ContactPerson 5 {
6 //姓名
7 public string Name { get; set; }
8
9 //电话号码
10 public string MobileNumber { get; set; }
11 }
12
13 // 发起人–相当于【发起人角色】Originator
14 public sealed class MobileBackOriginator 15 {
16 // 发起人需要保存的内部状态
17 private List _personList; 18
19
20 public List ContactPersonList 21 {
22 get
23 {
24 return this._personList;
25 }
26
27 set
28 {
29 this._personList=value;
30 }
31 }
32 //初始化需要备份的电话名单
33 public MobileBackOriginator(List personList) 34 {
35 if(personList!=null)
36 {
37 this._personList = personList; 38 }
39 else
40 {
41 throw new ArgumentNullException(“参数不能为空!”);
42 }
43 }
44
45 // 创建备忘录对象实例,将当期要保存的联系人列表保存到备忘录对象中
46 public ContactPersonMemento CreateMemento() 47 {
48 return new ContactPersonMemento(new List(this._personList));
49 }
50
51 // 将备忘录中的数据备份还原到联系人列表中
52 public void RestoreMemento(ContactPersonMemento memento) 53 {
54 this.ContactPersonList = memento.ContactPersonListBack; 55 }
56
57 public void Show() 58 {
59 Console.WriteLine(“联系人列表中共有{0}个人,他们是:”, ContactPersonList.Count);
60 foreach (ContactPerson p in ContactPersonList) 61 {
62 Console.WriteLine(“姓名: {0} 号码: {1}”, p.Name, p.MobileNumber);
63 }
64 }
65 }
66
67 // 备忘录对象,用于保存状态数据,保存的是当时对象具体状态数据–相当于【备忘录角色】Memeto
68 public sealed class ContactPersonMemento 69 {
70 // 保存发起人创建的电话名单数据,就是所谓的状态
71 public List ContactPersonListBack{get;private set;};
72
73 public ContactMemento(List personList) 74 {
75 ContactPersonListBack = personList; 76 }
77 }
78
79 // 管理角色,它可以管理【备忘录】对象,如果是保存多个【备忘录】对象,当然可以对保存的对象进行增、删等管理处理—相当于【管理者角色】Caretaker
80 public sealed class MementoManager 81 {
82 //如果想保存多个【备忘录】对象,可以通过字典或者堆栈来保存,堆栈对象可以反映保存对象的先后顺序
83 //比如:public Dictionary<string, ContactPersonMemento> ContactPersonMementoDictionary { get; set; }
84 public ContactPersonMemento ContactPersonMemento{ get; set; }
85 }
86
87 class Program 88 {
89 static void Main(string[] args)
90 {
91 List persons = new List()
92 {
93 new ContactPerson() { Name=”黄飞鸿”, MobileNum = “13533332222”},
94 new ContactPerson() { Name=”方世玉”, MobileNum = “13966554433”},
95 new ContactPerson() { Name=”洪熙官”, MobileNum = “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种面向对象的设计模式,终于写完了,今天是一个总结性的文章。各个模式都列了出来,每个模式的【动机】、【意图】和【结构图】也写了出来,可以方便大家查看。重新自己写了一遍,感觉很多都不一样了。理解更深刻了,学无止境,继续前进吧。

一、引言

在软件开发过程中,我们经常会遇到处理简单对象和复合对象的情况,例如对操作系统中目录的处理就是这样的一个例子,因为目录可以包括单独的文件,也可以包括文件夹,文件夹又是由文件组成的,由于简单对象和复合对象在功能上区别,导致在操作过程中必须区分简单对象和复合对象,这样就会导致客户调用带来不必要的麻烦,然而作为客户,它们希望能够始终一致地对待简单对象和复合对象。然而组合模式就是解决这样的问题。下面让我们看看组合模式是怎样解决这个问题的。

二、组合模式的详细介绍

2.1 组合模式的定义

组合模式允许你将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。下面我们用绘制的例子来详细介绍组合模式,图形可以由一些基本图形元素组成(如直线,圆等),也可以由一些复杂图形组成(由基本图形元素组合而成),为了使客户对基本图形和复杂图形的调用保持一致,我们使用组合模式来达到整个目的。

组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

2.2 组合模式的实现

介绍完组合模式的定义之后,让我们以图形的例子来实现组合模式,具体代码如下:

复制代码

// 通过一些简单图形以及一些复杂图形构建图形树来演示组合模式 // 客户端调用
class Client
{ static void Main(string[] args)
{
ComplexGraphics complexGraphics = new ComplexGraphics(“一个复杂图形和两条线段组成的复杂图形”);
complexGraphics.Add(new Line(“线段A”));
ComplexGraphics CompositeCG = new ComplexGraphics(“一个圆和一条线组成的复杂图形”);
CompositeCG.Add(new Circle(“圆”));
CompositeCG.Add(new Circle(“线段B”));
complexGraphics.Add(CompositeCG);
Line l = new Line(“线段C”);
complexGraphics.Add(l); // 显示复杂图形的画法
Console.WriteLine(“复杂图形的绘制如下:”);
Console.WriteLine(“-——————–”);
complexGraphics.Draw();
Console.WriteLine(“复杂图形绘制完成”);
Console.WriteLine(“-——————–”);
Console.WriteLine(); // 移除一个组件再显示复杂图形的画法
complexGraphics.Remove(l);
Console.WriteLine(“移除线段C后,复杂图形的绘制如下:”);
Console.WriteLine(“-——————–”);
complexGraphics.Draw();
Console.WriteLine(“复杂图形绘制完成”);
Console.WriteLine(“-——————–”);
Console.Read();
}
} ///


/// 图形抽象类, ///

public abstract class Graphics
{ public string Name { get; set; } public Graphics(string name)
{ this.Name = name;
} public abstract void Draw(); public abstract void Add(Graphics g); public abstract void Remove(Graphics g);
} ///
/// 简单图形类——线 ///

public class Line : Graphics
{ public Line(string name)
: base(name)
{ } // 重写父类抽象方法
public override void Draw()
{
Console.WriteLine(“画 “ + Name);
} // 因为简单图形在添加或移除其他图形,所以简单图形Add或Remove方法没有任何意义 // 如果客户端调用了简单图形的Add或Remove方法将会在运行时抛出异常 // 我们可以在客户端捕获该类移除并处理
public override void Add(Graphics g)
{ throw new Exception(“不能向简单图形Line添加其他图形”);
} public override void Remove(Graphics g)
{ throw new Exception(“不能向简单图形Line移除其他图形”);
}
} ///
/// 简单图形类——圆 ///

public class Circle : Graphics
{ public Circle(string name)
: base(name)
{ } // 重写父类抽象方法
public override void Draw()
{
Console.WriteLine(“画 “ + Name);
} public override void Add(Graphics g)
{ throw new Exception(“不能向简单图形Circle添加其他图形”);
} public override void Remove(Graphics g)
{ throw new Exception(“不能向简单图形Circle移除其他图形”);
}
} ///
/// 复杂图形,由一些简单图形组成,这里假设该复杂图形由一个圆两条线组成的复杂图形 ///

public class ComplexGraphics : Graphics
{ private List complexGraphicsList = new List(); public ComplexGraphics(string name)
: base(name)
{ } ///
/// 复杂图形的画法 ///

public override void Draw()
{ foreach (Graphics g in complexGraphicsList)
{
g.Draw();
}
} public override void Add(Graphics g)
{
complexGraphicsList.Add(g);
} public override void Remove(Graphics g)
{
complexGraphicsList.Remove(g);
}
}

复制代码

由于基本图形对象不存在Add和Remove方法,上面实现中直接通过抛出一个异常的方式来解决这样的问题的,但是我们想以一种更安全的方式来解决——因为基本图形根本不存在这样的方法,我们是不是可以移除这些方法呢?为了移除这些方法,我们就不得不修改Graphics接口,我们把管理子对象的方法声明放在复合图形对象里面,这样简单对象Line、Circle使用这些方法时在编译时就会出错,这样的一种实现方式我们称为安全式的组合模式,然而上面的实现方式称为透明式的组合模式,下面让我们看看安全式的组合模式又是怎样实现的,具体实现代码如下:

复制代码

/// 安全式的组合模式 /// 此方式实现的组合模式把管理子对象的方法声明在树枝构件ComplexGraphics类中 /// 这样如果叶子节点Line、Circle使用了Add或Remove方法时,就能在编译期间出现错误 /// 但这种方式虽然解决了透明式组合模式的问题,但是它使得叶子节点和树枝构件具有不一样的接口。 /// 所以这两种方式实现的组合模式各有优缺点,具体使用哪个,可以根据问题的实际情况而定
class Client
{ static void Main(string[] args)
{
ComplexGraphics complexGraphics = new ComplexGraphics(“一个复杂图形和两条线段组成的复杂图形”);
complexGraphics.Add(new Line(“线段A”));
ComplexGraphics CompositeCG = new ComplexGraphics(“一个圆和一条线组成的复杂图形”);
CompositeCG.Add(new Circle(“圆”));
CompositeCG.Add(new Circle(“线段B”));
complexGraphics.Add(CompositeCG);
Line l = new Line(“线段C”);
complexGraphics.Add(l); // 显示复杂图形的画法
Console.WriteLine(“复杂图形的绘制如下:”);
Console.WriteLine(“-——————–”);
complexGraphics.Draw();
Console.WriteLine(“复杂图形绘制完成”);
Console.WriteLine(“-——————–”);
Console.WriteLine(); // 移除一个组件再显示复杂图形的画法
complexGraphics.Remove(l);
Console.WriteLine(“移除线段C后,复杂图形的绘制如下:”);
Console.WriteLine(“-——————–”);
complexGraphics.Draw();
Console.WriteLine(“复杂图形绘制完成”);
Console.WriteLine(“-——————–”);
Console.Read();
}
} ///


/// 图形抽象类, ///

public abstract class Graphics
{ public string Name { get; set; } public Graphics(string name)
{ this.Name = name;
} public abstract void Draw(); // 移除了Add和Remove方法 // 把管理子对象的方法放到了ComplexGraphics类中进行管理 // 因为这些方法只在复杂图形中才有意义
} ///
/// 简单图形类——线 ///

public class Line : Graphics
{ public Line(string name)
: base(name)
{ } // 重写父类抽象方法
public override void Draw()
{
Console.WriteLine(“画 “ + Name);
}
} ///
/// 简单图形类——圆 ///

public class Circle : Graphics
{ public Circle(string name)
: base(name)
{ } // 重写父类抽象方法
public override void Draw()
{
Console.WriteLine(“画 “ + Name);
}
} ///
/// 复杂图形,由一些简单图形组成,这里假设该复杂图形由一个圆两条线组成的复杂图形 ///

public class ComplexGraphics : Graphics
{ private List complexGraphicsList = new List(); public ComplexGraphics(string name)
: base(name)
{ } ///
/// 复杂图形的画法 ///

public override void Draw()
{ foreach (Graphics g in complexGraphicsList)
{
g.Draw();
}
} public void Add(Graphics g)
{
complexGraphicsList.Add(g);
} public void Remove(Graphics g)
{
complexGraphicsList.Remove(g);
}
}

复制代码

2.3 组合模式的类图

看完了上面两者方式的实现之后,让我们具体看看组合模式的类图来理清楚组合模式中类之间的关系。

透明式的组合模式类图:

安全式组合模式的类图:

组合模式中涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,上面实现中Graphics充当这个角色,它给参加组合的对象定义出了公共的接口及默认行为,可以用来管理所有的子对象(在透明式的组合模式是这样的)。在安全式的组合模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
  • 树叶构件(Leaf)角色:树叶对象时没有下级子对象的对象,上面实现中Line和Circle充当这个角色,定义出参加组合的原始对象的行为
  • 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象,上面实现中ComplexGraphics充当这个角色,树枝对象给出所有管理子对象的方法实现,如Add、Remove等。

三、组合模式的优缺点

优点:

  1. 组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
  2. 将”客户代码与复杂的对象容器结构“解耦。
  3. 可以更容易地往组合对象中加入新的构件。

缺点:使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。

注意的问题:

  1. 有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结构存储在父构件里面作为缓存。
  2. 客户端尽量不要直接调用树叶类中的方法(在我上面实现就是这样的,创建的是一个树枝的具体对象,应该使用Graphics complexGraphics = new ComplexGraphics(“一个复杂图形和两条线段组成的复杂图形”);),而是借用其父类(Graphics)的多态性完成调用,这样可以增加代码的复用性。

四、组合模式的使用场景

在以下情况下应该考虑使用组合模式:

  1. 需要表示一个对象整体或部分的层次结构。
  2. 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

五、组合模式在.NET中的应用

组合模式在.NET 中最典型的应用就是应用与WinForms和Web的开发中,在.NET类库中,都为这两个平台提供了很多现有的控件,然而System.Windows.Forms.dll中System.Windows.Forms.Control类就应用了组合模式,因为控件包括Label、TextBox等这样的简单控件,同时也包括GroupBox、DataGrid这样复合的控件,每个控件都需要调用OnPaint方法来进行控件显示,为了表示这种对象之间整体与部分的层次结构,微软把Control类的实现应用了组合模式(确切地说应用了透明式的组合模式)。

六、总结

到这里组合模式的介绍就结束了,组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。

本文中所有源码:设计模式之组合模式

一、引言

在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ”门面“模式。下面就具体介绍下外观模式。

二、外观模式的详细介绍

2.1 定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。使用外观模式时,我们创建了一个统一的类,用来包装子系统中一个或多个复杂的类,客户端可以直接通过外观类来调用内部子系统中方法,从而外观模式让客户和子系统之间避免了紧耦合。

2.2 外观模式实现

介绍了外观模式的定义之后,让我们具体看看外观模式的由来以及实现,下面与学校中一个选课系统为例来解释外观模式,例如在选课系统中,有注册课程子系统和通知子系统,在不使用外观模式的情况下,客户端必须同时保存注册课程子系统和通知子系统两个引用,如果后期这两个子系统发生改变时,此时客户端的调用代码也要随之改变,这样就没有很好的可扩展性,下面看看不使用外观模式下选课系统的实现方式和客户端调用代码:

复制代码

///


/// 不使用外观模式的情况 /// 此时客户端与三个子系统都发送了耦合,使得客户端程序依赖与子系统 /// 为了解决这样的问题,我们可以使用外观模式来为所有子系统设计一个统一的接口 /// 客户端只需要调用外观类中的方法就可以了,简化了客户端的操作 /// 从而让客户和子系统之间避免了紧耦合 ///

class Client
{ static void Main(string[] args)
{
SubSystemA a = new SubSystemA();
SubSystemB b = new SubSystemB();
SubSystemC c = new SubSystemC();
a.MethodA();
b.MethodB();
c.MethodC();
Console.Read();
}
} // 子系统A
public class SubSystemA
{ public void MethodA()
{
Console.WriteLine(“执行子系统A中的方法A”);
}
} // 子系统B
public class SubSystemB
{ public void MethodB()
{
Console.WriteLine(“执行子系统B中的方法B”);
}
} // 子系统C
public class SubSystemC
{ public void MethodC()
{
Console.WriteLine(“执行子系统C中的方法C”);
}
}

复制代码

然而外观模式可以解决我们上面所说的问题,下面具体看看使用外观模式的实现:

复制代码

///


/// 以学生选课系统为例子演示外观模式的使用 /// 学生选课模块包括功能有: /// 验证选课的人数是否已满 /// 通知用户课程选择成功与否 /// 客户端代码 ///

class Student
{ private static RegistrationFacade facade = new RegistrationFacade(); static void Main(string[] args)
{ if (facade.RegisterCourse(“设计模式”, “Learning Hard”))
{
Console.WriteLine(“选课成功”);
} else {
Console.WriteLine(“选课失败”);
}

        Console.Read();
    }
} // 外观类
public class RegistrationFacade
{ private RegisterCourse registerCourse; private NotifyStudent notifyStu; public RegistrationFacade()
    {
        registerCourse \= new RegisterCourse();
        notifyStu \= new NotifyStudent();
    } public bool RegisterCourse(string courseName, string studentName)
    { if (!registerCourse.CheckAvailable(courseName))
        { return false;
        } return notifyStu.Notify(studentName);
    }
} #region 子系统
// 相当于子系统A
public class RegisterCourse
{ public bool CheckAvailable(string courseName)
    {
        Console.WriteLine("正在验证课程 {0}是否人数已满", courseName); return true;
    }
} // 相当于子系统B
public class NotifyStudent
{ public bool Notify(string studentName)
    {
        Console.WriteLine("正在向{0}发生通知", studentName); return true;
    }
} #endregion

复制代码

使用了外观模式之后,客户端只依赖与外观类,从而将客户端与子系统的依赖解耦了,如果子系统发生改变,此时客户端的代码并不需要去改变。外观模式的实现核心主要是——由外观类去保存各个子系统的引用,实现由一个统一的外观类去包装多个子系统类,然而客户端只需要引用这个外观类,然后由外观类来调用各个子系统中的方法。然而这样的实现方式非常类似适配器模式,然而外观模式与适配器模式不同的是:适配器模式是将一个对象包装起来以改变其接口,而外观是将一群对象 ”包装“起来以简化其接口。****它们的意图是不一样的,适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口

2.3 外观模式的结构

看完外观模式的实现之后,为了帮助理清外观模式中类之间的关系,下面给出上面实现代码中类图:

然而对于外观模式而言,是没有一个一般化的类图描述,下面演示一个外观模式的示意性对象图来加深大家对外观模式的理解:

在上面的对象图中有两个角色:

门面(Facade)角色:客户端调用这个角色的方法。该角色知道相关的一个或多个子系统的功能和责任,该角色会将从客户端发来的请求委派带相应的子系统中去。

子系统(subsystem)角色:可以同时包含一个或多个子系统。每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用或被门面角色调用。对于子系统而言,门面仅仅是另外一个客户端,子系统并不知道门面的存在。

三、外观的优缺点

优点:

  1. 外观模式对客户屏蔽了子系统组件,从而简化了接口,减少了客户处理的对象数目并使子系统的使用更加简单。
  2. 外观模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件是紧耦合的。松耦合使得子系统的组件变化不会影响到它的客户。

缺点:

  1. 如果增加新的子系统可能需要修改外观类或客户端的源代码,这样就违背了”开——闭原则“(不过这点也是不可避免)。

四、使用场景

 在以下情况下可以考虑使用外观模式:

  • 外一个复杂的子系统提供一个简单的接口
  • 提供子系统的独立性
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口。其中三层架构就是这样的一个例子

五、总结

到这里外观模式的介绍就结束了,外观模式,为子系统的一组接口提供一个统一的接口,该模式定义了一个高层接口,这一个高层接口使的子系统更加容易使用。并且外观模式可以解决层结构分离、降低系统耦合度和为新旧系统交互提供接口功能。

本文所有源码:设计模式之外观模式

一、引言

在软件开发过程,如果我们需要重复使用某个对象的时候,如果我们重复地使用new创建这个对象的话,这样我们在内存就需要多次地去申请内存空间了,这样可能会出现内存使用越来越多的情况,这样的问题是非常严重,然而享元模式可以解决这个问题,下面具体看看享元模式是如何去解决这个问题的。

二、享元模式的详细介绍

在前面说了,享元模式可以解决上面的问题了,在介绍享元模式之前,让我们先要分析下如果去解决上面那个问题,上面的问题就是重复创建了同一个对象,如果让我们去解决这个问题肯定会这样想:“既然都是同一个对象,能不能只创建一个对象,然后下次需要创建这个对象的时候,让它直接用已经创建好了的对象就好了”,也就是说——让一个对象共享。不错,这个也是享元模式的实现精髓所在。

2.1 定义

介绍完享元模式的精髓之后,让我们具体看看享元模式的正式定义:

享元模式——运用共享技术有效地支持大量细粒度的对象。享元模式可以避免大量相似类的开销,在软件开发中如果需要生成大量细粒度的类实例来表示数据,如果这些实例除了几个参数外基本上都是相同的,这时候就可以使用享元模式来大幅度减少需要实例化类的数量。如果能把这些参数(指的这些类实例不同的参数)移动类实例外面,在方法调用时将他们传递进来,这样就可以通过共享大幅度地减少单个实例的数目。(这个也是享元模式的实现要领),然而我们把类实例外面的参数称为享元对象的外部状态,把在享元对象内部定义称为内部状态。具体享元对象的内部状态与外部状态的定义为:

内部状态:在享元对象的内部并且不会随着环境的改变而改变的共享部分

外部状态:随环境改变而改变的,不可以共享的状态。

2.2 享元模式实现

分析完享元模式的实现思路之后,相信大家实现享元模式肯定没什么问题了,下面以一个实际的应用来实现下享元模式。这个例子是:一个文本编辑器中会出现很多字面,使用享元模式去实现这个文本编辑器的话,会把每个字面做成一个享元对象。享元对象的内部状态就是这个字面,而字母在文本中的位置和字体风格等其他信息就是它的外部状态。下面就以这个例子来实现下享元模式,具体实现代码如下:

复制代码

///


/// 客户端调用 ///

class Client
{ static void Main(string[] args)
{ // 定义外部状态,例如字母的位置等信息
int externalstate = 10; // 初始化享元工厂
FlyweightFactory factory = new FlyweightFactory(); // 判断是否已经创建了字母A,如果已经创建就直接使用创建的对象A
Flyweight fa = factory.GetFlyweight(“A”); if (fa != null)
{ // 把外部状态作为享元对象的方法调用参数
fa.Operation(–externalstate);
} // 判断是否已经创建了字母B
Flyweight fb = factory.GetFlyweight(“B”); if (fb != null)
{
fb.Operation(--externalstate);
} // 判断是否已经创建了字母C
Flyweight fc = factory.GetFlyweight(“C”); if (fc != null)
{
fc.Operation(--externalstate);
} // 判断是否已经创建了字母D
Flyweight fd= factory.GetFlyweight(“D”); if (fd != null)
{
fd.Operation(--externalstate);
} else {
Console.WriteLine(“驻留池中不存在字符串D”); // 这时候就需要创建一个对象并放入驻留池中
ConcreteFlyweight d = new ConcreteFlyweight(“D”);
factory.flyweights.Add(“D”, d);
}

        Console.Read();
    }
} /// <summary>
/// 享元工厂,负责创建和管理享元对象 /// </summary>
public class FlyweightFactory
{ // 最好使用泛型Dictionary<string,Flyweighy> //public Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
    public Hashtable flyweights = new Hashtable(); public FlyweightFactory()
    {
        flyweights.Add("A", new ConcreteFlyweight("A"));
        flyweights.Add("B", new ConcreteFlyweight("B"));
        flyweights.Add("C", new ConcreteFlyweight("C"));
    } public Flyweight GetFlyweight(string key)
    {

// 更好的实现如下
//Flyweight flyweight = flyweights[key] as Flyweight;
//if (flyweight == null)
//{
// Console.WriteLine(“驻留池中不存在字符串” + key);
// flyweight = new ConcreteFlyweight(key);
//}
//return flyweight;

return flyweights[key] as Flyweight;
}
} ///


/// 抽象享元类,提供具体享元类具有的方法 ///

public abstract class Flyweight
{ public abstract void Operation(int extrinsicstate);
} // 具体的享元对象,这样我们不把每个字母设计成一个单独的类了,而是作为把共享的字母作为享元对象的内部状态
public class ConcreteFlyweight : Flyweight
{ // 内部状态
private string intrinsicstate ; // 构造函数
public ConcreteFlyweight(string innerState)
{ this.intrinsicstate = innerState;
} ///
/// 享元类的实例方法 ///

/// 外部状态
public override void Operation(int extrinsicstate)
{
Console.WriteLine(“具体实现类: intrinsicstate {0}, extrinsicstate {1}”, intrinsicstate, extrinsicstate);
}
}

复制代码

在享元模式的实现中,我们没有像之前一样,把一个细粒度的类实例设计成一个单独的类,而是把它作为共享对象的内部状态放在共享类的内部定义,具体的解释注释中都有了,大家可以参考注释去进一步理解享元模式。

2.3 享元模式的类图

看完享元模式的实现之后,为了帮助大家理清楚享元模式中各类之间的关系,下面给出上面实现代码中的类图,如下所示:

(摘自http://www.cnblogs.com/zhenyulu/articles/55793.html

在上图中,涉及的角色如下几种角色:

抽象享元角色(Flyweight):此角色是所有的具体享元类的基类,为这些类规定出需要实现的公共接口。那些需要外部状态的操作可以通过调用方法以参数形式传入。

具体享元角色(ConcreteFlyweight):实现抽象享元角色所规定的接口。如果有内部状态的话,可以在类内部定义。

享元工厂角色(FlyweightFactory):本角色复杂创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享,当一个客户端对象调用一个享元对象的时候,享元工厂角色检查系统中是否已经有一个符合要求的享元对象,如果已经存在,享元工厂角色就提供已存在的享元对象,如果系统中没有一个符合的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

客户端角色(Client):本角色需要存储所有享元对象的外部状态。

注:上面的实现只是单纯的享元模式,同时还有复合的享元模式,由于复合享元模式较复杂,这里就不给出实现了。

三、享元模式的优缺点

分析完享元模式的实现之后,让我们继续分析下享元模式的优缺点:

优点:

  1. 降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点:

  1. 为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑更复杂,使系统复杂化。
  2. 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

四、使用场景

在下面所有条件都满足时,可以考虑使用享元模式:

  • 一个系统中有大量的对象;
  • 这些对象耗费大量的内存;
  • 这些对象中的状态大部分都可以被外部化
  • 这些对象可以按照内部状态分成很多的组,当把外部对象从对象中剔除时,每一个组都可以仅用一个对象代替
  • 软件系统不依赖这些对象的身份,

满足上面的条件的系统可以使用享元模式。但是使用享元模式需要额外维护一个记录子系统已有的所有享元的表,而这也需要耗费资源,所以,应当在有足够多的享元实例可共享时才值得使用享元模式。

注:在.NET类库中,string类的实现就使用了享元模式,更多内容可以参考字符串驻留池的介绍,同时也可以参考这个博文深入理解.NET中string类的设计——http://www.cnblogs.com/artech/archive/2010/11/25/internedstring.html

五、总结

到这里,享元模式的介绍就结束了,享元模式主要用来解决由于大量的细粒度对象所造成的内存开销的问题,它在实际的开发中并不常用,可以作为底层的提升性能的一种手段。

一、引言

在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代理对象代替目标对象,然后客户端只需要访问代理对象,由代理对象去帮我们去请求目标对象并返回结果给客户端,这样的一个解决思路就是今天要介绍的代理模式。

二、代理模式的详细介绍

代理模式按照使用目的可以分为以下几种:

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是本电脑中,也可以在另一台电脑中。最典型的例子就是——客户端调用Web服务或WCF服务。
  • 虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得对象只在需要时才会被真正创建。
  • Copy-on-Write代理:虚拟代理的一种,把复制(或者叫克隆)拖延到只有在客户端需要时,才真正采取行动。
  • 保护(Protect or Access)代理:控制一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
  • Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以这些结果。

在哦上面所有种类的代理模式中,虚拟代理、远程代理、智能引用代理和保护代理较为常见的代理模式。下面让我们具体看看代理模式的具体定义。

2.1 定义

代理模式——就是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。在一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。例如电脑桌面的快捷方式就是一个代理对象,快捷方式是它所引用的程序的一个代理

2.2 代理模式实现

看完代理模式的描述之后,下面以一个生活中的例子来解释下代理模式,在现实生活中,如果有同事出国或者朋友出国的情况下,我们经常会拖这位朋友帮忙带一些电子产品或化妆品等东西,这个场景中,出国的朋友就是一个代理,他(她)是他(她)朋友的一个代理,由于他朋友不能去国外买东西,他却可以,所以朋友们都托他帮忙带一些东西的。下面就以这个场景来实现下代理模式,具体代码如下:

复制代码

// 客户端调用
class Client
{ static void Main(string[] args)
{ // 创建一个代理对象并发出请求
Person proxy = new Friend();
proxy.BuyProduct();
Console.Read();
}
} // 抽象主题角色
public abstract class Person
{ public abstract void BuyProduct();
} //真实主题角色
public class RealBuyPerson : Person
{ public override void BuyProduct()
{
Console.WriteLine(“帮我买一个IPhone和一台苹果电脑”);
}
} // 代理角色
public class Friend:Person
{ // 引用真实主题实例
RealBuyPerson realSubject; public override void BuyProduct()
{
Console.WriteLine(“通过代理类访问真实实体对象的方法”); if (realSubject == null)
{
realSubject = new RealBuyPerson();
} this.PreBuyProduct(); // 调用真实主题方法
realSubject.BuyProduct(); this.PostBuyProduct();
} // 代理角色执行的一些操作
public void PreBuyProduct()
{ // 可能不知一个朋友叫这位朋友带东西,首先这位出国的朋友要对每一位朋友要带的东西列一个清单等
Console.WriteLine(“我怕弄糊涂了,需要列一张清单,张三:要带相机,李四:要带Iphone………..”);
} // 买完东西之后,代理角色需要针对每位朋友需要的对买来的东西进行分类
public void PostBuyProduct()
{
Console.WriteLine(“终于买完了,现在要对东西分一下,相机是张三的;Iphone是李四的……….”);
}
}

复制代码

在上面的代码中都有相应的注释,这里也不多解释了。

2.3 代理模式的类图结构

看完代理模式的实现之后,下面就以上面的例子来分析下代理模式的类图结构。具体的类图如下所示:

在上面类图中,代理模式所涉及的角色有三个:

抽象主题角色(Person):声明了真实主题和代理主题的公共接口,这样一来在使用真实主题的任何地方都可以使用代理主题。

代理主题角色(Friend):代理主题角色内部含有对真实主题的引用,从而可以操作真实主题对象;代理主题角色负责在需要的时候创建真实主题对象;代理角色通常在将客户端调用传递到真实主题之前或之后,都要执行一些其他的操作,而不是单纯地将调用传递给真实主题对象。例如这里的PreBuyProduct和PostBuyProduct方法就是代理主题角色所执行的其他操作。

真实主题角色(RealBuyPerson):定义了代理角色所代表的真是对象。

附:在实际开发过程中,我们在客户端添加服务引用的时候,在客户程序中会添加一些额外的类,在客户端生成的类扮演着代理主题角色,我们客户端也是直接调用这些代理角色来访问远程服务提供的操作。这个是远程代理的一个典型例子。

三、代理模式的优缺点

全面分析完代理模式之后,让我们看看这个模式的优缺点:

优点:

  1. 代理模式能够将调用用于真正被调用的对象隔离,在一定程度上降低了系统的耦合度;
  2. 代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。代理对象可以在对目标对象发出请求之前进行一个额外的操作,例如权限检查等。

缺点:

  1.  由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢
  2. 实现代理类也需要额外的工作,从而增加了系统的实现复杂度。

五、总结

到这里,代理模式的介绍就结束了,代理模式提供了对目标对象访问的代理。并且到这里,结构型模式的介绍也结束了,结构型模式包括:适配器模式桥接模式装饰者模式组合模式外观模式享元模式和代理模式,下面开始介绍行为型模式的第一个模式:模板方法模式。

  本专题所有源码:设计模式之代理模式源码

一、引言

提到模板,大家肯定不免想到生活中的“简历模板”、“论文模板”、“Word中模版文件”等,在现实生活中,模板的概念就是——有一个规定的格式,然后每个人都可以根据自己的需求或情况去更新它,例如简历模板,下载下来的简历模板的格式都是相同的,然而我们下载下来简历模板之后我们可以根据自己的情况填充不同的内容要完成属于自己的简历。在设计模式中,模板方法模式中模板和生活中模板概念非常类似,下面让我们就详细介绍模板方法的定义,大家可以根据生活中模板的概念来理解模板方法的定义。

二、模板方法模式详细介绍

2.1 模板方法模式的定义

模板方法模式——在一个抽象类中定义一个操作中的算法骨架(对应于生活中的大家下载的模板),而将一些步骤延迟到子类中去实现(对应于我们根据自己的情况向模板填充内容)。模板方法使得子类可以不改变一个算法的结构前提下,重新定义算法的某些特定步骤,模板方法模式把不变行为搬到超类中,从而去除了子类中的重复代码。

2.2 模板方法模式的实现

理解了模板方法的定义之后,自然实现模板方法也不是什么难事了,下面以生活中炒蔬菜为例来实现下模板方法模式。在现实生活中,做蔬菜的步骤都大致相同,如果我们针对每种蔬菜类定义一个烧的方法,这样在每个类中都有很多相同的代码,为了解决这个问题,我们一般的思路肯定是把相同的部分抽象出来到抽象类中去定义,具体子类来实现具体的不同部分,这个思路也正式模板方法的实现精髓所在,具体实现代码如下:

复制代码

// 客户端调用
class Client
{ static void Main(string[] args)
{ // 创建一个菠菜实例并调用模板方法
Spinach spinach = new Spinach();
spinach.CookVegetabel();
Console.Read();
}
} public abstract class Vegetabel
{ // 模板方法,不要把模版方法定义为Virtual或abstract方法,避免被子类重写,防止更改流程的执行顺序
public void CookVegetabel()
{
Console.WriteLine(“抄蔬菜的一般做法”); this.pourOil(); this.HeatOil(); this.pourVegetable(); this.stir_fry();
} // 第一步倒油
public void pourOil()
{
Console.WriteLine(“倒油”);
} // 把油烧热
public void HeatOil()
{
Console.WriteLine(“把油烧热”);
} // 油热了之后倒蔬菜下去,具体哪种蔬菜由子类决定
public abstract void pourVegetable(); // 开发翻炒蔬菜
public void stir_fry()
{
Console.WriteLine(“翻炒”);
}
} // 菠菜
public class Spinach : Vegetabel
{ public override void pourVegetable()
{
Console.WriteLine(“倒菠菜进锅中”);
}
} // 大白菜
public class ChineseCabbage : Vegetabel
{ public override void pourVegetable()
{
Console.WriteLine(“倒大白菜进锅中”);
}
}

复制代码

在上面的实现中,具体子类中重写了导入蔬菜种类的方法,因为这个真是烧菜方法中不同的地方,所以由具体子类去实现它。

2.3 模板方法模式的类图

实现完模板方法模式之后,让我们看看模板方法的类图结构,以理清该模式中类之间的关系,具体类图如下:

模板方法模式中涉及了两个角色:

  • 抽象模板角色(Vegetable扮演这个角色):定义了一个或多个抽象操作,以便让子类实现,这些抽象操作称为基本操作。
  • 具体模板角色(ChineseCabbage和Spinach扮演这个角色):实现父类所定义的一个或多个抽象方法。

三、模板方法模式的优缺点

下面让我们继续分析下模板方法的优缺点。

优点:

  1. 实现了代码复用
  2. 能够灵活应对子步骤的变化,符合开放-封闭原则

缺点:因为引入了一个抽象类,如果具体实现过多的话,需要用户或开发人员需要花更多的时间去理清类之间的关系。

 附:在.NET中模板方法的应用也很多,例如我们在开发自定义的Web控件或WinForm控件时,我们只需要重写某个控件的部分方法。

四、总结

到这里,模板方法的介绍就结束了,模板方法模式在抽象类中定义了算法的实现步骤,将这些步骤的实现延迟到具体子类中去实现,从而使所有子类复用了父类的代码,所以模板方法模式是基于继承的一种实现代码复用的技术。

C#模拟http 发送post或get请求 - Aaronguo - 博客园

Excerpt

转自: C#模拟http 发送post或get请求 在post的时候有时也用的到cookie,像登录163发邮件时候就需要发送cookie,所以在外部一个cookie属性随时保存 CookieContainer cookie = new CookieContainer(); !注意:有时候


转自: C#模拟http 发送post或get请求 

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<span> 1</span> <span>private</span> <span>string</span> HttpPost(<span>string</span> Url, <span>string</span><span> postDataStr)
</span><span> 2</span> <span> {
</span><span> 3</span> HttpWebRequest request =<span> (HttpWebRequest)WebRequest.Create(Url);
</span><span> 4</span> request.Method = <span>"</span><span>POST</span><span>"</span><span>;
</span><span> 5</span> request.ContentType = <span>"</span><span>application/x-www-form-urlencoded</span><span>"</span><span>;
</span><span> 6</span> request.ContentLength =<span> Encoding.UTF8.GetByteCount(postDataStr);
</span><span> 7</span> request.CookieContainer =<span> cookie;
</span><span> 8</span> Stream myRequestStream =<span> request.GetRequestStream();
</span><span> 9</span> StreamWriter myStreamWriter = <span>new</span> StreamWriter(myRequestStream, Encoding.GetEncoding(<span>"</span><span>gb2312</span><span>"</span><span>));
</span><span>10</span> <span> myStreamWriter.Write(postDataStr);
</span><span>11</span> <span> myStreamWriter.Close();
</span><span>12</span>
<span>13</span> HttpWebResponse response =<span> (HttpWebResponse)request.GetResponse();
</span><span>14</span>
<span>15</span> response.Cookies =<span> cookie.GetCookies(response.ResponseUri);
</span><span>16</span> Stream myResponseStream =<span> response.GetResponseStream();
</span><span>17</span> StreamReader myStreamReader = <span>new</span> StreamReader(myResponseStream, Encoding.GetEncoding(<span>"</span><span>utf-8</span><span>"</span><span>));
</span><span>18</span> <span>string</span> retString =<span> myStreamReader.ReadToEnd();
</span><span>19</span> <span> myStreamReader.Close();
</span><span>20</span> <span> myResponseStream.Close();
</span><span>21</span>
<span>22</span> <span>return</span><span> retString;
</span><span>23</span> <span> }
</span><span>24</span>
<span>25</span> <span>public</span> <span>string</span> HttpGet(<span>string</span> Url, <span>string</span><span> postDataStr)
</span><span>26</span> <span> {
</span><span>27</span> HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == <span>""</span> ? <span>""</span> : <span>"</span><span>?</span><span>"</span>) +<span> postDataStr);
</span><span>28</span> request.Method = <span>"</span><span>GET</span><span>"</span><span>;
</span><span>29</span> request.ContentType = <span>"</span><span>text/html;charset=UTF-8</span><span>"</span><span>;
</span><span>30</span>
<span>31</span> HttpWebResponse response =<span> (HttpWebResponse)request.GetResponse();
</span><span>32</span> Stream myResponseStream =<span> response.GetResponseStream();
</span><span>33</span> StreamReader myStreamReader = <span>new</span> StreamReader(myResponseStream, Encoding.GetEncoding(<span>"</span><span>utf-8</span><span>"</span><span>));
</span><span>34</span> <span>string</span> retString =<span> myStreamReader.ReadToEnd();
</span><span>35</span> <span> myStreamReader.Close();
</span><span>36</span> <span> myResponseStream.Close();
</span><span>37</span>
<span>38</span> <span>return</span><span> retString;
</span><span>39</span> }

复制代码

在post的时候有时也用的到cookie,像登录163发邮件时候就需要发送cookie,所以在外部一个cookie属性随时保存 CookieContainer cookie = new CookieContainer();

!注意:有时候请求会重定向,但我们就需要从重定向url获取东西,像QQ登录成功后获取sid,但上面的会自动根据重定向地址跳转。我们可以用:
request.AllowAutoRedirect = false;设置重定向禁用,你就可以从headers的Location属性中获取重定向地址

Post另一实现:

post 调用时传参,如:

string url = “http://www.baidu.com/“;
            string result = string.Empty;

            string param = string.Format(“WechatOpenID={0}&Content={1}”, webchatOpenID, content);
            result = HttpPostData(url, param);

public string HttpPostData(string url, string param)
        {
            var result = string.Empty;
            //注意提交的编码 这边是需要改变的 这边默认的是Default:系统当前编码
            byte[] postData = Encoding.UTF8.GetBytes(param);

            // 设置提交的相关参数 
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            Encoding myEncoding = Encoding.UTF8;
            request.Method = “POST”;
            request.KeepAlive = false;
            request.AllowAutoRedirect = true;
            request.ContentType = “application/x-www-form-urlencoded”;
            request.UserAgent = “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR  3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)”;
            request.ContentLength = postData.Length;

            // 提交请求数据 
            System.IO.Stream outputStream = request.GetRequestStream();
            outputStream.Write(postData, 0, postData.Length);
            outputStream.Close();

            HttpWebResponse response;
            Stream responseStream;
            StreamReader reader;
            string srcString;
            response = request.GetResponse() as HttpWebResponse;
            responseStream = response.GetResponseStream();
            reader = new System.IO.StreamReader(responseStream, Encoding.GetEncoding(“UTF-8”));
            srcString = reader.ReadToEnd();
            result = srcString;   //返回值赋值
            reader.Close();

            return result;
        }

Post传输图片等时的代码示例:

Post多张图片和其他参数时,用此方法进行调用,调用方法如:

//imgDic是图片接收名称如media,图片本地完整路径或网络完整路径的键值对的集合

imgDesc是图片信息参数及其他参数的集合,图片信息如media1,media2,media3等,这个图片接收名字是由接收方确定的

注意:

FileStream只能用于本地完整路径,如C/xx.jpg等,如果是网路图片则无法使用它。

推荐使用

WebClient wc = new WebClient();
            byte[] buffer = wc.DownloadData(filePath);
            memStream.Write(buffer, 0, buffer.Length);

这种方式,这个类无论是网路图片还是本地图片都可以使用,但路径中要用/,而不是\,否则会出现不支持URI的错误。

if (openIDList != null && openIDList.Any() && imgDesc != null)
            {
                Dictionary<string, string> imgDic = new Dictionary<string, string>();
                foreach (var item in imgDesc)
                {
                    imgDic.Add(item.name, item.path);
                }

                NameValueCollection col = new NameValueCollection();
                col.Add(“imgDesc”, Newtonsoft.Json.JsonConvert.SerializeObject(imgDesc));
                col.Add(“open_ids”, string.Join(“,”, openIDList));
                result = HttpPostData(url, 60000, imgDic, col);
            }

private static string HttpPostData(string url, int timeOut, Dictionary<string, string> imgDic, NameValueCollection stringDict)
        {
            LogEntry entry = new LogEntry(“发送图片开始-HttpPostData – “ + stringDict[“open_ids”], 1);
            LogStub.Log(entry);

            var firstImg = imgDic.FirstOrDefault();
            string fileKeyName = firstImg.Key;
            string filePath = firstImg.Value;

            string responseContent;
            var memStream = new MemoryStream();
            var webRequest = (HttpWebRequest)WebRequest.Create(url);
            // 边界符
            var boundary = “—————“ + DateTime.Now.Ticks.ToString(“x”);
            // 边界符
            var beginBoundary = Encoding.ASCII.GetBytes(“–” + boundary + “\r\n”);
            //var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

                        // 最后的结束符
            var endBoundary = Encoding.ASCII.GetBytes(“–” + boundary + “–\r\n”);

            // 设置属性
            webRequest.Method = “POST”;
            webRequest.Timeout = timeOut;
            webRequest.ContentType = “multipart/form-data; boundary=” + boundary;

            // 写入文件
            const string filePartHeader =
                “Content-Disposition: form-data; name=\“{0}\“; filename=\“{1}\“\r\n” +
                 “Content-Type: application/octet-stream\r\n\r\n”;
            var header = string.Format(filePartHeader, fileKeyName, filePath);
            var headerbytes = Encoding.UTF8.GetBytes(header);

            memStream.Write(beginBoundary, 0, beginBoundary.Length);
            memStream.Write(headerbytes, 0, headerbytes.Length);

            //var buffer = new byte[1024];
            //int bytesRead; // =0

            //while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
            //{
            //    memStream.Write(buffer, 0, bytesRead);
            //}

            WebClient wc = new WebClient();
            byte[] buffer = wc.DownloadData(filePath);
            memStream.Write(buffer, 0, buffer.Length);

            //第二章图片
            //memStream.Write(beginBoundary, 0, beginBoundary.Length);

            //var aaa = Encoding.ASCII.GetBytes(“\r\n–” + boundary + “\r\n”);
            //memStream.Write(aaa, 0, aaa.Length);

            string imgName = string.Empty;
            string imgPath = string.Empty;
            foreach (var img in imgDic.Where(p => p.Key != fileKeyName))
            {
                imgName = img.Key;
                imgPath = img.Value;

                string nxetFileFormat = “\r\n–” + boundary + “\r\n” + filePartHeader;

                header = string.Format(nxetFileFormat, imgName, imgPath);
                headerbytes = Encoding.UTF8.GetBytes(header);

                memStream.Write(headerbytes, 0, headerbytes.Length);
                //fileStream = new FileStream(imgPath, FileMode.Open, FileAccess.Read);
                //while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                //{
                //    memStream.Write(buffer, 0, bytesRead);
                //}

                buffer = wc.DownloadData(imgPath);
                memStream.Write(buffer, 0, buffer.Length);
            }

            // 写入字符串的Key
            var stringKeyHeader = “\r\n–” + boundary +
                                   “\r\nContent-Disposition: form-data; name=\“{0}\“” +
                                   “\r\n\r\n{1}\r\n”;

            foreach (byte[] formitembytes in from string key in stringDict.Keys
                                             select string.Format(stringKeyHeader, key, stringDict[key])
                                                 into formitem
                                                 select Encoding.UTF8.GetBytes(formitem))
            {
                memStream.Write(formitembytes, 0, formitembytes.Length);
            }

            // 写入最后的结束边界符
            memStream.Write(endBoundary, 0, endBoundary.Length);

            webRequest.ContentLength = memStream.Length;

            var requestStream = webRequest.GetRequestStream();

            memStream.Position = 0;
            var tempBuffer = new byte[memStream.Length];
            memStream.Read(tempBuffer, 0, tempBuffer.Length);
            memStream.Close();

            requestStream.Write(tempBuffer, 0, tempBuffer.Length);
            requestStream.Close();

            var httpWebResponse = (HttpWebResponse)webRequest.GetResponse();

            using (var httpStreamReader = new StreamReader(httpWebResponse.GetResponseStream(),
                                                            Encoding.GetEncoding(“utf-8”)))
            {
                responseContent = httpStreamReader.ReadToEnd();
            }

            fileStream.Close();
            httpWebResponse.Close();
            webRequest.Abort();

            entry = new LogEntry(“发送图片结束-HttpPostData – “ + responseContent, 1);
            LogStub.Log(entry);

            return responseContent;
        }

单行图片调用以下方法时,如:

NameValueCollection col = new NameValueCollection();
                //col.Add(“media”, filePath);
                col.Add(“title”, title);
                col.Add(“content”, content);
                col.Add(“open_ids”, string.Join(“,”, openIDList));

                result = HttpPostData(url, 60000, “media”, filePath, col);

filePath是客户端的图片完整路径,

media是服务端接受图片的参数,这个要看接收端接收图片的参数名,注意此方法是单张图片的post调用

private static string HttpPostData(string url, int timeOut, string fileKeyName,
                                    string filePath, NameValueCollection stringDict)
        {
            string responseContent;
            var memStream = new MemoryStream();
            var webRequest = (HttpWebRequest)WebRequest.Create(url);
            // 边界符
            var boundary = “—————“ + DateTime.Now.Ticks.ToString(“x”);
            // 边界符
            var beginBoundary = Encoding.ASCII.GetBytes(“–” + boundary + “\r\n”);
            var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            // 最后的结束符
            var endBoundary = Encoding.ASCII.GetBytes(“–” + boundary + “–\r\n”);

            // 设置属性
            webRequest.Method = “POST”;
            webRequest.Timeout = timeOut;
            webRequest.ContentType = “multipart/form-data; boundary=” + boundary;

            // 写入文件
            const string filePartHeader =
                “Content-Disposition: form-data; name=\“{0}\“; filename=\“{1}\“\r\n” +
                 “Content-Type: application/octet-stream\r\n\r\n”;
            var header = string.Format(filePartHeader, fileKeyName, filePath);
            var headerbytes = Encoding.UTF8.GetBytes(header);

            memStream.Write(beginBoundary, 0, beginBoundary.Length);
            memStream.Write(headerbytes, 0, headerbytes.Length);

            var buffer = new byte[1024];
            int bytesRead; // =0

            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
            {
                memStream.Write(buffer, 0, bytesRead);
            }

            // 写入字符串的Key
            var stringKeyHeader = “\r\n–” + boundary +
                                   “\r\nContent-Disposition: form-data; name=\“{0}\“” +
                                   “\r\n\r\n{1}\r\n”;

            foreach (byte[] formitembytes in from string key in stringDict.Keys
                                             select string.Format(stringKeyHeader, key, stringDict[key])
                                                 into formitem
                                                 select Encoding.UTF8.GetBytes(formitem))
            {
                memStream.Write(formitembytes, 0, formitembytes.Length);
            }

            // 写入最后的结束边界符
            memStream.Write(endBoundary, 0, endBoundary.Length);

            webRequest.ContentLength = memStream.Length;

            var requestStream = webRequest.GetRequestStream();

            memStream.Position = 0;
            var tempBuffer = new byte[memStream.Length];
            memStream.Read(tempBuffer, 0, tempBuffer.Length);
            memStream.Close();

            requestStream.Write(tempBuffer, 0, tempBuffer.Length);
            requestStream.Close();

            var httpWebResponse = (HttpWebResponse)webRequest.GetResponse();

            using (var httpStreamReader = new StreamReader(httpWebResponse.GetResponseStream(),
                                                            Encoding.GetEncoding(“utf-8”)))
            {
                responseContent = httpStreamReader.ReadToEnd();
            }

            fileStream.Close();
            httpWebResponse.Close();
            webRequest.Abort();

            return responseContent;
        }

 原理简介及简单实现:

WebRequest request = WebRequest.Create(“http://www.baidu.com/“);//为指定的 URI 方案初始化新的 System.Net.WebRequest 实例
        request.UseDefaultCredentials = false;//获取或设置一个 System.Boolean 值,该值控制 System.Net.CredentialCache.DefaultCredentials
        WebResponse response = request.GetResponse();//返回对 Internet 请求的响应。
        Stream resStream = response.GetResponseStream();//返回从 Internet 资源返回数据流
        StreamReader sr = new StreamReader(resStream, System.Text.Encoding.Default);//实例华一个流的读写器
        ContentHtml.Text = sr.ReadToEnd();//这就是百度首页的HTML哦 ,字符串形式的流的其余部分(从当前位置到末尾)。如果当前位置位于流的末尾,则返回空字符串 (“”)
        resStream.Close();//关闭当前流并释放与之关联的所有资源
        sr.Close(); //关闭 System.IO.StreamReader 对象和基础流,并释放与读取器关联的所有系统资源

HttpWebRequest 和 HttpWebResponse 的应用

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(addr);
// Downloads the XML file from the specified server.
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
System.IO.StreamReader sr = new StreamReader(response.GetResponseStream(), System.Text.Encoding.GetEncoding(“gb2312”));
Console.Write(sr.ReadToEnd());
sr.Close();
response.Close();

1.获取http地址上的图片
http://www.cnblogs.com/fooo/archive/2007/06/28/798335.html

HttpWebResponse resp;
        HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(“http://www.csdn.net/Images/logo\_csdn.gif“);
        req.Timeout = 150000;
        resp = (HttpWebResponse)req.GetResponse();
        System.Drawing.Image img;
        img = new System.Drawing.Bitmap(resp.GetResponseStream());

HttpWebRequest&HttpWebResponse Headers

介绍

这里简要介绍如何使用HttpWebRequest&HttpWebResponse两个对象与HTTP服务器进行直接交互的过程.HttpWebRequest类对WebRequest中定义的属性和方法提供支持,在使用HttpWebRequest对象向HTTP服务器发起请求时请不要使用HttpWebRequest对象的构造函数,而应该使用WebRequest.Create()方法来初始化新的HttpWebRequest对象.如果统一资源标识符方案是”http://“或”https://“时,Create()则返回HttpWebResponse对象.

代码

首先,我们需要创建一个新的HttpWebRequest对象,代码如下:

HttpWebRequest myrequest = (HttpWebRequest)WebRequest.Create(new Uri(“urlstring”));

注意:上文中已经提到过不要使用HttpWebRequest的构造函数来创建对象;使用WebRequest.Create()方法初始化HttpWebRequest对象时应该对其进行类型转换.

接下来可以对新初始化的对象进行简单操作,比如可以设置它的标头属性,
下表列出了由属性或方法设置或由系统设置的标头:


标头

设置方法

Accept                由Accept属性设置                
Connection                    由Connection属性和KeepAlive属性设置                    
Content-Length                        由ContentLength属性设置                        
Content-Type                            由ContentType属性设置                            
Expect                                由Expect属性设置                                
Date                                    由系统设置为当前日期                                    
Host                                        由系统设置为当前主机信息                                        
If-Modified-Since                                            由IfModifiedSince属性设置                                            
Range                                                由Range属性设置                                                
Transfer-Encoding                                                    由TransferEncoding属性设置                                                    
Referer                                                        由Referer属性设置                                                        
User-Agent                                                            

由UserAgent属性设置


注意:HttpWebRequest自动注册.使用以”http://“或”https://“开头的URL之前,不需要调用RegisterPrefix方法来注册                                                

System.Net.HttpWebRequest选自:
MSDN:ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.NETDEVFX.v20.chs/cpref10/html/T_System_Net_HttpWebRequest.htm                                                

在完成对服务器的请求之后,我们需要获取从服务器中返回的信息,正如前面提到过的,使用HttpWebResponse类,具体做法参照如下代码:                                                

HttpWebResponse myresponse = (HttpWebResponse)myrequest.GetResponse();                                                

同样这里我们需要使用HttpWebResponse进行类型转换.到这里,我们已经成功的获得服务器返回的信息,一个myresponse对象,我们可以利用它来获得更多详细的信息,比如从服务器返回的Headers标头,还有其他的有用信息.而在此我们只给您列出如何获得来自服务器响应的Headers标头信息.我们的具体方法是这样的:                                                

        #region GetHeaders                                                

        public static WebHeaderCollection GetHeaders(Uri uri)                                                

        {                                                

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);                                                

            // 省略部分代码……                                                

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();                                                

            // 省略部分代码……                                                

            return response.Headers;                                                

        }                                                

        #endregion                                                

下面就是提供的main()函数:                                                

        // The app entry point                                                

        public static void Main(string[] args)                                                

        {                                                

            WebHeaderCollection headers = GetHeaders(new Uri(“http://www.listim.net“));                                                

            string[] headkeys = headers.AllKeys;                                                

            Console.WriteLine(“{0,-30}{1}”, “Name”, “Value”);                                                

            Console.WriteLine(“——————————–”);                                                

            foreach (string s in headkeys)                                                

            {                                                

                Console.WriteLine(“{0,-30}{1}”, s, headers[s]);                                                

            }                                                

        }

另一种实现方式:

///


        /// Post data到url
        ///

        /// 要post的数据
        /// 目标url
        /// 服务器响应
        static string PostDataToUrl(string data, string url)
        {
            Encoding encoding = Encoding.GetEncoding(sRequestEncoding);
            byte[] bytesToPost = encoding.GetBytes(data);
            return PostDataToUrl(bytesToPost, url);
        }

        ///


        /// Post data到url
        ///

        /// 要post的数据
        /// 目标url
        /// 服务器响应
        static string PostDataToUrl(byte[] data, string url)
        {
            #region 创建httpWebRequest对象
            WebRequest webRequest = WebRequest.Create(url);
            HttpWebRequest httpRequest = webRequest as HttpWebRequest;
            if (httpRequest == null)
            {
                throw new ApplicationException(
                string.Format(“Invalid url string: {0}”, url)
                );
            }
            #endregion

            #region 填充httpWebRequest的基本信息
            httpRequest.UserAgent = sUserAgent;
            httpRequest.ContentType = sContentType;
            httpRequest.Method = “POST”;
            #endregion

            #region 填充要post的内容
            httpRequest.ContentLength = data.Length;
            Stream requestStream = httpRequest.GetRequestStream();
            requestStream.Write(data, 0, data.Length);
            requestStream.Close();
            #endregion

            #region 发送post请求到服务器并读取服务器返回信息
            Stream responseStream;
            try
            {
                responseStream = httpRequest.GetResponse().GetResponseStream();
            }
            catch (Exception e)
            {
                // log error
                Console.WriteLine(
                string.Format(“POST操作发生异常:{0}”, e.Message)
                );
                throw e;
            }
            #endregion

            #region 读取服务器返回信息
            string stringResponse = string.Empty;
            using (StreamReader responseReader =
            new StreamReader(responseStream, Encoding.GetEncoding(sResponseEncoding)))
            {
                stringResponse = responseReader.ReadToEnd();
            }
            responseStream.Close();
            #endregion
            return stringResponse;
        }

调用时:

string param = string.Format(“time={0}&oid={1}&order={2}&name={3}&area={4}&point={5}&amount={6}&return={7}&hash={8}&version={9}”, Time, Oid, OrderID, LoginName, Area, Point, Amount, ReturnUrl, HashMoli, Version);

string result = PostDataToUrl(param, PostUrl);

还有一种实现的例子:

使用httpwebrequest Post数据到网站

怎样通过HttpWebRequest 发送 POST 请求到一个网页服务器?例如编写个程序实现自动用户登录,自动提交表单数据到网站等。
假如某个页面有个如下的表单(Form):

      < /form>

    从表单可看到表单有两个表单域,一个是userid另一个是password,所以以POST形式提交的数据应该包含有这两项。
其中POST的数据格式为:
表单域名称1=值1&表单域名称2=值2&表单域名称3=值3……
要注意的是“值”必须是经过HTMLEncode的,即不能包含“<>=&”这些符号。

本例子要提交的数据应该是:
userid=value1&password=value2

用C#写提交程序:

  string strId = “guest”;
  string strPassword= “123456”;

  ASCIIEncoding encoding=new ASCIIEncoding();
  string postData=”userid=”+strId;
  postData += (“&password=”+strPassword);

  byte[] data = encoding.GetBytes(postData);

  // Prepare web request…
  HttpWebRequest myRequest =
   (HttpWebRequest)WebRequest.Create(“http://www.here.com/login.asp“);

  myRequest.Method = “POST”;
  myRequest.ContentType=”application/x-www-form-urlencoded”;
  myRequest.ContentLength = data.Length;
  Stream newStream=myRequest.GetRequestStream();

  // Send the data.
  newStream.Write(data,0,data.Length);
  newStream.Close();

  // Get response
  HttpWebResponse myResponse=(HttpWebResponse)myRequest.GetResponse();
  StreamReader reader = new StreamReader(response.GetResponseStream(),Encoding.Default);
  string content = reader.ReadToEnd();
  Console.WriteLine(content);

C#模拟http 发送post或get请求 - shineme - 博客园

Excerpt

private string HttpPost(string Url, string postDataStr) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url); request.Method = “POST


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

private string HttpPost(string Url, string postDataStr)

        {

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);

            request.Method = "POST";

            request.ContentType = "application/x-www-form-urlencoded";

            request.ContentLength = Encoding.UTF8.GetByteCount(postDataStr);

            request.CookieContainer = cookie;

            Stream myRequestStream = request.GetRequestStream();

            StreamWriter myStreamWriter = new StreamWriter(myRequestStream, Encoding.GetEncoding("gb2312"));

            myStreamWriter.Write(postDataStr);

            myStreamWriter.Close();

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            response.Cookies = cookie.GetCookies(response.ResponseUri);

            Stream myResponseStream = response.GetResponseStream();

            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));

            string retString = myStreamReader.ReadToEnd();

            myStreamReader.Close();

            myResponseStream.Close();

            return retString;

        }

        public string HttpGet(string Url, string postDataStr)

        {

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr);

            request.Method = "GET";

            request.ContentType = "text/html;charset=UTF-8";

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            Stream myResponseStream = response.GetResponseStream();

            StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));

            string retString = myStreamReader.ReadToEnd();

            myStreamReader.Close();

            myResponseStream.Close();

            return retString;

        }

在post的时候有时也用的到cookie,像登录163发邮件时候就需要发送cookie,所以在外部一个cookie属性随时保存 CookieContainer cookie = new CookieContainer();

!注意:有时候请求会重定向,但我们就需要从重定向url获取东西,像QQ登录成功后获取sid,但上面的会自动根据重定向地址跳转。我们可以用:
request.AllowAutoRedirect = false;设置重定向禁用,你就可以从headers的Location属性中获取重定向地址

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

上篇文章《C#之相等比较(常规比较)》写了C#中比较是否相等的常规方法的使用说明,但是在实际开发中,往往也会用到两个引用类型的对象进行比较,而引用类型中有包含引用类型的属性或字段。
例如: 有一个引用类型TestClass,TestClass中同时包含值类型(int)的属性和引用类型属性(ExClass),引用类型的属性中又包含了引用类型的属性(ExClass2)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestClass
{
public int ID { get; set; }
public string Name { get; set; }
public ExClass OtherCalss { get; set; }
}

public class ExClass
{
public string Param1 { get; set; }
public ExClass2 OtherCalss2 { get; set; }
}

public class ExClass2
{
public string P1 { get; set; }
public string P2 { get; set; }
}

这个时候,如果有两个TestClass的实例,c1 ,c2

1
2
3
4
5
6
7
8
static void Main(string[] args)
{
TestClass c1 = new TestClass();
TestClass c2 = new TestClass();
//TODO:c1.ID=? c1.Name=? c1.OtherClass = ?……
//TODO:c2.ID=? c2.Name=? c2.OtherClass = ?……
//问题: c1的各属性值是否等于c2的各属性值
}

假设:c1,c2各自经过不同的操作进行了赋值,现在怎么比较c1和c2包含的属性值是否相等???

以上问题是C#提供的常规方法实现不了的(_如果有,请各位大牛留言告知,感激不尽……_)

因此,我就想办法自己写了一个比较的方法,称之为“深度比较”,就是比较c1和c2所有属性值是否相等,包括属性值的属性值

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
/// <summary>
/// 判断两个相同引用类型的对象的属性值是否相等
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj1">对象1</param>
/// <param name="obj2">对象2</param>
/// <param name="type">按type类型中的属性进行比较</param>
/// <returns></returns>
public static bool CompareProperties<T>(T obj1, T obj2, Type type)
{
//为空判断
if (obj1 == null && obj2 == null)
return true;
else if (obj1 == null || obj2 == null)
return false;

Type t = type;
PropertyInfo[] props = t.GetProperties();
foreach (var po in props)
{
if (IsCanCompare(po.PropertyType))
{
if (!po.GetValue(obj1).Equals(po.GetValue(obj2)))
{
return false;
}
}
else
{
var b = CompareProperties(po.GetValue(obj1), po.GetValue(obj2), po.PropertyType);
if(!b) return false;
}
}

return true;
}

/// <summary>
/// 该类型是否可直接进行值的比较
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
private static bool IsCanCompare(Type t)
{
if (t.IsValueType)
{
return true;
}
else
{
//String是特殊的引用类型,它可以直接进行值的比较
if (t.FullName == typeof(String).FullName)
{
return true;
}
return false;
}
}

除了属性值的比较,还可以比较字段值是否相等,原理相同,完整的测试代码在git上有,
地址:https://github.com/PandaCuipp/ObjectCompares

另外:网上有一种方法,是将c1、c2分别 二进制序列化后比较md5值,不知是否可行,还未试验(已验证,代码已更新到上面的git中)

C#搭建Oauth2.0认证流程以及代码示例 - WebEnh - 博客园

Excerpt

我认为对于一个普遍问题,必有对应的一个简洁优美的解决方案。当然这也许只是我的一厢情愿,因为根据宇宙法则,所有事物总归趋于混沌,而OAuth协议就是混沌中的产物,不管是1.0、1.0a还是2.0,单看版本号就让人神伤。 对接过各类开放平台的朋友对OAuth应该不会陌生。当年我小试了下淘宝API,各种t


<2024年9月>
1234567
891011121314
15161718192021
22232425262728
293012345
6789101112

C#搭建Oauth2.0认证流程以及代码示例

Posted on 2017-06-27 11:22  WebEnh  阅读(19363)  评论()  编辑  收藏  举报

复制代码

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
我认为对于一个普遍问题,必有对应的一个简洁优美的解决方案。当然这也许只是我的一厢情愿,因为根据宇宙法则,所有事物总归趋于混沌,而OAuth协议就是混沌中的产物,不管是1.<span>0</span>、<span>1</span>.0a还是2.<span>0</span><span>,单看版本号就让人神伤。


对接过各类开放平台的朋友对OAuth应该不会陌生。当年我小试了下淘宝API,各种token、key、secret、code、id,让我眼花缭乱,不明所以,虽然最终调通,但那种照猫画虎的感觉颇不好受。最近公司计划,开放接口的授权协议从1.0升到2.</span><span>0</span><span>,这个任务不巧就落在了我的头上。


声明:我并没有认真阅读过OAuth2.0协议规范,本文对OAuth2.0的阐述或有不当之处,请谅解。本文亦不保证叙述的正确性,欢迎指正。


OAuth2.0包含四种角色:


用户,又叫资源所有者
客户端,俗称第三方商户
授权服务端,颁发AccessToken
资源服务端,根据AccessToken开放相应的资源访问权限
本文涉及到三种授权模式:


Authorization Code模式:这是现在互联网应用中最常见的授权模式。客户端引导用户在授权服务端输入凭证获取用户授权(AccessToken),进而访问用户资源。需要注意的是,在用户授权后,授权服务端先回传客户端授权码,然后客户端再使用授权码换取AccessToken。为什么不直接返回AccessToken呢?主要是由于用户授权后,授权服务端重定向到客户端地址,此时数据只能通过QueryString方式向客户端传递,在地址栏URL中可见,不安全,于是分成了两步,第二步由客户端主动请求获取最终的令牌。
Client Credentials Flow:客户端乃是授权服务端的信任合作方,不需要用户参与授权,事先就约定向其开放指定资源(不特定于用户)的访问权限。客户端通过证书或密钥(或其它约定形式)证明自己的身份,获取AccessToken,用于后续访问。
Username and Password Flow:客户端被用户和授权服务端高度信任,用户直接在客户端中输入用户名密码,然后客户端传递用户名密码至授权服务端获取AccessToken,便可访问相应的用户资源。这在内部多系统资源共享、同源系统资源共享等场景下常用,比如单点登录,在登录时就获取了其它系统的AccessToken,避免后续授权,提高了用户体验。
上述模式涉及到三类凭证:


AuthorizationCode:授权码,授权服务端和客户端之间传输。
AccessToken:访问令牌,授权服务端发给客户端,客户端用它去到资源服务端请求资源。
RefreshToken:刷新令牌,授权服务端和客户端之间传输。
对客户端来说,授权的过程就是获取AccessToken的过程。


总的来说,OAuth并没有新鲜玩意,仍是基于加密、证书诸如此类的技术,在OAuth出来之前,这些东东就已经被大伙玩的差不多了。OAuth给到我们的最大好处就是统一了流程标准,一定程度上促进了互联网的繁荣。


我接到任务后,本着善假于物的理念,先去网上搜了一遍,原本以为有很多资源,结果只搜到DotNetOpenAuth这个开源组件。更让人失望的是,官方API文档没找到(可能是我找的姿势不对,有知道的兄弟告知一声),网上其它资料也少的可怜,其间发现一篇 OAuth2学习及DotNetOpenAuth部分源码研究 ,欣喜若狂,粗粗浏览一遍,有收获,却觉得该组件未免过于繁杂(由于时间紧迫,我并没有深入研究,只是当前观点)。DotNetOpenAuth包含OpenID、OAuth1.</span><span>0</span>[a]/<span>2.0</span><span>,自带的例子有几处暗坑,不易(能)调通。下面介绍我在搭建基于该组件的OAuth2.0授权框架时的一些心得体会。


本文介绍的DotNetOpenAuth乃是对应.Net4.0的版本。


授权服务端


授权服务端交道打的最多的就是客户端,于是定义一个Client类,实现DotNetOpenAuth.OAuth2.IClientDescription接口,下面我们来看IClientDescription的定义:


</span><span>public</span> <span>interface</span><span> IClientDescription {


Uri DefaultCallback { </span><span>get</span><span>; }


</span><span>//</span><span>0:有secret 1:没有secret</span>
ClientType ClientType { <span>get</span><span>; }


</span><span>//</span><span>该client的secret是否为空</span>
<span>bool</span> HasNonEmptySecret { <span>get</span><span>; }


</span><span>//</span><span>检查传入的callback与该client的callback是否一致</span>
<span>bool</span><span> IsCallbackAllowed(Uri callback);


</span><span>//</span><span>检查传入的secret与该client的secret是否一致</span>
<span>bool</span> IsValidClientSecret(<span>string</span><span> secret);
}
其中隐含了许多信息。DefaultCallback表示客户端的默认回调地址(假如有的话),在接收客户端请求时,使用IsCallbackAllowed判断回调地址是否合法(比如查看该次回调地址和默认地址是否属于同一个域),过滤其它应用的恶意请求。若ClientType 为0,则表示客户端需持密钥(secret)表明自己的身份,授权服务端可以据此赋予此类客户端相对更多的权限。自定义的Client类一般需要多定义一个ClientSecret属性。DefaultCallback和ClientSecret在下文常有涉及。


DotNetOpenAuth预定义了一个接口——IAuthorizationServerHost,这是个重要的接口,定义如下:


</span><span>public</span> <span>interface</span><span> IAuthorizationServerHost
{
ICryptoKeyStore CryptoKeyStore { </span><span>get</span><span>; }
INonceStore NonceStore { </span><span>get</span><span>; }


AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest);
AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(</span><span>string</span> userName, <span>string</span><span> password, IAccessTokenRequest accessRequest);
AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage);
IClientDescription GetClient(</span><span>string</span><span> clientIdentifier);
</span><span>bool</span><span> IsAuthorizationValid(IAuthorizationDescription authorization);
}
简单地说,CryptoKeyStore用于存取对称加密密钥,用于授权码和刷新令牌的加密,由于客户端不需要对它们进行解密,所以密钥只存于授权服务端;关于AccessToken的传输则略有不同,关于这点我们待会说。理解NonceStore 属性需要知道 N once和 Timestamp的概念,Nonce与消息合并加密可防止重放攻击,Timestamp是为了避免可能的Nonce重复问题,也将一同参与加密,具体参看 nonce和timestamp在Http安全协议中的作用 ;这项技术放在这里主要是为了确保一个授权码只能被使用一次。 CheckAuthorizeClientCredentialsGrant方法在客户端凭证模式下使用,CheckAuthorizeResourceOwnerCredentialGrant在用户名密码模式下使用,经测试,IsAuthorizationValid方法只在授权码模式下被调用,这三个方法的返回值标示是否通过授权。


当授权通过后,通过CreateAccessToken生成AccessToken并返回给客户端,客户端于是就可以用AccessToken访问资源服务端了。那当资源服务端接收到AccessToken时,需要做什么工作呢?首先,它要确认这个AccessToken是由合法的授权服务端颁发的,否则,攻击者就能使用DotNetOpenAuth另外建一个授权服务端,后果可想而知。说到身份认证,最成熟的就是RSA签名技术,即授权服务端私钥对AccessToken签名,资源服务端接收后使用授权服务端的公钥验证。我们还可以使用 资源服务器公</span>/<span>私钥对来加解密AccessToken(签名在加密后),这对于OAuth2.0来说没任何意义,而是为OAuth1.0服务的。


</span><span>public</span><span> AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage)
{
</span><span>var</span> accessToken = <span>new</span><span> AuthorizationServerAccessToken();
</span><span>int</span> minutes = <span>0</span><span>;
</span><span>string</span> setting = ConfigurationManager.AppSettings[<span>"</span><span>AccessTokenLifeTime</span><span>"</span><span>];
minutes </span>= <span>int</span>.TryParse(setting, <span>out</span> minutes) ? minutes : <span>10</span>;<span>//</span><span>10分钟</span>
accessToken.Lifetime =<span> TimeSpan.FromMinutes(minutes);


</span><span>//</span><span>这里设置加密公钥
</span><span>//</span><span>accessToken.ResourceServerEncryptionKey = new RSACryptoServiceProvider();
</span><span>//</span><span>accessToken.ResourceServerEncryptionKey.ImportParameters(ResourceServerEncryptionPublicKey);


</span><span>//</span><span>签名私钥,这是必须的(在后续版本中可以设置accessToken.SymmetricKeyStore替代)</span>
accessToken.AccessTokenSigningKey =<span> CreateRSA();


</span><span>var</span> result = <span>new</span><span> AccessTokenResult(accessToken);
</span><span>return</span><span> result;
}
前面说了,所有授权模式都是为了获取AccessToken,授权码模式和用户名密码模式还有个RefreshToken,当然授权码模式独有Authorization Code。一般来说,这三个东西,对于客户端是一个经过加密编码的字符串,对于服务端是可序列化的对象,存储相关授权信息。需要注意的是客户端证书模式没有RefreshToken,这是为什么呢?我们不妨想想为什么授权码模式和用户名密码模式有个RefreshToken,或者说RefreshToken的作用是什么。以下是我个人推测:


首先要明确,AccessToken一般是不会永久有效的。因为,AccessToken并没有承载可以验证客户端身份的完备信息,并且资源服务端也不承担验证客户端身份的职责,一旦AccessToken被他人获取,那么就有可能被恶意使用。失效机制有效减少了产生此类事故可能造成的损失。当AccessToken失效后,需要重新获取。对于授权码模式和用户名密码模式来说,假如没有RefreshToken,就意味这需要用户重新输入用户名密码进行再次授权。如果AccessToken有效期够长,比如几天,倒不觉得有何不妥,有些敏感应用只设置数分钟,就显得不够人性化了。为了解决这个问题,引入RefreshToken,它会在AccessToken失效后,在不需要用户参与的情况下,重新获取新的AccessToken,这里有个前提就是RefreshToken的有效期(如果有的话)要比AccessToken长,可设为永久有效。那么,RefreshToken泄露了会带来问题吗?答案是不会,除非你同时泄露了客户端身份凭证。需要同时具备RefreshToken和客户端凭证信息,才能获取新的AccessToken,我们甚至可以将旧的AccessToken当作RefreshToken。同理可推,由于不需要用户参与授权,在客户端证书模式下,客户端在AccessToken失效后只需提交自己的身份凭证重新请求新AccessToken即可,根本不需要RefreshToken。


授权码模式,用户授权后(此时并生成返回AccessToken,而是返回授权码),授权服务端要保存相关的授权信息,为此定义一个ClientAuthorization类:


</span><span>public</span> <span>class</span><span> ClientAuthorization
{
</span><span>public</span> <span>int</span> ClientId { <span>get</span>; <span>set</span><span>; }


</span><span>public</span> <span>string</span> UserId { <span>get</span>; <span>set</span><span>; }


</span><span>public</span> <span>string</span> Scope { <span>get</span>; <span>set</span><span>; }


</span><span>public</span> DateTime? ExpirationDateUtc { <span>get</span>; <span>set</span><span>; }
}
ClientId和UserId就不说了,Scope是授权范围,可以是一串Uri,也可以是其它标识,只要后台代码能通过它来判断待访问资源是否属于授权范围即可。ExpirationDateUtc乃是授权过期时间,即当该时间到期后,需要用户重新授权(有RefreshToken)也没用,为null表示永不过期。


资源服务端


在所有的授权模式下,资源服务端都只专注一件和OAuth相关的事情——验证AccessToken。这个步骤相对来说就简单很多,以Asp.net WebAPI为例。在此之前建议对Asp.net WebAPI消息拦截机制不熟悉的朋友浏览一遍 ASP.NET Web API之消息[拦截]处理 。这里我们新建一个继承自DelegatingHandler的类作为例子:


</span><span>public</span> <span>class</span><span> BearerTokenHandler : DelegatingHandler
{
</span><span>///</span> <span>&lt;summary&gt;</span>
<span>///</span><span> 验证访问令牌合法性,由授权服务器私钥签名,资源服务器通过对应的公钥验证
</span><span>///</span> <span>&lt;/summary&gt;</span>
<span>private</span> <span>static</span> <span>readonly</span> RSAParameters AuthorizationServerSigningPublicKey = <span>new</span> RSAParameters();<span>//</span><span>just a 例子</span>


<span>private</span><span> RSACryptoServiceProvider CreateAuthorizationServerSigningServiceProvider()
{
</span><span>var</span> authorizationServerSigningServiceProvider = <span>new</span><span> RSACryptoServiceProvider();
authorizationServerSigningServiceProvider.ImportParameters(AuthorizationServerSigningPublicKey);
</span><span>return</span><span> authorizationServerSigningServiceProvider;
}


</span><span>protected</span> <span>override</span> Task&lt;HttpResponseMessage&gt;<span> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
</span><span>if</span> (request.Headers.Authorization != <span>null</span><span>)
{
</span><span>if</span> (request.Headers.Authorization.Scheme == <span>"</span><span>Bearer</span><span>"</span><span>)
{
</span><span>var</span> resourceServer = <span>new</span> ResourceServer(<span>new</span> StandardAccessTokenAnalyzer(<span>this</span>.CreateAuthorizationServerSigningServiceProvider(), <span>null</span><span>));
</span><span>var</span> principal = resourceServer.GetPrincipal(request);<span>//</span><span>可以在此传入待访问资源标识参与验证</span>
HttpContext.Current.User =<span> principal;
Thread.CurrentPrincipal </span>=<span> principal;
}
}


</span><span>return</span> <span>base</span><span>.SendAsync(request, cancellationToken);
}
}
需要注意,AccessToken乃是从头信息Authorization获取,格式为“Bearer:AccessToken”,在下文“ 原生方式获取AccessToken ”中有进一步描述。ResourceServer.GetPrincipal方法使用授权服务端的公钥验证AccessToken的合法性,同时解密AccessToken,若传入参数有scope,则还会判断scope是否属于授权范围内,通过后将会话标识赋给当前会话,该会话标识乃是当初用户授权时的用户信息,这样就实现了用户信息的传递。一般来说若返回的principal为null,就可以不必执行后续逻辑了。


客户端


可以认为DotNetOpenAuth.OAuth2.Client是DotNetOpenAuth给C#客户端提供的默认SDK。我们以授权码模式为例。先声明一个IAuthorizationState接口对象,IAuthorizationState接口是用来保存最终换取AccessToken成功后授权服务端返回的信息,其部分定义如下:


</span><span>public</span> <span>interface</span><span> IAuthorizationState {
Uri Callback { </span><span>get</span>; <span>set</span><span>; }
</span><span>string</span> RefreshToken { <span>get</span>; <span>set</span><span>; }
</span><span>string</span> AccessToken { <span>get</span>; <span>set</span><span>; }
DateTime</span>? AccessTokenIssueDateUtc { <span>get</span>; <span>set</span><span>; }
DateTime</span>? AccessTokenExpirationUtc { <span>get</span>; <span>set</span><span>; }
HashSet</span>&lt;<span>string</span>&gt; Scope { <span>get</span><span>; }
}
AccessTokenExpirationUtc是AccessToken过期时间,以Utc时间为准。若该对象为null,则表示尚未授权,我们需要去授权服务端请求。


</span><span>private</span> <span>static</span> AuthorizationServerDescription _authServerDescription = <span>new</span><span> AuthorizationServerDescription
{
TokenEndpoint </span>= <span>new</span><span> Uri(MvcApplication.TokenEndpoint),
AuthorizationEndpoint </span>= <span>new</span><span> Uri(MvcApplication.AuthorizationEndpoint),
};


</span><span>private</span> <span>static</span> WebServerClient _client = <span>new</span> WebServerClient(_authServerDescription, <span>"</span><span>democlient</span><span>"</span>, <span>"</span><span>samplesecret</span><span>"</span><span>);


[HttpPost]
</span><span>public</span><span> ActionResult Index()
{
</span><span>if</span> (Authorization == <span>null</span><span>)
{
</span><span>return</span><span> _client.PrepareRequestUserAuthorization().AsActionResult();
}
</span><span>return</span><span> View();
}
AuthorizationServerDescription包含两个属性,AuthorizationEndpoint是用户显式授权的地址,一般即用户输用户名密码的地;TokenEndpoint是用授权码换取AccessToken的地址,注意该地址须用POST请求。“democlient”和“samplesecret”是示例用的客户端ID和客户端Secret。WebServerClient.PrepareRequestUserAuthorization方法将会首先返回code和state到当前url,以querystring的形式(若用户授权的话)。


code即是授权码,state参数不好理解,这涉及到CSRF,可参看浅谈CSRF攻击方式,state就是为了预防CSRF而引入的随机数。客户端生成该值,将其附加到state参数的同时,存入用户Cookie中,用户授权完毕后,该参数会同授权码一起返回到客户端,然后客户端将其值同Cookie中的值比较,若一样则表示该次授权为当前用户操作,视为有效。由于不同域的cookie无法共享,因此其它站点并不能知道state的确切的值,CSRF攻击也就无从谈起了。简单地说,state参数起到一个标示消息是否合法的作用。结合获取授权码这步来说,授权服务端返回的url为 http:</span><span>//</span><span>localhost:22187/?code=xxxxxxxxx&amp;state=_PzGpfJzyQI9DkdoyWeWr 格式,若忽略state,那么攻击方将code替换成自己的授权码,最终客户端获取的AccessToken是攻击方的AccessToken,由于AccessToken同用户关联,也就是说,后续客户端做的其实是另一个用户资源(也许是攻击方注册的虚拟用户),如果操作中包括新增或更新,那么真实用户信息就会被攻击方获取到。可参看 OAuth2 Cross Site Request Forgery, and state parameter 。</span>
<span>

有了code就可以去换取AccessToken了:


</span><span>public</span> ActionResult Index(<span>string</span> code,<span>string</span><span> state)
{
</span><span>if</span> (!<span>string</span>.IsNullOrEmpty(code) &amp;&amp; !<span>string</span><span>.IsNullOrEmpty(state))
{
</span><span>var</span> authorization =<span> _client.ProcessUserAuthorization(Request);
Authorization </span>=<span> authorization;
</span><span>return</span><span> View(authorization);
}
</span><span>return</span><span> View();
}
如前所述,Authorization不为null即表示整个授权流程成功完成。然后就可以用它来请求资源了。


</span><span>public</span><span> ActionResult Invoke()
{
</span><span>var</span> request = <span>new</span> HttpRequestMessage(<span>new</span> HttpMethod(<span>"</span><span>GET</span><span>"</span>), <span>"</span><span>http://demo.openapi.cn/bookcates</span><span>"</span><span>);
</span><span>using</span> (<span>var</span> httpClient = <span>new</span><span> HttpClient(_client.CreateAuthorizingHandler(Authorization)))
{
</span><span>using</span> (<span>var</span> resourceResponse =<span> httpClient.SendAsync(request))
{
ViewBag.Result </span>=<span> resourceResponse.Result.Content.ReadAsStringAsync().Result;
}
}
</span><span>return</span><span> View(Authorization);
}
WebServerClient.CreateAuthorizingHandler方法返回一个DelegatingHandler,主要用来当AccessToken过期时,使用RefreshToken刷新换取新的AccessToken;并设置Authorization头信息,下文有进一步说明。


原生方式获取AccessToken


既然是开放平台,面对的客户端种类自然多种多样,DotNetOpenAuth.OAuth2.Client显然就不够用了,我也不打算为了这个学遍所有程序语言。所幸OAuth基于http,不管任何语言开发的客户端,获取AccessToken的步骤本质上就是提交http请求和接收http响应的过程,客户端SDK只是将这个过程封装得更易用一些。下面就让我们以授权码模式为例,一窥究竟。


参照前述事例,当我们第一次(新的浏览器会话)在客户端点击“请求授权”按钮后,会跳转到授权服务端的授权界面。


可以看到,url中带了client_id、redirect_uri、state、response_type四个参数,若要请求限定的授权范围,还可以传入scope参数。其中response_type设为code表示请求的是授权码。


</span><span>1</span> <span>private</span> <span>string</span> GetNonCryptoRandomDataAsBase64(<span>int</span><span> binaryLength)
</span><span>2</span><span> {
</span><span>3</span> <span>byte</span>[] buffer = <span>new</span> <span>byte</span><span>[binaryLength];
</span><span>4</span><span> _random.NextBytes(buffer);
</span><span>5</span> <span>string</span> uniq =<span> Convert.ToBase64String(buffer);
</span><span>6</span> <span>return</span><span> uniq;
</span><span>7</span><span> }
</span><span>8</span>
<span>9</span> <span>public</span><span> ActionResult DemoRequestCode()
</span><span>10</span><span> {
</span><span>11</span> <span>string</span> xsrfKey = <span>this</span>.GetNonCryptoRandomDataAsBase64(<span>16</span>);<span>//</span><span>生成随机数</span>
<span>12</span> <span>string</span> url = MvcApplication.AuthorizationEndpoint + <span>"</span><span>?</span><span>"</span> +
<span>13</span> <span>string</span>.Format(<span>"</span><span>client_id={0}&amp;redirect_uri={1}&amp;response_type={2}&amp;state={3}</span><span>"</span><span>,
</span><span>14</span> <span>"</span><span>democlient</span><span>"</span>, <span>"</span><span>http://localhost:22187/</span><span>"</span>, <span>"</span><span>code</span><span>"</span><span>, xsrfKey);
</span><span>15</span> HttpCookie xsrfKeyCookie = <span>new</span><span> HttpCookie(XsrfCookieName, xsrfKey);
</span><span>16</span> xsrfKeyCookie.HttpOnly = <span>true</span><span>;
</span><span>17</span> xsrfKeyCookie.Secure =<span> FormsAuthentication.RequireSSL;
</span><span>18</span><span> Response.Cookies.Add(xsrfKeyCookie);
</span><span>19</span>
<span>20</span> <span>return</span><span> Redirect(url);
</span><span>21</span><span> }
授权码返回后,先检查state参数,若通过则换取AccessToken:


</span><span>private</span> <span>bool</span> VerifyState(<span>string</span><span> state)
{
</span><span>var</span> cookie =<span> Request.Cookies[XsrfCookieName];
</span><span>if</span> (cookie == <span>null</span><span>)
</span><span>return</span> <span>false</span><span>;


</span><span>var</span> xsrfCookieValue =<span> cookie.Value;
</span><span>return</span> xsrfCookieValue ==<span> state;
}


</span><span>private</span><span> AuthenticationHeaderValue SetAuthorizationHeader()
{
</span><span>string</span> concat = <span>"</span><span>democlient:samplesecret</span><span>"</span><span>;
</span><span>byte</span>[] bits =<span> Encoding.UTF8.GetBytes(concat);
</span><span>string</span> base64 =<span> Convert.ToBase64String(bits);
</span><span>return</span> <span>new</span> AuthenticationHeaderValue(<span>"</span><span>Basic</span><span>"</span><span>, base64);
}


</span><span>public</span> ActionResult Demo(<span>string</span> code, <span>string</span><span> state)
{
</span><span>if</span> (!<span>string</span>.IsNullOrEmpty(code) &amp;&amp; !<span>string</span>.IsNullOrEmpty(state) &amp;&amp;<span> VerifyState(state))
{
</span><span>var</span> httpClient = <span>new</span><span> HttpClient();
</span><span>var</span> httpContent = <span>new</span> FormUrlEncodedContent(<span>new</span> Dictionary&lt;<span>string</span>, <span>string</span>&gt;<span>()
{
{</span><span>"</span><span>code</span><span>"</span><span>, code},
{</span><span>"</span><span>redirect_uri</span><span>"</span>, <span>"</span><span>http://localhost:22187/</span><span>"</span><span>},
{</span><span>"</span><span>grant_type</span><span>"</span>,<span>"</span><span>authorization_code</span><span>"</span><span>}
});
httpClient.DefaultRequestHeaders.Authorization </span>= <span>this</span><span>.SetAuthorizationHeader();


</span><span>var</span> response =<span> httpClient.PostAsync(MvcApplication.TokenEndpoint, httpContent).Result;
Authorization </span>= response.Content.ReadAsAsync&lt;AuthorizationState&gt;<span>().Result;
</span><span>return</span><span> View(Authorization);
}
</span><span>return</span><span> View();
}
如上所示,以Post方式提交,三个参数,code即是授权码,redirect_uri和获取授权码时传递的redirect_uri要保持一致,grant_type设置为“authorization_code”。注意 SetAuthorizationHeader方法 ,需要设置请求头的Authorization属性,key为“Basic”,值为以Base64编码的“客户端ID:客户端Secret”字符串, 至于为何要如此规定,暂时没有探究 。 成功后返回的信息可以转为前面说的IAuthorizationState接口对象。


如前所述,当AccessToken过期后,需要用RefreshToken刷新。


</span><span>private</span> <span>void</span><span> RefreshAccessToken()
{
</span><span>var</span> httpClient = <span>new</span><span> HttpClient();
</span><span>var</span> httpContent = <span>new</span> FormUrlEncodedContent(<span>new</span> Dictionary&lt;<span>string</span>, <span>string</span>&gt;<span>()
{
{</span><span>"</span><span>refresh_token</span><span>"</span><span>, Authorization.RefreshToken},
{</span><span>"</span><span>grant_type</span><span>"</span>,<span>"</span><span>refresh_token</span><span>"</span><span>}
});
httpClient.DefaultRequestHeaders.Authorization </span>= <span>this</span><span>.SetAuthorizationHeader();


</span><span>var</span> response =<span> httpClient.PostAsync(MvcApplication.TokenEndpoint, httpContent).Result;
Authorization </span>= response.Content.ReadAsAsync&lt;AuthorizationState&gt;<span>().Result;
}
其中grant_type须设置为”refresh_token”,请求头信息设置同前。


获取AccessToken后, 就可以用于访问用户资源了。


</span><span>public</span><span> ActionResult DemoInvoke()
{
</span><span>var</span> httpClient = <span>new</span><span> HttpClient();
</span><span>if</span> (<span>this</span>.Authorization.AccessTokenExpirationUtc.HasValue &amp;&amp; <span>this</span>.Authorization.AccessTokenExpirationUtc.Value &lt;<span> DateTime.UtcNow)
{
</span><span>this</span><span>.RefreshAccessToken();
}
</span><span>var</span> bearerToken = <span>this</span><span>.Authorization.AccessToken;


httpClient </span>= <span>new</span><span> HttpClient();
httpClient.DefaultRequestHeaders.Authorization </span>= <span>new</span> AuthenticationHeaderValue(<span>"</span><span>Bearer</span><span>"</span><span>, bearerToken);
</span><span>var</span> request = <span>new</span> HttpRequestMessage(<span>new</span> HttpMethod(<span>"</span><span>GET</span><span>"</span>), <span>"</span><span>http://demo.openapi.cn/bookcates</span><span>"</span><span>);
</span><span>using</span> (<span>var</span> resourceResponse =<span> httpClient.SendAsync(request))
{
ViewBag.Result </span>=<span> resourceResponse.Result.Content.ReadAsStringAsync().Result;
}
</span><span>return</span><span> View(Authorization);
}
用法很简单, Authorization 请求头,key设为“Bearer”,值为AccessToken即可。</span>

复制代码