Java 设计模式


Java 设计模式 • (1)创建类型:创建类型的模式都是用于创建类的实 例。但是和通过 new 来创建实例不同,这些模式提供了 更加灵活的方式,是程序能够根据特定的情况创建特定 的类。结构类型 • (2)结构类型:结构类型的模式帮助开发人员将简单 对象组合在一起以后的更加复杂的结构。 • (3)行为类型:行为类型的模式帮助开发人员控制类 之间的通讯。 2009/07/31 Simple Factory Pattern 2008-04-12 13:02 工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一 个类实例化,不必事先知道每次要实例化哪一个类。工厂模式有以下几种形态: • 简单工厂(Simple Factory)模式 • 工厂方法(Factory Method)模式 • 抽象工厂(Abstract Factory)模式 一、 简单工厂(Simple Factory)模式 Simple Factory 模式根据提供给它的数据,返回几个可能类中的一个类的实例。 通常它返回的类都有一个公共的父类和公共的方法。 Simple Factory 模式实际上不是 GoF 23 个设计模式中的一员。 二、 Simple Factory 模式角色与结构: 工厂类角色 Creator (LightSimpleFactory):工厂类在客户端的直接控制下 (Create 方法)创建产品对象。 抽象产品角色 Product (Light):定义简单工厂创建的对象的父类或它们共同拥 有的接口。可以是一个类、抽象类或接口。 具体产品角色 ConcreteProduct (BulbLight, TubeLight):定义工厂具体加工 出的对象。 三、 程序举例: public abstract class Light{ public abstract void TurnOn(); public abstract void TurnOff(); } public class BulbLight extends Light{ public void TurnOn(){ System.out.println("Bulb Light is Turned on"); } public void TurnOff(){ System.out.println("Bulb Light is Turned off"); } } public class TubeLight extends Light{ public void TurnOn(){ System.out.println("Tube Light is Turned on"); } public void TurnOff(){ System.out.println("Tube Light is Turned off"); } } public class LightSimpleFactory{ public Light Create(string LightType){ if(LightType == "Bulb") return new BulbLight(); else if(LightType == "Tube") return new TubeLight(); else return null; } } public class Client{ public static void main(String[] args){ LightSimpleFactory lsf = new LightSimpleFactory(); Light l = lsf.Create("Bulb"); l.TurnOn(); l.TurnOff(); System.out.println("-----------------"); l = lsf.Create("Tube"); l.TurnOn(); l.TurnOff(); } } 四、 Simple Factory 模式演化 Simple Factory 模式演化(一) 除了上面的用法外,在有些情况下 Simple Factory 可以由抽象产品角色扮演, 一个抽象产品类同时是子类的工厂。 程序举例: public class Light{ public void TurnOn(){ } public void TurnOff(){ } public static Light Create(string LightType){ if(LightType == "Bulb") return new BulbLight(); else if(LightType == "Tube") return new TubeLight(); else return null; } } public class BulbLight extends Light{ public void TurnOn(){ System.out.println("Bulb Light is Turned on"); } public void TurnOff(){ System.out.println("Bulb Light is Turned off"); } } public class TubeLight extends Light{ public void TurnOn(){ System.out.println("Tube Light is Turned on"); } public void TurnOff(){ System.out.println("Tube Light is Turned off"); } } public class Client{ public static void main(String[] args){ Light l = Light.Create("Bulb"); l.TurnOn(); l.TurnOff(); System.out.println("-----------------"); l = Light.Create("Tube"); l.TurnOn(); l.TurnOff(); } } Simple Factory 模式演化(二) 三个角色全部合并: 与单件模式(Singleton)相近,但是有区别。 五、 优点与缺点: 优点: 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客 户端可以免除直接创建产品对象的责任,而仅仅"消费"产品。简单工厂模式通过 这种做法实现了对责任的分割。 缺点: 当产品有复杂的多层等级结构时,工厂类只有自己,以不变应万变,就是模式的 缺点。因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要 受到影响。 同时,系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂 逻辑过于复杂。 另外,简单工厂模式通常使用静态工厂方法,这使得无法由子类继承,造成工厂 角色无法形成基于继承的等级结构。 抽象工厂模式(Abstract Factory Pattern) 2007-03-30 13:13 抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。 为了方便引进抽象工厂模式,引进一个新概念:产品族(Product Family)。所 谓产品族,是指位于不同产品等级结构,功能相关联的产品组成的家族。如图: 图中一共有四个产品族,分布于三个不同的产品等级结构中。只要指明一个产品 所处的产品族以及它所属的等级结构,就可以唯一的确定这个产品。 引进抽象工厂模式 所谓的抽象工厂是指一个工厂等级结构可以创建出分属于不同产品等级结构的 一个产品族中的所有对象。如果用图来描述的话,如下图: 二、 Abstract Factory 模式的结构: 图中描述的东西用产品族描述如下: 抽象工厂(Abstract Factory)角色:担任这个角色的是工厂方法模式的核心, 它是与应用系统商业逻辑无关的。 具体工厂(Concrete Factory)角色:这个角色直接在客户端的调用下创建产品 的实例。这个角色含有选择合适的产品对象的逻辑,而这个逻辑是与应用系统的 商业逻辑紧密相关的。 抽象产品(Abstract Product)角色:担任这个角色的类是工厂方法模式所创建 的对象的父类,或它们共同拥有的接口。 具体产品(Concrete Product)角色:抽象工厂模式所创建的任何产品对象都是 某一个具体产品类的实例。这是客户端最终需要的东西,其内部一定充满了应用 系统的商业逻辑。 三、 程序举例: 该程序演示了抽象工厂的结构,本身不具有任何实际价值。 // Abstract Factory pattern -- Structural example using System; // "AbstractFactory" abstract class AbstractFactory { // Methods abstract public AbstractProductA CreateProductA(); abstract public AbstractProductB CreateProductB(); } // "ConcreteFactory1" class ConcreteFactory1 : AbstractFactory { // Methods override public AbstractProductA CreateProductA() { return new ProductA1(); } override public AbstractProductB CreateProductB() { return new ProductB1(); } } // "ConcreteFactory2" class ConcreteFactory2 : AbstractFactory { // Methods override public AbstractProductA CreateProductA() { return new ProductA2(); } override public AbstractProductB CreateProductB() { return new ProductB2(); } } // "AbstractProductA" abstract class AbstractProductA { } // "AbstractProductB" abstract class AbstractProductB { // Methods abstract public void Interact(AbstractProductA a); } // "ProductA1" class ProductA1 : AbstractProductA { } // "ProductB1" class ProductB1 : AbstractProductB { // Methods override public void Interact(AbstractProductA a) { Console.WriteLine(this + " interacts with " + a); } } // "ProductA2" class ProductA2 : AbstractProductA { } // "ProductB2" class ProductB2 : AbstractProductB { // Methods override public void Interact(AbstractProductA a) { Console.WriteLine(this + " interacts with " + a); } } // "Client" - the interaction environment of the products class Environment { // Fields private AbstractProductA AbstractProductA; private AbstractProductB AbstractProductB; // Constructors public Environment(AbstractFactory factory) { AbstractProductB = factory.CreateProductB(); AbstractProductA = factory.CreateProductA(); } // Methods public void Run() { AbstractProductB.Interact(AbstractProductA); } } /**/ /// /// ClientApp test environment /// class ClientApp { public static void Main(string[] args) { AbstractFactory factory1 = new ConcreteFactory1(); Environment e1 = new Environment(factory1); e1.Run(); AbstractFactory factory2 = new ConcreteFactory2(); Environment e2 = new Environment(factory2); e2.Run(); } } 四、 在什么情形下使用抽象工厂模式: 在以下情况下应当考虑使用抽象工厂模式: • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对 于所有形态的工厂模式都是重要的。 • 这个系统有多于一个的产品族,而系统只消费其中某一产品族。 • 同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计 中体现出来。 • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端 不依赖于实现。 五、 抽象工厂的起源 据说最早的应用是用来创建在不同操作系统的视窗环境下都能够运行的系统。比 如在 Windows 与 Unix 系统下都有视窗环境的构件,在每一个操作系统中,都有 一个视窗构件组成的构件家族。我们可以通过一个抽象角色给出功能描述,而由 具体子类给出不同操作系统下的具体实现,如图: 可以发现上面产品类图有两个产品等级结构,分别是 Button 与 Text;同时有两 个产品族:Unix 产品族与 Windows 产品族。 系统对产品对象的创建要求由一个工厂的等级结构满足。其中有两个具体工厂角 色,即 UnixFactory 和 WinFactory。UnixFactory 对象负责创建 Unix 产品族中 的产品,而 WinFactory 负责创建 Windows 产品族中的产品。 显然一个系统只能够在某一个操作系统的视窗环境下运行,而不能同时在不同的 操作系统上运行。所以,系统实际上只能消费属于同一个产品族的产品。 在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统只能 消费某一个产品族了。 六、 Abstract Factory 模式在实际系统中的实现 Herbivore:草食动物 Carnivore:食肉动物 Bison:['baisn],美洲或欧洲的野牛 下面实际代码演示了一个电脑游戏中创建不同动物的抽象工厂。尽管在不同大陆 下动物物种是不一样的,但动物间的关系仍然保留了下来。 // Abstract Factory pattern -- Real World example using System; // "AbstractFactory" abstract class ContinentFactory { // Methods abstract public Herbivore CreateHerbivore(); abstract public Carnivore CreateCarnivore(); } // "ConcreteFactory1" class AfricaFactory : ContinentFactory { // Methods override public Herbivore CreateHerbivore() { return new Wildebeest(); } override public Carnivore CreateCarnivore() { return new Lion(); } } // "ConcreteFactory2" class AmericaFactory : ContinentFactory { // Methods override public Herbivore CreateHerbivore() { return new Bison(); } override public Carnivore CreateCarnivore() { return new Wolf(); } } // "AbstractProductA" abstract class Herbivore { } // "AbstractProductB" abstract class Carnivore { // Methods abstract public void Eat(Herbivore h); } // "ProductA1" class Wildebeest : Herbivore { } // "ProductB1" class Lion : Carnivore { // Methods override public void Eat(Herbivore h) { // eat wildebeest Console.WriteLine(this + " eats " + h); } } // "ProductA2" class Bison : Herbivore { } // "ProductB2" class Wolf : Carnivore { // Methods override public void Eat(Herbivore h) { // Eat bison Console.WriteLine(this + " eats " + h); } } // "Client" class AnimalWorld { // Fields private Herbivore herbivore; private Carnivore carnivore; // Constructors public AnimalWorld(ContinentFactory factory) { carnivore = factory.CreateCarnivore(); herbivore = factory.CreateHerbivore(); } // Methods public void RunFoodChain() { carnivore.Eat(herbivore); } } /**/ /// /// GameApp test class /// class GameApp { public static void Main(string[] args) { // Create and run the Africa animal world ContinentFactory africa = new AfricaFactory(); AnimalWorld world = new AnimalWorld(africa); world.RunFoodChain(); // Create and run the America animal world ContinentFactory america = new AmericaFactory(); world = new AnimalWorld(america); world.RunFoodChain(); } } 抽象工厂的另外一个例子: 如何设计抽象类工厂留作思考。 七、 "开放-封闭"原则 "开放-封闭"原则要求系统对扩展开放,对修改封闭。通过扩展达到增强其功能 的目的。对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两 方面: 增加产品族:Abstract Factory 很好的支持了"开放-封闭"原则。 增加新产品的等级结构:需要修改所有的工厂角色,没有很好支持"开放-封闭" 原则。 综合起来,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的 增加提供方便,而不能为新的产品等级结构的增加提供这样的方便。 生成器模式(Builder Pattern) 2007-04-19 09:16 一、 建造者(Builder)模式 建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使 一个建造过程生成具有不同的内部表象的产品对象。 对象性质的建造 有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不 能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、 主题、内容、附录等部分,而在最起码的收件人地址未被赋值之前,这个电子邮 件不能发出。 有些情况下,一个对象的一些性质必须按照某个顺序赋值才有意义。在某个性质 没有赋值之前,另一个性质则无法赋值。这些情况使得性质本身的建造涉及到复 杂的商业逻辑。 这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的零 件,建造产品的过程就是组合零件的过程。由于组合零件的过程很复杂,因此, 这些"零件"的组合过程往往被"外部化"到一个称作建造者的对象里,建造者返还 给客户端的是一个全部零件都建造完毕的产品对象。 命名的考虑 之所以使用"建造者"而没有用"生成器"就是因为用零件生产产品,"建造"更为合 适,"创建"或"生成"不太恰当。 二、 Builder 模式的结构: 建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分 的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对 象的是具体建造者(ConcreteBuilder)角色。具体建造者类必须实现这个接口 所要求的方法:一个是建造方法,另一个是结果返还方法。 具体建造者(Concrete Builder)角色:担任这个角色的是于应用程序紧密相关 的类,它们在应用程序调用下创建产品实例。这个角色主要完成的任务包括: 实现 Builder 角色提供的接口,一步一步完成创建产品实例的过程。 在建造过程完成后,提供产品的实例。 指导者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对 象。导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造 者对象。 产品(Product)角色:产品便是建造中的复杂对象。 指导者角色是于客户端打交道的角色。导演者角色将客户端创建产品的请求划分 为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角 色是做具体建造工作的,但却不为客户端所知。 三、 程序举例: 该程序演示了 Builder 模式一步一步完成构件复杂产品的过程。用户可以控制生 成过程以及生成不同对象。 // Builder pattern -- Structural example using System; using System.Collections; // "Director" class Director { // Methods public void Construct( Builder builder ) { builder.BuildPartA(); builder.BuildPartB(); } } // "Builder" abstract class Builder { // Methods abstract public void BuildPartA(); abstract public void BuildPartB(); abstract public Product GetResult(); } // "ConcreteBuilder1" class ConcreteBuilder1 : Builder { // Fields private Product product; // Methods override public void BuildPartA() { product = new Product(); product.Add( "PartA" ); } override public void BuildPartB() { product.Add( "PartB" ); } override public Product GetResult() { return product; } } // "ConcreteBuilder2" class ConcreteBuilder2 : Builder { // Fields private Product product; // Methods override public void BuildPartA() { product = new Product(); product.Add( "PartX" ); } override public void BuildPartB() { product.Add( "PartY" ); } override public Product GetResult() { return product; } } // "Product" class Product { // Fields ArrayList parts = new ArrayList(); // Methods public void Add( string part ) { parts.Add( part ); } public void Show() { Console.WriteLine( " Product Parts -------" ); foreach( string part in parts ) Console.WriteLine( part ); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Create director and builders Director director = new Director( ); Builder b1 = new ConcreteBuilder1(); Builder b2 = new ConcreteBuilder2(); // Construct two products director.Construct( b1 ); Product p1 = b1.GetResult(); p1.Show(); director.Construct( b2 ); Product p2 = b2.GetResult(); p2.Show(); } } 四、 建造者模式的活动序列: 客户端负责创建指导者和具体建造者对象。然后,客户把具体建造者对象交给指 导者。客户一声令下,指导者操纵建造者开始创建产品。当产品创建完成后,建 造者把产品返还给客户端。 五、 建造者模式的实现: 下面的程序代码演示了 Shop 对象使用 VehicleBuilders 来建造不同的交通工具。 该例子使用了 Builder 模式顺序建造交通工具的不同部分。 // Builder pattern -- Real World example using System; using System.Collections; // "Director" class Shop { // Methods public void Construct( VehicleBuilder vehicleBuilder ) { vehicleBuilder.BuildFrame(); vehicleBuilder.BuildEngine(); vehicleBuilder.BuildWheels(); vehicleBuilder.BuildDoors(); } } // "Builder" abstract class VehicleBuilder { // Fields protected Vehicle vehicle; // Properties public Vehicle Vehicle { get{ return vehicle; } } // Methods abstract public void BuildFrame(); abstract public void BuildEngine(); abstract public void BuildWheels(); abstract public void BuildDoors(); } // "ConcreteBuilder1" class MotorCycleBuilder : VehicleBuilder { // Methods override public void BuildFrame() { vehicle = new Vehicle( "MotorCycle" ); vehicle[ "frame" ] = "MotorCycle Frame"; } override public void BuildEngine() { vehicle[ "engine" ] = "500 cc"; } override public void BuildWheels() { vehicle[ "wheels" ] = "2"; } override public void BuildDoors() { vehicle[ "doors" ] = "0"; } } // "ConcreteBuilder2" class CarBuilder : VehicleBuilder { // Methods override public void BuildFrame() { vehicle = new Vehicle( "Car" ); vehicle[ "frame" ] = "Car Frame"; } override public void BuildEngine() { vehicle[ "engine" ] = "2500 cc"; } override public void BuildWheels() { vehicle[ "wheels" ] = "4"; } override public void BuildDoors() { vehicle[ "doors" ] = "4"; } } // "ConcreteBuilder3" class ScooterBuilder : VehicleBuilder { // Methods override public void BuildFrame() { vehicle = new Vehicle( "Scooter" ); vehicle[ "frame" ] = "Scooter Frame"; } override public void BuildEngine() { vehicle[ "engine" ] = "none"; } override public void BuildWheels() { vehicle[ "wheels" ] = "2"; } override public void BuildDoors() { vehicle[ "doors" ] = "0"; } } // "Product" class Vehicle { // Fields private string type; private Hashtable parts = new Hashtable(); // Constructors public Vehicle( string type ) { this.type = type; } // Indexers public object this[ string key ] { get{ return parts[ key ]; } set{ parts[ key ] = value; } } // Methods public void Show() { Console.WriteLine( " ---------------------------"); Console.WriteLine( "Vehicle Type: "+ type ); Console.WriteLine( " Frame : " + parts[ "frame" ] ); Console.WriteLine( " Engine : "+ parts[ "engine"] ); Console.WriteLine( " #Wheels: "+ parts[ "wheels"] ); Console.WriteLine( " #Doors : "+ parts[ "doors" ] ); } } /**//// /// BuilderApp test /// public class BuilderApp { public static void Main( string[] args ) { // Create shop and vehicle builders Shop shop = new Shop(); VehicleBuilder b1 = new ScooterBuilder(); VehicleBuilder b2 = new CarBuilder(); VehicleBuilder b3 = new MotorCycleBuilder(); // Construct and display vehicles shop.Construct( b1 ); b1.Vehicle.Show(); shop.Construct( b2 ); b2.Vehicle.Show(); shop.Construct( b3 ); b3.Vehicle.Show(); } } 六、 建造者模式的演化 建造者模式在使用的过程中可以演化出多种形式。 省略抽象建造者角色 如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。这时代码可能 如下: // "Director" class Director { private ConcreteBuilder builder; // Methods public void Construct() { builder.BuildPartA(); builder.BuildPartB(); } } 省略指导者角色 在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可 以省略掉指导者角色。让Builder 角色自己扮演指导者与建造者双重角色。这时 代码可能如下: public class Builder { private Product product = new Product(); public void BuildPartA() { //Some code here } public void BuildPartB() { //Some code here } public Product GetResult() { return product; } public void Construct() { BuildPartA(); BuildPartB(); } } 同时,客户端也需要进行相应的调整,如下: public class Client { private static Builder builder; public static void Main() { builder = new Builder(); builder.Construct(); Product product = builder.GetResult(); } } C#中的 StringBuilder 就是这样一个例子。 七、 在什么情况下使用建造者模式 以下情况应当使用建造者模式: 1、 需要生成的产品对象有复杂的内部结构。 2、 需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。 3、 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的 创建过程中不易得到。 使用建造者模式主要有以下效果: 1、 建造模式的使用使得产品的内部表象可以独立的变化。使用建造者模式可以 使客户端不必知道产品内部组成的细节。 2、 每一个 Builder 都相对独立,而与其它的 Builder 无关。 3、 模式所建造的最终产品更易于控制。 原型(Prototype)模式 原型模式的用意是:通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个原型对 象的办法创建出更多的同类型对象。 从孙大圣的手段谈起 孙悟空在与黄风怪的战斗中,"使一个身外身的手段:把毫毛揪下一把,用口嚼得粉碎,望上一 喷,叫声'变',变有百十个行者,都是一样得打扮,各执一根铁棒,把那怪围在空中。"换而言之, 孙悟空可以根据自己的形象,复制出很多"身外身"来。 老孙这种身外身的手段在面向对象设计领域里叫原型(Prototype)模式。 C#对原型模式的支持 在 C#里面,我们可以很容易的通过 Clone()方法实现原型模式。任何类,只要想支持克隆,必 须实现 C#中的 ICloneable 接口。ICloneable 接口中有一 Clone 方法,可以在类中复写实现 自定义的克隆方法。克隆的实现方法有两种:浅拷贝(shallow copy)与深拷贝(deep copy)。 (以下摘自:《.NET 框架程序设计(修订版)》,李建忠译)浅拷贝是指当对象的字段值被拷 贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对 该对象做了一个浅拷贝,那么两个对象将引用同一个字符串。而深拷贝是对对象实例中字段引用 的对象也进行拷贝的一种方式,所以如果一个对象有一个指向字符串的字段,并且我们对该对象 做了一个深拷贝的话,我们将创建一个新的对象和一个新的字符串--新对象将引用新字符串。需 要注意的是执行深拷贝后,原来的对象和新创建的对象不会共享任何东西;改变一个对象对另外 一个对象没有任何影响。 二、 Prototype 模式的结构: 客户(Client)角色:客户类提出创建对象的请求。 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个 C#接口或抽象类实现。此角色 给出所有的具体原型类所需的接口。在 C#中,抽象原型角色通常实现了 ICloneable 接口。 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求 的接口。 三、 程序举例: 下面的程序给出了一个示意性的实现: // Prototype pattern -- Structural example using System; // "Prototype" abstract class Prototype { // Fields private string id; // Constructors public Prototype( string id ) { this.id = id; } public string Id { get{ return id; } } // Methods abstract public Prototype Clone(); } // "ConcretePrototype1" class ConcretePrototype1 : Prototype { // Constructors public ConcretePrototype1( string id ) : base ( id ) {} // Methods override public Prototype Clone() { // Shallow copy return (Prototype)this.MemberwiseClone(); } } // "ConcretePrototype2" class ConcretePrototype2 : Prototype { // Constructors public ConcretePrototype2( string id ) : base ( id ) {} // Methods override public Prototype Clone() { // Shallow copy return (Prototype)this.MemberwiseClone(); } } /// /// Client test /// class Client { public static void Main( string[] args ) { // Create two instances and clone each ConcretePrototype1 p1 = new ConcretePrototype1( "I" ); ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone(); Console.WriteLine( "Cloned: {0}", c1.Id ); ConcretePrototype2 p2 = new ConcretePrototype2( "II" ); ConcretePrototype2 c2 = (ConcretePrototype2)p2.Clone(); Console.WriteLine( "Cloned: {0}", c2.Id ); } } 这个例子实现了一个浅拷贝。其中 MemberwiseClone()方法是 Object 类的一个受保护方法, 实现了对象的浅拷贝。如果希望实现一个深拷贝,应该实现 ICloneable 接口,并自己编写 ICloneable 的 Clone 接口方法。 四、 带 Prototype Manager 的原型模式 原型模式的第二种形式是带原型管理器的原型模式,其 UML 图如下: 客户(Client)角色:客户端类向原型管理器提出创建对象的请求。 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个 C#接口或抽象类实现。此角色 给出所有的具体原型类所需的接口。在 C#中,抽象原型角色通常实现了 ICloneable 接口。 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要 求的接口。 原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对 象。 下面这个例子演示了在原型管理器中存储用户预先定义的颜色原型,客户通过原型管理器克隆颜 色对象。 // Prototype pattern -- Real World example using System; using System.Collections; // "Prototype" abstract class ColorPrototype { // Methods public abstract ColorPrototype Clone(); } // "ConcretePrototype" class Color : ColorPrototype { // Fields private int red, green, blue; // Constructors public Color( int red, int green, int blue) { this.red = red; this.green = green; this.blue = blue; } // Methods public override ColorPrototype Clone() { // Creates a 'shallow copy' return (ColorPrototype) this.MemberwiseClone(); } public void Display() { Console.WriteLine( "RGB values are: {0},{1},{2}", red, green, blue ); } } // Prototype manager class ColorManager { // Fields Hashtable colors = new Hashtable(); // Indexers public ColorPrototype this[ string name ] { get{ return (ColorPrototype)colors[ name ]; } set{ colors.Add( name, value ); } } } /// /// PrototypeApp test /// class PrototypeApp { public static void Main( string[] args ) { ColorManager colormanager = new ColorManager(); // Initialize with standard colors colormanager[ "red" ] = new Color( 255, 0, 0 ); colormanager[ "green" ] = new Color( 0, 255, 0 ); colormanager[ "blue" ] = new Color( 0, 0, 255 ); // User adds personalized colors colormanager[ "angry" ] = new Color( 255, 54, 0 ); colormanager[ "peace" ] = new Color( 128, 211, 128 ); colormanager[ "flame" ] = new Color( 211, 34, 20 ); // User uses selected colors string colorName = "red"; Color c1 = (Color)colormanager[ colorName ].Clone(); c1.Display(); colorName = "peace"; Color c2 = (Color)colormanager[ colorName ].Clone(); c2.Display(); colorName = "flame"; Color c3 = (Color)colormanager[ colorName ].Clone(); c3.Display(); } } 五、 浅拷贝与深拷贝 下面给出浅拷贝与深拷贝的两个例子,例子使用了 ICloneable 接口。C#中的数组是引用型的 变量,我们通过数组来进行演示: 浅拷贝: using System; class ShallowCopy : ICloneable { public int[] v = {1,2,3}; public Object Clone() { return this.MemberwiseClone(); } public void Display() { foreach(int i in v) Console.Write( i + ", "); Console.WriteLine(); } } class Client { public static void Main() { ShallowCopy sc1 = new ShallowCopy(); ShallowCopy sc2 = (ShallowCopy)sc1.Clone(); sc1.v[0] = 9; sc1.Display(); sc2.Display(); } } ShallowCopy 对象实现了一个浅拷贝,因此当对 sc1 进行克隆时,其字段 v 并没有克隆,这导 致 sc1 与 sc2 的字段 v 都指向了同一个 v,因此,当修改了 sc1 的 v[0]后,sc2 的 v[0]也发 生了变化。 深拷贝: using System; class DeepCopy : ICloneable { public int[] v = {1,2,3}; // 默认构造函数 public DeepCopy() { } // 供 Clone 方法调用的私有构造函数 private DeepCopy(int[] v) { this.v = (int[])v.Clone(); } public Object Clone() { // 构造一个新的 DeepCopy 对象,构造参数为 // 原有对象中使用的 v return new DeepCopy(this.v); } public void Display() { foreach(int i in v) Console.Write( i + ", "); Console.WriteLine(); } } class Client { public static void Main() { DeepCopy dc1 = new DeepCopy(); DeepCopy dc2 = (DeepCopy)dc1.Clone(); dc1.v[0] = 9; dc1.Display(); dc2.Display(); } } 这次在克隆的时候,不但克隆对象本身,连里面的数组字段一并克隆。因此,最终打印出来的 dc1 与 dc2 不同。 六、 Prototype 模式的优点与缺点 Prototype 模式的优点包括 1、Prototype 模式允许动态增加或减少产品类。由于创建产品类实例的方法是产批类内部具有 的,因此增加新产品对整个结构没有影响。 2、Prototype 模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相 同的等级结构,而 Prototype 模式就不需要这样。 3、Portotype 模式具有给一个应用软件动态加载新功能的能力。由于 Prototype 的独立性较 高,可以很容易动态加载新功能而不影响老系统。 4、产品类不需要非得有任何事先确定的等级结构,因为 Prototype 模式适用于任何的等级结构。 Prototype 模式的缺点: Prototype 模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对 类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容 易的事。 概述 Singleton 模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点。这就提出了一个 问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一 个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任, 而不是类使用者的责任。 从另一个角度来说,Singleton 模式其实也是一种职责型模式。因为我们创建了一个对象,这个 对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它 也肩负了行使这种权力的职责! 意图 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 模型图 逻辑模型图: 物理模型图: 生活中的例子 美国总统的职位是 Singleton,美国宪法规定了总统的选举,任期以及继任的顺序。这样,在任 何时刻只能由一个现任的总统。无论现任总统的身份为何,其头衔"美利坚合众国总统"是访问这 个职位的人的一个全局的访问点。 五种实现 1.简单实现 1 public sealed class Singleton 2 { 3 static Singleton instance=null; 4 5 Singleton() 6 { 7 } 8 9 public static Singleton Instance 10 { 11 get 12 { 13 if (instance==null) 14 { 15 instance = new Singleton(); 16 } 17 return instance; 18 } 19 } 20 } 这种方式的实现对于线程来说并不是安全的,因为在多线程的环境下有可能得到 Singleton 类的 多个实例。如果同时有两个线程去判断(instance == null),并且得到的结果为真,这时两个 线程都会创建类 Singleton 的实例,这样就违背了 Singleton 模式的原则。实际上在上述代码中, 有可能在计算出表达式的值之前,对象实例已经被创建,但是内存模型并不能保证对象实例在第 二个线程创建之前被发现。 该实现方式主要有两个优点: z 由于实例是在 Instance 属性方法内部创建的,因此 类可以使用附加功能(例如,对子类进行实例化),即 使它可能引入不想要的依赖性。 z 直到对象要求产生一个实例才执行实例化;这种方法 称为“惰性实例化”。惰性实例化避免了在应用程序启 动时实例化不必要的 singleton。 2.安全的线程 1 public sealed class Singleton 2 { 3 static Singleton instance=null; 4 static readonly object padlock = new object(); 5 6 Singleton() 7 { 8 } 9 10 public static Singleton Instance 11 { 12 get 13 { 14 lock (padlock) 15 { 16 if (instance==null) 17 { 18 instance = new Singleton(); 19 } 20 return instance; 21 } 22 } 23 } 24 } 25 26 这种方式的实现对于线程来说是安全的。我们首先创建了一个进程辅助对象,线程在进入时先对 辅助对象加锁然后再检测对象是否被创建,这样可以确保只有一个实例被创建,因为在同一个时 刻加了锁的那部分程序只有一个线程可以进入。这种情况下,对象实例由最先进入的那个线程创 建,后来的线程在进入时(instence == null)为假,不会再去创建对象实例了。但是这种实现 方式增加了额外的开销,损失了性能。 3.双重锁定 1 public sealed class Singleton 2 { 3 static Singleton instance=null; 4 static readonly object padlock = new object(); 5 6 Singleton() 7 { 8 } 9 10 public static Singleton Instance 11 { 12 get 13 { 14 if (instance==null) 15 { 16 lock (padlock) 17 { 18 if (instance==null) 19 { 20 instance = new Singleton(); 21 } 22 } 23 } 24 return instance; 25 } 26 } 27 } 28 这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建 时它才加锁,有了我们上面第一部分的里面的分析,我们知道,加锁后还得再进行对象是否已被 创建的判断。它解决了线程并发问题,同时避免在每个 Instance 属性方法的调用中都出现独 占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类 型的实现。大多数情况下我们会用静态初始化。这种方式仍然有很多缺点:无法实现延迟初始化。 4.静态初始化 1 public sealed class Singleton 2 { 3 static readonly Singleton instance=new Singleton(); 4 5 static Singleton() 6 { 7 } 8 9 Singleton() 10 { 11 } 12 13 public static Singleton Instance 14 { 15 get 16 { 17 return instance; 18 } 19 } 20 } 21 看到上面这段富有戏剧性的代码,我们可能会产生怀疑,这还是 Singleton 模式吗?在此实现 中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记 为 sealed 以阻止发生派生,而派生可能会增加实例。此外,变量标记为 readonly,这意味着 只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。 该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来 解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访 问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。 由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用 所引用之前,不会发生实例化。 这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中, 您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。 5.延迟初始化 1 public sealed class Singleton 2 { 3 Singleton() 4 { 5 } 6 7 public static Singleton Instance 8 { 9 get 10 { 11 return Nested.instance; 12 } 13 } 14 15 class Nested 16 { 17 static Nested() 18 { 19 } 20 21 internal static readonly Singleton instance = new Singleton(); 22 } 23 } 24 这里,初始化工作有 Nested 类的一个静态成员来完成,这样就实现了延迟初始化,并具有很多 的优势,是值得推荐的一种实 现方式。 实现要点 z Singleton 模式是限制而不是改进类的创建。 z Singleton 类中的实例构造器可以设置为 Protected 以允许子类派生。 z Singleton 模式一般不要支持 Icloneable 接口,因为 这可能导致多个对象实例,与 Singleton 模式的初衷违 背。 z Singleton 模式一般不要支持序列化,这也有可能导致 多个对象实例,这也与 Singleton 模式的初衷违背。 z Singleton 只考虑了对象创建的管理,没有考虑到销毁 的管理,就支持垃圾回收的平台和对象的开销来讲,我 们一般没必要对其销毁进行特殊的管理。 z 理解和扩展 Singleton 模式的核心是“如何控制用户 使用 new 对一个类的构造器的任意调用”。 z 可以很简单的修改一个 Singleton,使它有少数几个实 例,这样做是允许的而且是有意义的。 优点 z 实例控制:Singleton 会阻止其他对象实例化其自己 的 Singleton 对象的副本,从而确保所有对象都访问 唯一实例 z 灵活性:因为类控制了实例化过程,所以类可以更加 灵活修改实例化过程 缺点 z 开销:虽然数量很少,但如果每次对象请求引用时都 要检查是否存在类的实例,将仍然需要一些开销。可以 通过使用静态初始化解决此问题,上面的五种实现方式 中已经说过了。 z 可能的开发混淆:使用 singleton 对象(尤其在类库中 定义的对象)时,开发人员必须记住自己不能使用 new 关 键字实例化对象。因为可能无法访问库源代码,因此应用程 序开发人员可能会意外发现自己无法直接实例化此类。 z 对象的生存期:Singleton 不能解决删除单个对象的问 题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实 例被取消分配,因为它包含对该实例的私有引用。在某 些语言中(如 C++),其他类可以删除 对象实例,但这样会导致 Singleton 类中出现悬浮引 用。 适用性 z 当类只能有一个实例而且客户可以从一个众所周知的 访问点访问它时。 z 当这个唯一实例应该是通过子类化可扩展的,并且客 户应该无需更改代码就能使用一个扩展的实例时。 应用场景 z 每台计算机可以有若干个打印机,但只能有一个 Printer Spooler,避免两个打印作业同时输出到打印 机。 (摘自吕震宇的C#设计模式(7)-Singleton Pattern) z PC 机中可能有几个串口,但只能有一个 COM1 口的实 例。 z 系统中只能有一个窗口管理器。 z .NET Remoting 中服务器激活对象中的 Sigleton 对象, 确保所有的客户程序的请求都只有一个实例来处理。 完整示例 这是一个简单的计数器例子,四个线程同时进行计数。 1 using System; 2 using System.Threading; 3 4 namespace SigletonPattern.SigletonCounter 5 { 6 /// 7 /// 功能:简单计数器的单件模式 8 /// 编写:Terrylee 9 /// 日期:2005 年 12 月 06 日 10 /// 11 public class CountSigleton 12 { 13 ///存储唯一的实例 14 static CountSigleton uniCounter = new CountSigleton(); 15 16 ///存储计数值 17 private int totNum = 0; 18 19 private CountSigleton() 20 21 { 22 ///线程延迟 2000 毫秒 23 Thread.Sleep(2000); 24 } 25 26 static public CountSigleton Instance() 27 28 { 29 30 return uniCounter; 31 32 } 33 34 ///计数加 1 35 public void Add() 36 { 37 totNum ++; 38 } 39 40 ///获得当前计数值 41 public int GetCounter() 42 { 43 return totNum; 44 } 45 46 } 47 } 48 1 using System; 2 using System.Threading; 3 using System.Text; 4 5 namespace SigletonPattern.SigletonCounter 6 { 7 /// 8 /// 功能:创建一个多线程计数的类 9 /// 编写:Terrylee 10 /// 日期:2005 年 12 月 06 日 11 /// 12 public class CountMutilThread 13 { 14 public CountMutilThread() 15 { 16 17 } 18 19 /// 20 /// 线程工作 21 /// 22 public static void DoSomeWork() 23 { 24 ///构造显示字符串 25 string results = ""; 26 27 ///创建一个 Sigleton 实例 28 CountSigleton MyCounter = CountSigleton.Instance(); 29 30 ///循环调用四次 31 for(int i=1;i<5;i++) 32 { 33 ///开始计数 34 MyCounter.Add(); 35 36 results +="线程"; 37 results += Thread.CurrentThread.Name.ToString() + "——〉"; 38 results += "当前的计数:"; 39 results += MyCounter.GetCounter().ToString(); 40 results += "\n"; 41 42 Console.WriteLine(results); 43 44 ///清空显示字符串 45 results = ""; 46 } 47 } 48 49 public void StartMain() 50 { 51 52 Thread thread0 = Thread.CurrentThread; 53 54 thread0.Name = "Thread 0"; 55 56 Thread thread1 =new Thread(new ThreadStart(DoSomeWork)); 57 58 thread1.Name = "Thread 1"; 59 60 Thread thread2 =new Thread(new ThreadStart(DoSomeWork)); 61 62 thread2.Name = "Thread 2"; 63 64 Thread thread3 =new Thread(new ThreadStart(DoSomeWork)); 65 66 thread3.Name = "Thread 3"; 67 68 thread1.Start(); 69 70 thread2.Start(); 71 72 thread3.Start(); 73 74 ///线程 0 也只执行和其他线程相同的工作 75 DoSomeWork(); 76 } 77 } 78 } 79 1 using System; 2 using System.Text; 3 using System.Threading; 4 5 namespace SigletonPattern.SigletonCounter 6 { 7 /// 8 /// 功能:实现多线程计数器的客户端 9 /// 编写:Terrylee 10 /// 日期:2005 年 12 月 06 日 11 /// 12 public class CountClient 13 { 14 public static void Main(string[] args) 15 { 16 CountMutilThread cmt = new CountMutilThread(); 17 18 cmt.StartMain(); 19 20 Console.ReadLine(); 21 } 22 } 23 } 24 总结 Singleton 设计模式是一个非常有用的机制,可用于在面向对象的应用程序中提供单个访问点。 文中通过五种实现方式的比较和一个完整的示例,完成了对 Singleton 模式的一个总结和探索。 用一句广告词来概括 Singleton 模式就是“简约而不简单”。 适配器模式(Adapter Pattern) 2007-04-19 09:40 结构模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的 结构。结构模式描述两种不同的东西:类与类的实例。根据这一点,结构模式可 以分为类的结构模式和对象的结构模式。 后续内容将包括以下结构模式: 适配器模式(Adapter):Match interfaces of different classes 合成模式(Composite):A tree structure of simple and composite objects 装饰模式(Decorator):Add responsibilities to objects dynamically 代理模式(Proxy):An object representing another object 享元模式(Flyweight):A fine-grained instance used for efficient sharing 门面模式(Facade):A single class that represents an entire subsystem 桥梁模式(Bridge):Separates an object interface from its implementation 一、 适配器(Adapter)模式 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口 不匹配而无法在一起工作的两个类能够在一起工作。 名称由来 这很像变压器(Adapter),变压器把一种电压变换成另一种电压。美国的生活 用电电压是 110V,而中国的电压是 220V。如果要在中国使用美国电器,就必须 有一个能把 220V 电压转换成 110V 电压的变压器。这个变压器就是一个 Adapter。 Adapter 模式也很像货物的包装过程:被包装的货物的真实样子被包装所掩盖和 改变,因此有人把这种模式叫做包装(Wrapper)模式。事实上,大家经常写很 多这样的 Wrapper 类,把已有的一些类包装起来,使之有能满足需要的接口。 适配器模式的两种形式 适配器模式有类的适配器模式和对象的适配器模式两种。我们将分别讨论这两种 Adapter 模式。 二、 类的 Adapter 模式的结构: 由图中可以看出,Adaptee 类没有 Request 方法,而客户期待这个方法。为了使 客户能够使用 Adaptee 类,提供一个中间环节,即类 Adapter 类,Adapter 类实 现了 Target 接口,并继承自 Adaptee,Adapter 类的 Request 方法重新封装了 Adaptee 的 SpecificRequest 方法,实现了适配的目的。 因为 Adapter 与 Adaptee 是继承的关系,所以这决定了这个适配器模式是类的。 该适配器模式所涉及的角色包括: 目标(Target)角色:这是客户所期待的接口。因为C#不支持多继承,所以Target 必须是接口,不可以是类。 源(Adaptee)角色:需要适配的类。 适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类。 三、 类的 Adapter 模式示意性实现: 下面的程序给出了一个类的 Adapter 模式的示意性的实现: // Class Adapter pattern -- Structural example using System; // "ITarget" interface ITarget { // Methods void Request(); } // "Adaptee" class Adaptee { // Methods public void SpecificRequest() { Console.WriteLine("Called SpecificRequest()" ); } } // "Adapter" class Adapter : Adaptee, ITarget { // Implements ITarget interface public void Request() { // Possibly do some data manipulation // and then call SpecificRequest this.SpecificRequest(); } } /**//// /// Client test /// public class Client { public static void Main(string[] args) { // Create adapter and place a request ITarget t = new Adapter(); t.Request(); } } 四、 对象的 Adapter 模式的结构: 从图中可以看出:客户端需要调用 Request 方法,而 Adaptee 没有该方法,为了 使客户端能够使用 Adaptee 类,需要提供一个包装(Wrapper)类 Adapter。这 个包装类包装了一个 Adaptee 的实例,从而将客户端与Adaptee 衔接起来。由于 Adapter 与 Adaptee 是委派关系,这决定了这个适配器模式是对象的。 该适配器模式所涉及的角色包括: 目标(Target)角色:这是客户所期待的接口。目标可以是具体的或抽象的类, 也可以是接口。 源(Adaptee)角色:需要适配的类。 适配器(Adapter)角色:通过在内部包装(Wrap)一个 Adaptee 对象,把源接 口转换成目标接口。 五、 对象的 Adapter 模式示意性实现: 下面的程序给出了一个类的 Adapter 模式的示意性的实现: // Adapter pattern -- Structural example using System; // "Target" class Target { // Methods virtual public void Request() { // Normal implementation goes here } } // "Adapter" class Adapter : Target { // Fields private Adaptee adaptee = new Adaptee(); // Methods override public void Request() { // Possibly do some data manipulation // and then call SpecificRequest adaptee.SpecificRequest(); } } // "Adaptee" class Adaptee { // Methods public void SpecificRequest() { Console.WriteLine("Called SpecificRequest()" ); } } /**//// /// Client test /// public class Client { public static void Main(string[] args) { // Create adapter and place a request Target t = new Adapter(); t.Request(); } } 六、 在什么情况下使用适配器模式 在以下各种情况下使用适配器模式: 1、 系统需要使用现有的类,而此类的接口不符合系统的需要。 2、 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些 类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。 3、 (对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用 类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。 七、 一个实际应用 Adapter 模式的例子 下面的程序演示了 Class Adapter 与 Object Adapter 的应用。 // Example of implementing the Adapter pattern using System; // Target public interface ICar { void Drive(); } // Direct use without Adapter public class CToyota : ICar { public void Drive() { Console.WriteLine("Vroom Vroom, we're off in our Toyota"); } } // Adaptee public class CCessna { public void Fly() { Console.WriteLine("Static runup OK, we're off in our C172"); } } // Class Adapter public class CDrivableCessna : CCessna, ICar { public void Drive() { base.Fly(); } } // Object Adapter public class CDrivableCessna2 : ICar { private CCessna m_oContained; public CDrivableCessna2() { m_oContained = new CCessna(); } public void Drive() { m_oContained.Fly(); } } // Client public class Client { public static void Main(string[] args) { ICar oCar = new CToyota(); Console.Write("Class Adapter: Driving an Automobile"); oCar.Drive(); oCar = new CDrivableCessna(); Console.Write("Driving a Cessna"); oCar.Drive(); oCar = new CDrivableCessna2(); Console.Write(" Object Adapter: Driving a Cessna"); oCar.Drive(); } } 八、 关于 Adapter 模式的讨论 Adapter 模式在实现时有以下这些值得注意的地方: 1、 目标接口可以省略,模式发生退化。但这种做法看似平庸而并不平庸,它可 以使 Adaptee 不必实现不需要的方法(可以参考 Default Adapter模式)。其表 现形式就是父类实现缺省方法,而子类只需实现自己独特的方法。这有些像模板 (Template)模式。 2、 适配器类可以是抽象类。 3、 带参数的适配器模式。使用这种办法,适配器类可以根据参数返还一个合适 的实例给客户端。 桥接模式(Bridge Pattern) 2007-04-19 09:32 一、 桥梁(Bridge)模式 桥梁模式是一个非常有用的模式,也是比较复杂的一个模式。熟悉这个模式对于 理解面向对象的设计原则,包括"开-闭"原则(OCP)以及组合/聚合复用原则 (CARP)都很有帮助。理解好这两个原则,有助于形成正确的设计思想和培养良好 的设计风格。 桥梁模式的用意 【GOF95】在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化 (Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这 句话有三个关键词,也就是抽象化、实现化和脱耦。 抽象化 存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就 是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。 实现化 抽象化给出的具体实现,就是实现化。 脱耦 所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦 合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开, 或者说是将它们之间的强关联改换成弱关联。 将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱 关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化 之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这 就是桥梁模式的用意。 二、 桥梁模式的结构 桥梁模式【GOF95】是对象的结构模式,又称为柄体(Handle and Body)模式或接 口(Interface)模式。 下图所示就是一个实现了桥梁模式的示意性系统的结构图。 可以看出,这个系统含有两个等级结构,也就是: 由抽象化角色和修正抽象化角色组成的抽象化等级结构。 由实现化角色和两个具体实现化角色所组成的实现化等级结构。 桥梁模式所涉及的角色有: 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引 用。 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对 抽象化的定义。 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的 实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上, 这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应 当只给出基于底层操作的更高一层的操作。 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体 实现。 三、 桥梁模式的示意性源代码 // Bridge pattern -- Structural example using System; // "Abstraction" class Abstraction { // Fields protected Implementor implementor; // Properties public Implementor Implementor { set{ implementor = value; } } // Methods virtual public void Operation() { implementor.Operation(); } } // "Implementor" abstract class Implementor { // Methods abstract public void Operation(); } // "RefinedAbstraction" class RefinedAbstraction : Abstraction { // Methods override public void Operation() { implementor.Operation(); } } // "ConcreteImplementorA" class ConcreteImplementorA : Implementor { // Methods override public void Operation() { Console.WriteLine("ConcreteImplementorA Operation"); } } // "ConcreteImplementorB" class ConcreteImplementorB : Implementor { // Methods override public void Operation() { Console.WriteLine("ConcreteImplementorB Operation"); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { Abstraction abstraction = new RefinedAbstraction(); // Set implementation and call abstraction.Implementor = new ConcreteImplementorA(); abstraction.Operation(); // Change implemention and call abstraction.Implementor = new ConcreteImplementorB(); abstraction.Operation(); } } 四、 调制解调器问题 感觉《敏捷软件开发-原则、模式与实践》中关于Bridge 模式的例子很好。(《Java 与模式》一书 33 章的对变化的封装一节也写得很不错,推荐大家读一读。它深 入的阐述了《Design Patterns Explained》一书中"1)Design to interfaces. 2)Favor composition over inheritance. 3)Find what varies and encapsulate it"的三个观点。)。 如图所示,有大量的调制解调器客户程序在使用 Modem 接口。Modem 接口被几个 派生类 HayesModem、USRoboticsModem 和 EarniesModem 实现。它很好地遵循了 OCP、LSP 和 DIP。当增加新种类的调制解调器时,调制解调器的客户程序不会受 影响。 假定这种情形持续了几年,并有许多调制解调器的客户程序都在使用着 Modem 接口。现出现了一种不拨号的调制解调器,被称为专用调制解调器。它们位于一 条专用连接的两端。有几个新应用程序使用这些专用调制解调器,它们无需拨号。 我们称这些使用者为 DedUser。但是,客户希望当前所有的调制解调器客户程序 都可以使用这些专用调制解调器。他们不希望去更改许许多多的调制解调器客户 应用程序,所以完全可以让这些调制解调器客户程序去拨一些假(dummy)电话号 码。 如果能选择的话,我们会把系统的设计更改为下图所示的那样。 我们把拨号和通信功能分离为两个不同的接口。原来的调制解调器实现这两个接 口,而调制解调器客户程序使用这两个接口。DedUser 只使用 Modem 接口,而 DedicateModem 只实现 Modem 接口。但这样做会要求我们更改所有的调制解调器 客户程序--这是客户不允许的。 一个可能的解决方案是让 DedicatedModem 从 Modem 派生并且把 dial 方法和 hangup 方法实现为空,就像下面这样: 几个月后,已经有了大量的 DedUser,此时客户提出了一个新的更改。为了能拨 国际电话号码、信用卡电话、PIN标识电话等等,必修对现有dial 中使用 char[10] 存储号码改为能够拨打任意长度的电话号码。 显然,所有的调制解调器客户程序都必须更改。客户同意了对调制解调器客户程 序的更改,因为他们别无选择。糟糕的是,现在必须要去告诉DedUser 的编写者, 他们必须要更改他们的代码!你可以想象他们听到这个会有多高兴。本来他们是 不用调用 dial 的。 这就是许多项目都会具有的那种有害的混乱依赖关系。系统某一部分中的一个杂 凑体(kludge)创建了一个有害的依赖关系,最终导致系统中完全无关的部分出现 问题。 如果使用 ADAPTER 模式解决最初的问题的话,就可以避免这个严重问题。如图: 请注意,杂凑体仍然存在。适配器仍然要模拟连接状态。然而,所有的依赖关系 都是从适配器发起的。杂凑体和系统隔离,藏身于几乎无人知晓的适配器中。 BRIDGE 模式 看待这个问题,还有另外一个方式。现在,出现了另外一种切分 Modem 层次结构 的方式。如下图: 这不是一个理想的结构。每当增加一款新硬件时,就必须创建两个新类--一个针 对专用的情况,一个针对拨号的情况。每当增加一种新连接类型时,就必须创建 3 个新类,分别对应 3 款不同的硬件。如果这两个自由度根本就是不稳定的,那 么不用多久,就会出现大量的派生类。 在类型层次结构具有多个自由度的情况中,BRIDGE 模式通常是有用的。我们可 以把这些层次结构分开并通过桥把它们结合到一起,而不是把它们合并起来。如 图: 我们把调制解调器类层次结构分成两个层次结构。一个表示连接方法,另一个表 示硬件。 这个结构虽然复杂,但是很有趣。它的创建不会影响到调制解调器的使用者,并 且还完全分离了连接策略和硬件实现。ModemConnectController 的每个派生类 代表了一个新的连接策略。在这个策略的实现中可以使用sendlmp、receivelmp、 diallmp 和 hanglmp。新 imp 方法的增加不会影响到使用者。可以使用 ISP 来给 连接控制类增加新的接口。这种做法可以创建出一条迁移路径,调制解调器的客 户程序可以沿着这条路径慢慢地得到一个比 dial 和 hangup 层次更高的 API。 五、 另外一个实际应用 Bridge 模式的例子 该例子演示了业务对象(BusinessObject)通过 Bridge 模式与数据对象 (DataObject)解耦。数据对象的实现可以在不改变客户端代码的情况下动态进 行更换。 // Bridge pattern -- Real World example using System; using System.Collections; // "Abstraction" class BusinessObject { // Fields private DataObject dataObject; protected string group; // Constructors public BusinessObject( string group ) { this.group = group; } // Properties public DataObject DataObject { set{ dataObject = value; } get{ return dataObject; } } // Methods virtual public void Next() { dataObject.NextRecord(); } virtual public void Prior() { dataObject.PriorRecord(); } virtual public void New( string name ) { dataObject.NewRecord( name ); } virtual public void Delete( string name ) { dataObject.DeleteRecord( name ); } virtual public void Show() { dataObject.ShowRecord(); } virtual public void ShowAll() { Console.WriteLine( "Customer Group: {0}", group ); dataObject.ShowAllRecords(); } } // "RefinedAbstraction" class CustomersBusinessObject : BusinessObject { // Constructors public CustomersBusinessObject( string group ) : base( group ){} // Methods override public void ShowAll() { // Add separator lines Console.WriteLine(); Console.WriteLine( "------------------------" ); base.ShowAll(); Console.WriteLine( "------------------------" ); } } // "Implementor" abstract class DataObject { // Methods abstract public void NextRecord(); abstract public void PriorRecord(); abstract public void NewRecord( string name ); abstract public void DeleteRecord( string name ); abstract public void ShowRecord(); abstract public void ShowAllRecords(); } // "ConcreteImplementor" class CustomersDataObject : DataObject { // Fields private ArrayList customers = new ArrayList(); private int current = 0; // Constructors public CustomersDataObject() { // Loaded from a database customers.Add( "Jim Jones" ); customers.Add( "Samual Jackson" ); customers.Add( "Allen Good" ); customers.Add( "Ann Stills" ); customers.Add( "Lisa Giolani" ); } // Methods public override void NextRecord() { if( current <= customers.Count - 1 ) current++; } public override void PriorRecord() { if( current > 0 ) current--; } public override void NewRecord( string name ) { customers.Add( name ); } public override void DeleteRecord( string name ) { customers.Remove( name ); } public override void ShowRecord() { Console.WriteLine( customers[ current ] ); } public override void ShowAllRecords() { foreach( string name in customers ) Console.WriteLine( " " + name ); } } /**//// /// Client test /// public class BusinessApp { public static void Main( string[] args ) { // Create RefinedAbstraction CustomersBusinessObject customers = new CustomersBusinessObject(" Chicago "); // Set ConcreteImplementor customers.DataObject = new CustomersDataObject(); // Exercise the bridge customers.Show(); customers.Next(); customers.Show(); customers.Next(); customers.Show(); customers.New( "Henry Velasquez" ); customers.ShowAll(); } } 六、 在什么情况下应当使用桥梁模式 根据上面的分析,在以下的情况下应当使用桥梁模式: 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避 免在两个层次之间建立静态的联系。 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对 客户端是完全透明的。 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦 合。 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独 立变化,设计要求需要独立管理这两者。 合成模式(Composite Pattern) 2007-04-19 09:38 一、 合成(Composite)模式 合成模式有时又叫做部分-整体模式(Part-Whole)。合成模式将对象组织到树 结构中,可以用来描述整体与部分的关系。合成模式可以使客户端将单纯元素与 复合元素同等看待。 从和尚的故事谈起 这是小时候我奶奶讲的故事:从前有个山,山里有个庙,庙里有个老和尚在给小 和尚讲故事,讲的什么故事呢?从前有个山,山里有个庙……。奶奶的故事要循 环多少次,根据你多长时间睡着而定。在故事中有山、有庙、有和尚、有故事。 因此,故事的角色有两种:一种里面没有其它角色;另一种内部有其它角色。 对象的树结构 一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而 一个树叶节点不可以有子节点。除了根节点外,其它节点有且只有一个父节点。 注意:一个树枝节点可以不带任何叶子,但是它因为有带叶子的能力,因此仍然 是树枝节点,而不会成为叶节点。一个树叶节点永远不可能带有子节点。 二、 合成模式概述 下图所示的类图省略了各个角色的细节。 可以看出,上面的类图结构涉及到三个角色: 抽象构件(Component)角色:这是一个抽象角色,它给参与组合的对象规定一 个接口。这个角色给出共有接口及其默认行为。 树叶构件(Leaf)角色:代表参加组合的树叶对象。一个树叶对象没有下级子对 象。 树枝构件(Composite)角色:代表参加组合的有子对象的对象,并给出树枝构 件对象的行为。 可以看出,Composite 类型的对象可以包含其它 Component 类型的对象。换而言 之,Composite 类型对象可以含有其它的树枝(Composite)类型或树叶(Leaf) 类型的对象。 合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明 模式。合成模式可以不提供父对象的管理方法,但合成模式必须在合适的地方提 供子对象的管理方法(诸如:add、remove、getChild 等)。 透明方式 作为第一种选择,在Component 里面声明所有的用来管理子类对象的方法,包括 add()、remove(),以及 getChild()方法。这样做的好处是所有的构件类 都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层 次上消失了,客户端可以同等同的对待所有的对象。这就是透明形式的合成模式。 这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别 的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild ()方法没有意义,是在编译时期不会出错,而只会在运行时期才会出错。 安全方式 第二种选择是在 Composite 类里面声明所有的用来管理子类对象的方法。这样的 做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此, 如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。 这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。 这两个形式各有优缺点,需要根据软件的具体情况做出取舍决定。 三、 安全式的合成模式的结构 安全式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶 构件中。 这种形式涉及到三个角色: 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公 共的接口及其默认行为,可以用来管理所有的子对象。在安全式的合成模式里, 构件角色并不是定义出管理子对象的方法,这一定义由树枝构件对象给出。 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的 原始对象的行为。 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝对象 给出所有的管理子对象的方法,如 add()、remove()、getChild()等。 四、 安全式的合成模式实现 以下示例性代码演示了安全式的合成模式代码: // Composite pattern -- Structural example using System; using System.Text; using System.Collections; // "Component" abstract class Component { // Fields protected string name; // Constructors public Component( string name ) { this.name = name; } // Operation public abstract void Display( int depth ); } // "Composite" class Composite : Component { // Fields private ArrayList children = new ArrayList(); // Constructors public Composite( string name ) : base( name ) {} // Methods public void Add( Component component ) { children.Add( component ); } public void Remove( Component component ) { children.Remove( component ); } public override void Display( int depth ) { Console.WriteLine( new String( '-', depth ) + name ); // Display each of the node's children foreach( Component component in children ) component.Display( depth + 2 ); } } // "Leaf" class Leaf : Component { // Constructors public Leaf( string name ) : base( name ) {} // Methods public override void Display( int depth ) { Console.WriteLine( new String( '-', depth ) + name ); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Create a tree structure Composite root = new Composite( "root" ); root.Add( new Leaf( "Leaf A" )); root.Add( new Leaf( "Leaf B" )); Composite comp = new Composite( "Composite X" ); comp.Add( new Leaf( "Leaf XA" ) ); comp.Add( new Leaf( "Leaf XB" ) ); root.Add( comp ); root.Add( new Leaf( "Leaf C" )); // Add and remove a leaf Leaf l = new Leaf( "Leaf D" ); root.Add( l ); root.Remove( l ); // Recursively display nodes root.Display( 1 ); } } 五、 透明式的合成模式结构 与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论 树枝构件还是树叶构件,均符合一个固定的接口。 这种形式涉及到三个角色: 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一 个接口,规范共有的接口及默认行为。 树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象 的行为。树叶类会给出 add()、remove()以及 getChild()之类的用来管理 子类对对象的方法的平庸实现。 树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样的 对象的行为。 六、 透明式的合成模式实现 以下示例性代码演示了安全式的合成模式代码: // Composite pattern -- Structural example using System; using System.Text; using System.Collections; // "Component" abstract class Component { // Fields protected string name; // Constructors public Component( string name ) { this.name = name; } // Methods abstract public void Add(Component c); abstract public void Remove( Component c ); abstract public void Display( int depth ); } // "Composite" class Composite : Component { // Fields private ArrayList children = new ArrayList(); // Constructors public Composite( string name ) : base( name ) {} // Methods public override void Add( Component component ) { children.Add( component ); } public override void Remove( Component component ) { children.Remove( component ); } public override void Display( int depth ) { Console.WriteLine( new String( '-', depth ) + name ); // Display each of the node's children foreach( Component component in children ) component.Display( depth + 2 ); } } // "Leaf" class Leaf : Component { // Constructors public Leaf( string name ) : base( name ) {} // Methods public override void Add( Component c ) { Console.WriteLine("Cannot add to a leaf"); } public override void Remove( Component c ) { Console.WriteLine("Cannot remove from a leaf"); } public override void Display( int depth ) { Console.WriteLine( new String( '-', depth ) + name ); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Create a tree structure Composite root = new Composite( "root" ); root.Add( new Leaf( "Leaf A" )); root.Add( new Leaf( "Leaf B" )); Composite comp = new Composite( "Composite X" ); comp.Add( new Leaf( "Leaf XA" ) ); comp.Add( new Leaf( "Leaf XB" ) ); root.Add( comp ); root.Add( new Leaf( "Leaf C" )); // Add and remove a leaf Leaf l = new Leaf( "Leaf D" ); root.Add( l ); root.Remove( l ); // Recursively display nodes root.Display( 1 ); } } 七、 使用合成模式时考虑的几个问题 明显的给出父对象的引用。在子对象里面给出父对象的引用,可以很容易的遍历 所有父对象。有了这个引用,可以方便的应用责任链模式。 在通常的系统里,可以使用享元模式实现构件的共享,但是由于合成模式的对象 经常要有对父对象的引用,因此共享不容易实现。 有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构 件的结果暂时存储在父构件里面作为缓存。 关于使用什么数据类型来存储子对象的问题,在示意性的代码中使用了 ArrayList,在实际系统中可以使用其它聚集或数组等。 客户端尽量不要直接调用树叶类中的方法,而是借助其父类(Component)的多 态性完成调用,这样可以增加代码的复用性。 八、 和尚的故事 九、 一个实际应用 Composite 模式的例子 下面是一个实际应用中的程序,演示了通过一些基本图像元素(直线、园等)以 及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树的过程。 // Composite pattern -- Real World example using System; using System.Collections; // "Component" abstract class DrawingElement { // Fields protected string name; // Constructors public DrawingElement( string name ) { this.name = name; } // Operation abstract public void Display( int indent ); } // "Leaf" class PrimitiveElement : DrawingElement { // Constructors public PrimitiveElement( string name ) : base( name ) {} // Operation public override void Display( int indent ) { Console.WriteLine( new String( '-', indent ) + " draw a {0}", name ); } } // "Composite" class CompositeElement : DrawingElement { // Fields private ArrayList elements = new ArrayList(); // Constructors public CompositeElement( string name ) : base( name ) {} // Methods public void Add( DrawingElement d ) { elements.Add( d ); } public void Remove( DrawingElement d ) { elements.Remove( d ); } public override void Display( int indent ) { Console.WriteLine( new String( '-', indent ) + "+ " + name ); // Display each child element on this node foreach( DrawingElement c in elements ) c.Display( indent + 2 ); } } /**//// /// CompositeApp test /// public class CompositeApp { public static void Main( string[] args ) { // Create a tree structure CompositeElement root = new CompositeElement( "Picture" ); root.Add( new PrimitiveElement( "Red Line" )); root.Add( new PrimitiveElement( "Blue Circle" )); root.Add( new PrimitiveElement( "Green Box" )); CompositeElement comp = new CompositeElement( "Two Circles" ); comp.Add( new PrimitiveElement( "Black Circle" ) ); comp.Add( new PrimitiveElement( "White Circle" ) ); root.Add( comp ); // Add and remove a PrimitiveElement PrimitiveElement l = new PrimitiveElement( "Yellow Line" ); root.Add( l ); root.Remove( l ); // Recursively display nodes root.Display( 1 ); } } 合成模式与很多其它模式都有联系,将在后续内容中逐步介绍。 代理模式(Proxy Pattern) 2007-04-19 09:24 一、 代理(Proxy)模式 代理(Proxy)模式给某一个对象提供一个代理,并由代理对象控制对原对象的 引用。 代理模式的英文叫做 Proxy 或 Surrogate,中文都可译成"代理"。所谓代理,就 是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下, 一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对 象之间起到中介的作用。 二、 代理的种类 如果按照使用目的来划分,代理有以下几种: 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。 这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫 做大使(Ambassador)。 虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只 在需要时才会被真正创建。 Copy-on-Write 代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需 要时,才真正采取行动。 保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给 不同的用户提供不同级别的使用权限。 Cache 代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可 以共享这些结果。 防火墙(Firewall)代理:保护目标,不让恶意用户接近。 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲 突。 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操 作,比如将对此对象调用的次数记录下来等。 在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能 引用代理(Smart Reference Proxy)和保护(Protect or Access)代理是最为 常见的代理模式。 三、 远程代理的例子 Achilles 是一个用来测试网站的安全性能的工具软件。Achilles 相当于位于客 户端的的一个桌面代理服务器,在一个 HTTP 过程里起到一个中间人的作用,但 是 Achilles 与通常的代理服务器又有不同。Achilles 截获双向的通信数据,使 得 Achilles 软件的用户可以改变来自和发往网络服务器的数据,甚至可以拦截 并修改 SSL 通讯。(这点在《Java 与模式》中解释的不是很清楚,关于对非对 称密钥加密拦截、破解方法,可以参考我的另外一篇文章《通过代理截取并修改 非对称密钥加密信息》)。 另外一个例子就是 Windows 的快捷方式。快捷方式是它所引用的程序的一个代 理。 四、 代理模式的结构 代理模式的类图如下图所示: 代理模式所涉及的角色有: 抽象主题角色(Subject):声明了真实主题和代理主题的共同接口,这样一来 在任何使用真实主题的地方都可以使用代理主题。 代理主题(Proxy)角色:代理主题角色内部含有对真是主题的引用,从而可以 在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接 口,以便可以在任何时候都可以替代真实主体;控制真实主题的应用,负责在需 要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端 调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯的将调用传 递给真实主题对象。 真实主题角色(RealSubject)角色:定义了代理角色所代表的真实对象。 五、 代理模式示例性代码 以下示例性代码实现了代理模式: // Proxy pattern -- Structural example using System; // "Subject" abstract class Subject { // Methods abstract public void Request(); } // "RealSubject" class RealSubject : Subject { // Methods override public void Request() { Console.WriteLine("Called RealSubject.Request()"); } } // "Proxy" class Proxy : Subject { // Fields RealSubject realSubject; // Methods override public void Request() { // Uses "lazy initialization" if( realSubject == null ) realSubject = new RealSubject(); preRequest(); realSubject.Request(); postRequest(); } public void preRequest() { Console.WriteLine("PreRequest."); } public void postRequest() { Console.WriteLine("PostRequest."); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Create proxy and request a service Proxy p = new Proxy(); p.Request(); } } 六、 高老庄悟空降八戒 尽管那时候八戒还不叫八戒,但为了方便,这里仍然这样称呼他。 高老庄的故事 却说那春融时节,悟空牵着白马,与唐僧赶路西行。忽一日天色将晚,远远地望 见一村人,这就是高老庄,猪八戒的丈人高太公家。为了将高家三小姐解救出八 戒的魔掌,悟空决定扮做高小姐,会一会这个妖怪: "行者却弄神通,摇身一变,变得就如那女子一般,独自个坐在房里等那妖精。 不多时,一阵风来,真个是走石飞砂……那阵狂风过处,只见半空里来了一个妖 精,果然生得丑陋:黑脸短毛,长喙大耳,穿一领青不青、蓝不蓝的梭布直裰, 系一条花布手巾……走进房,一把搂住,就要亲嘴……" 高家三小姐的神貌和本人 悟空的下手之处是将高家三小姐的神貌和她本人分割开来,这和"开一闭"原则有 异曲同工之妙。这样一来,"高家三小姐本人"也就变成了"高家三小姐神貌"的具 体实现,而"高家三小姐神貌"则变成了抽象角色,如下图所示。 悟空扮演并代替高家三小姐 悟空巧妙地实现了"高家三小姐神貌",也就是说同样变成了"高家三小姐神貌" 的子类。悟空可以扮演高家三小姐,并代替高家三小姐会见八戒,其静态结构图 如下图所示。 悟空代替"高家三小姐本人"去会见猪八戒。显然这就是代理模式的应用。具体地 讲,这是保护代理模式的应用。只有代理对象认为合适时,才会将客户端的请求 传递给真实主题对象。 八戒分辨不出真假老婆 从《西游记》的描述可以看出,猪八戒根本份辨不出悟空扮演的"高家三小姐替 身"和 "高家三小姐本人"。客户端分辨不出代理主题对象与真实主题对象,这是 代理模式的一个 重要用意。 悟空代替高家三小姐会见八戒的对象图如下图所示。 七、 不同类型的代理模式 远程代理 可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认 为被代理的对象是局域的而不是远程的,而代理对象承担了大部分的网络通信工 作,远程代理的结构图如下图所示。 虚拟代理 使用虚拟代理模式的优点就是代理对象可以在必要的时候才将被代理的对象加 载。代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的 时候,虚拟代理的优点就非常明显。 保护代理 保护代理可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用 传递给被代理的对象。 智能引用代理 在访问一个对象时可以执行一些内务处理(Housekeeping)操作,比如计数操作 等。 八、 代理模式实际应用的例子 该例子演示了利用远程代理模式提供对另外一个应用程序域(AppDomain)的对 象进行访问控制。 // Proxy pattern -- Real World example using System; using System.Runtime.Remoting; // "Subject" public interface IMath { // Methods double Add( double x, double y ); double Sub( double x, double y ); double Mul( double x, double y ); double Div( double x, double y ); } // "RealSubject" class Math : MarshalByRefObject, IMath { // Methods public double Add( double x, double y ){ return x + y; } public double Sub( double x, double y ){ return x - y; } public double Mul( double x, double y ){ return x * y; } public double Div( double x, double y ){ return x / y; } } // Remote "Proxy Object" class MathProxy : IMath { // Fields Math math; // Constructors public MathProxy() { // Create Math instance in a different AppDomain AppDomain ad = System.AppDomain.CreateDomain("MathDomain",null, null ); ObjectHandle o = ad.CreateInstance("Proxy_RealWorld", "Math", false, System.Reflection.BindingFlags.CreateInstance, null, null, null,null,null ); math = (Math) o.Unwrap(); } // Methods public double Add( double x, double y ) { return math.Add(x,y); } public double Sub( double x, double y ) { return math.Sub(x,y); } public double Mul( double x, double y ) { return math.Mul(x,y); } public double Div( double x, double y ) { return math.Div(x,y); } } /**//// /// ProxyApp test /// public class ProxyApp { public static void Main( string[] args ) { // Create math proxy MathProxy p = new MathProxy(); // Do the math Console.WriteLine( "4 + 2 = {0}", p.Add( 4, 2 ) ); Console.WriteLine( "4 - 2 = {0}", p.Sub( 4, 2 ) ); Console.WriteLine( "4 * 2 = {0}", p.Mul( 4, 2 ) ); Console.WriteLine( "4 / 2 = {0}", p.Div( 4, 2 ) ); } } 装饰模式(Decorator Pattern) 2007-03-30 12:56 一、 装饰(Decorator)模式 装饰(Decorator)模式又名包装(Wrapper)模式[GOF95]。装饰模式以对客户 端透明的方式扩展对象的功能,是继承关系的一个替代方案。 引言 孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼 儿时,就可以到水里游泳;他变成雀儿时,就可以在天上飞行。而不管悟空怎么 变化,在二郎神眼里,他永远是那只猢狲。 装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客 户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造 更多子类的情况下,将对象的功能加以扩展。 二、 装饰模式的结构 装饰模式使用原来被装饰的类的一个子类的实例,把客户端的调用委派到被装饰 类。装饰模式的关键在于这种扩展是完全透明的。 在孙猴子的例子里,老孙变成的鱼儿相当于老孙的子类,这条鱼儿与外界的互动 要通过"委派",交给老孙的本尊,由老孙本尊采取行动。 装饰模式的类图如下图所示: 在装饰模式中的各个角色有: • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加 责任的对象。 • 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。 • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定 义一个与抽象构件接口一致的接口。 • 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责 任。 三、 装饰模式示例性代码 以下示例性代码实现了装饰模式: // Decorator pattern -- Structural example using System; // "Component" abstract class Component { // Methods abstract public void Operation(); } // "ConcreteComponent" class ConcreteComponent : Component { // Methods override public void Operation() { Console.WriteLine("ConcreteComponent.Operation()"); } } // "Decorator" abstract class Decorator : Component { // Fields protected Component component; // Methods public void SetComponent( Component component ) { this.component = component; } override public void Operation() { if( component != null ) component.Operation(); } } // "ConcreteDecoratorA" class ConcreteDecoratorA : Decorator { // Fields private string addedState; // Methods override public void Operation() { base.Operation(); addedState = "new state"; Console.WriteLine("ConcreteDecoratorA.Operation()"); } } // "ConcreteDecoratorB" class ConcreteDecoratorB : Decorator { // Methods override public void Operation() { base.Operation(); AddedBehavior(); Console.WriteLine("ConcreteDecoratorB.Operation()"); } void AddedBehavior() { } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Create ConcreteComponent and two Decorators ConcreteComponent c = new ConcreteComponent(); ConcreteDecoratorA d1 = new ConcreteDecoratorA(); ConcreteDecoratorB d2 = new ConcreteDecoratorB(); // Link decorators d1.SetComponent( c ); d2.SetComponent( d1 ); d2.Operation(); } } 上面的代码在执行装饰时是通过 SetComponent 方法实现的,在实际应用中,也 有通过构造函数实现的,一个典型的创建过程可能如下: new Decorator1( new Decorator2( new Decorator3( new ConcreteComponent() ) ) ) 装饰模式常常被称为包裹模式,就是因为每一个具体装饰类都将下一个具体装饰 类或者具体构件类包裹起来。 四、 装饰模式应当在什么情况下使用 在以下情况下应当使用装饰模式: 需要扩展一个类的功能,或给一个类增加附加责任。 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系 变得不现实。 五、 装饰模式实际应用的例子 该例子演示了通过装饰模式为图书馆的图书与录像带添加"可借阅"装饰。 // Decorator pattern -- Real World example using System; using System.Collections; // "Component" abstract class LibraryItem { // Fields private int numCopies; // Properties public int NumCopies { get{ return numCopies; } set{ numCopies = value; } } // Methods public abstract void Display(); } // "ConcreteComponent" class Book : LibraryItem { // Fields private string author; private string title; // Constructors public Book(string author,string title,int numCopies) { this.author = author; this.title = title; this.NumCopies = numCopies; } // Methods public override void Display() { Console.WriteLine( " Book ------ " ); Console.WriteLine( " Author: {0}", author ); Console.WriteLine( " Title: {0}", title ); Console.WriteLine( " # Copies: {0}", NumCopies ); } } // "ConcreteComponent" class Video : LibraryItem { // Fields private string director; private string title; private int playTime; // Constructor public Video( string director, string title, int numCopies, int playTime ) { this.director = director; this.title = title; this.NumCopies = numCopies; this.playTime = playTime; } // Methods public override void Display() { Console.WriteLine( " Video ----- " ); Console.WriteLine( " Director: {0}", director ); Console.WriteLine( " Title: {0}", title ); Console.WriteLine( " # Copies: {0}", NumCopies ); Console.WriteLine( " Playtime: {0}", playTime ); } } // "Decorator" abstract class Decorator : LibraryItem { // Fields protected LibraryItem libraryItem; // Constructors public Decorator ( LibraryItem libraryItem ) { this.libraryItem = libraryItem; } // Methods public override void Display() { libraryItem.Display(); } } // "ConcreteDecorator" class Borrowable : Decorator { // Fields protected ArrayList borrowers = new ArrayList(); // Constructors public Borrowable( LibraryItem libraryItem ) : base( libraryItem ) {} // Methods public void BorrowItem( string name ) { borrowers.Add( name ); libraryItem.NumCopies--; } public void ReturnItem( string name ) { borrowers.Remove( name ); libraryItem.NumCopies++; } public override void Display() { base.Display(); foreach( string borrower in borrowers ) Console.WriteLine( " borrower: {0}", borrower ); } } /**//// /// DecoratorApp test /// public class DecoratorApp { public static void Main( string[] args ) { // Create book and video and display Book book = new Book( "Schnell", "My Home", 10 ); Video video = new Video( "Spielberg", "Schindler's list", 23, 60 ); book.Display(); video.Display(); // Make video borrowable, then borrow and display Console.WriteLine( " Video made borrowable:" ); Borrowable borrowvideo = new Borrowable( video ); borrowvideo.BorrowItem( "Cindy Lopez" ); borrowvideo.BorrowItem( "Samuel King" ); borrowvideo.Display(); } } 六、 使用装饰模式的优点和缺点 使用装饰模式主要有以下的优点: 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继 承更多的灵活性。 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多 不同行为的组合。 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。 使用装饰模式主要有以下的缺点: 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当 然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关 系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相 像。 七、 模式实现的讨论 大多数情况下,装饰模式的实现都比上面定义中给出的示意性实现要简单。对模 式进行简化时需要注意以下的情况: (1)一个装饰类的接口必须与被装饰类的接口相容。 (2)尽量保持 Component 作为一个"轻"类,不要把太多的逻辑和状态放在 Component 类里。 (3)如果只有一个 ConcreteComponent 类而没有抽象的 Component 类(接口), 那么 Decorator 类经常可以是 ConcreteComponent 的一个子类。如下图所示: (4)如果只有一个 ConcreteDecorator 类,那么就没有必要建立一个单独的 Decorator 类,而可以把 Decorator 和 ConcreteDecorator 的责任合并成一个类。 八、 透明性的要求 透明的装饰模式 装饰模式通常要求针对抽象编程。装饰模式对客户端的透明性要求程序不要声明 一个 ConcreteDecorator 类型的变量,而应当声明一个 Component 类型的变量。 换言之,下面的做法是对的: Component c = new ConcreteComponent(); Component c1 = new ConcreteDecorator1(c); Component c2 = new ConcreteDecorator(c1); 而下面的做法是不对的: ConcreteComponent c = new ConcreteDecorator(); 这就是前面所说的,装饰模式对客户端是完全透明的含义。 用孙悟空的例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如 果把老孙变成的雀儿当成雀儿,而不是老孙,那就被老孙骗了,而这是不应当发 生的。 下面的做法是不对的: 大圣本尊 c = new 大圣本尊(); 雀儿 bird = new 雀儿 (c); 半透明的装饰模式 然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增 强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。即便 是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而 雀儿有。这就意味着雀儿应当有一个新的 fly()方法。 这就导致了大多数的装饰模式的实现都是"半透明"(semi-transparent)的,而 不是完全"透明"的。换言之,允许装饰模式改变接口,增加新的方法。即声明 ConcreteDecorator 类型的变量,从而可以调用 ConcreteDecorator 类中才有的 方法: 齐天大圣 c = new 大圣本尊(); 雀儿 bird = new 雀儿(c); bird.fly(); 齐天大圣接口根本没有 fly()这个方法,而雀儿接口里有这个方法。 九、 装饰模式在.NET 中的应用 .net 中存在如下类模型: 下面的代码段用来将 XmlDocument 的内容格式输出。我们可以体会 Decorator 模式在这里所起的作用。 // 生成 ConcreteComponent(内存流 ms) MemoryStream ms = new MemoryStream(); // 用 XmlTextWriter 对内存流 ms 进行装饰 // 此处使用了半透明的装饰模式 XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8); xtw.Formatting = Formatting.Indented; // 对装饰 xtw 的操作会转而操作本体-内存流 ms xmlDoc.Save(xtw); byte[] buf = ms.ToArray(); txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length); xtw.Close(); 享元模式(Flyweight Pattern) 2007-04-19 09:18 一、 享元(Flyweight)模式 Flyweight 在拳击比赛中指最轻量级,即"蝇量级",有些作者翻译为"羽量级"。 这里使用"享元模式"更能反映模式的用意。 享元模式以共享的方式高效地支持大量的细粒度对象。享元对象能做到共享的关 键是区分内蕴状态(Internal State)和外蕴状态(External State)。内蕴状 态是存储在享元对象内部并且不会随环境改变而改变。因此内蕴状态并可以共 享。 外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须 由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象 内部。外蕴状态与内蕴状态是相互独立的。 享元模式的应用 享元模式在编辑器系统中大量使用。一个文本编辑器往往会提供很多种字体,而 通常的做法就是将每一个字母做成一个享元对象。享元对象的内蕴状态就是这个 字母,而字母在文本中的位置和字模风格等其他信息则是外蕴状态。比如,字母 a 可能出现在文本的很多地方,虽然这些字母 a 的位置和字模风格不同,但是所 有这些地方使用的都是同一个字母对象。这样一来,字母对象就可以在整个系统 中共享。 二、 单纯享元模式的结构 在单纯享元模式中,所有的享元对象都是可以共享的。单纯享元模式所涉及的角 色如下: 抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定 出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调 用商业方法以参数形式传入。 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有 内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与 对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。 享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必 须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的 时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已 经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适 当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。 客户端(Client)角色:本角色需要维护一个对所有享元对象的引用。本角色需要 自行存储所有享元对象的外蕴状态。 三、 单纯享元模式的示意性源代码 // Flyweight pattern -- Structural example using System; using System.Collections; // "FlyweightFactory" class FlyweightFactory { // Fields private Hashtable flyweights = new Hashtable(); // Constructors public FlyweightFactory() { flyweights.Add("X", new ConcreteFlyweight()); flyweights.Add("Y", new ConcreteFlyweight()); flyweights.Add("Z", new ConcreteFlyweight()); } // Methods public Flyweight GetFlyweight(string key) { return((Flyweight)flyweights[ key ]); } } // "Flyweight" abstract class Flyweight { // Methods abstract public void Operation( int extrinsicstate ); } // "ConcreteFlyweight" class ConcreteFlyweight : Flyweight { private string intrinsicstate = "A"; // Methods override public void Operation( int extrinsicstate ) { Console.WriteLine("ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}", intrinsicstate, extrinsicstate ); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Arbitrary extrisic state int extrinsicstate = 22; FlyweightFactory f = new FlyweightFactory(); // Work with different flyweight instances Flyweight fx = f.GetFlyweight("X"); fx.Operation( --extrinsicstate ); Flyweight fy = f.GetFlyweight("Y"); fy.Operation( --extrinsicstate ); Flyweight fz = f.GetFlyweight("Z"); fz.Operation( --extrinsicstate ); } } 四、 复合享元模式的结构 单纯享元模式中,所有的享元对象都可以直接共享。下面考虑一个较为复杂的情 况,即将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合 享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。 复合享元模式的类图如下图所示: 享元模式所涉及的角色有抽象享元角色、具体享元角色、复合享元角色、享员工 厂角色,以及客户端角色等。 抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的 公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传 入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非 所有的享元对象都是可以共享的。 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有 内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与 对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享 元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过 复合而成的。 复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共 享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复 合享元角色又称做不可共享的享元对象。 享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必 须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的 时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果 已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个 适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。 客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。 注:由于复合享元模式比较复杂,这里就不再给出示意性代码。通过将享元模式 与合成模式组合在一起,可以确保复合享元中所包含的每个单纯享元都具有相同 的外蕴状态,而这些单纯享元的内蕴状态往往不同。该部分内容可以参考《Java 与模式》第 31 章内容。 五、 一个咖啡摊的例子 在这个咖啡摊(Coffee Stall)所使用的系统里,有一系列的咖啡"风味 (Flavor)"。客人到摊位上购买咖啡,所有的咖啡均放在台子上,客人自己拿到 咖啡后就离开摊位。咖啡有内蕴状态,也就是咖啡的风味;咖啡没有环境因素, 也就是说没有外蕴状态。如果系统为每一杯咖啡都创建一个独立的对象的话,那 么就需要创建出很多的细小对象来。这样就不如把咖啡按照种类(即"风味")划 分,每一种风味的咖啡只创建一个对象,并实行共享。 使用咖啡摊主的语言来讲,所有的咖啡都可按"风味"划分成如 Capucino、 Espresso 等,每一种风味的咖啡不论卖出多少杯,都是全同、不可分辨的。所 谓共享,就是咖啡风味的共享,制造方法的共享等。因此,享元模式对咖啡摊来 说,就意味着不需要为每一份单独调制。摊主可以在需要时,一次性地调制出足 够一天出售的某一种风味的咖啡。 很显然,这里适合使用单纯享元模式。系统的设计如下: using System; using System.Collections; public abstract class Order { // 将咖啡卖给客人 public abstract void Serve(); // 返回咖啡的名字 public abstract string GetFlavor(); } public class Flavor : Order { private string flavor; // 构造函数,内蕴状态以参数方式传入 public Flavor(string flavor) { this.flavor = flavor; } // 返回咖啡的名字 public override string GetFlavor() { return this.flavor; } // 将咖啡卖给客人 public override void Serve() { Console.WriteLine("Serving flavor " + flavor); } } public class FlavorFactory { private Hashtable flavors = new Hashtable(); public Order GetOrder(string key) { if(! flavors.ContainsKey(key)) flavors.Add(key, new Flavor(key)); return ((Order)flavors[key]); } public int GetTotalFlavorsMade() { return flavors.Count; } } public class Client { private static FlavorFactory flavorFactory; private static int ordersMade = 0; public static void Main( string[] args ) { flavorFactory = new FlavorFactory(); TakeOrder("Black Coffee"); TakeOrder("Capucino"); TakeOrder("Espresso"); TakeOrder("Capucino"); TakeOrder("Espresso"); TakeOrder("Black Coffee"); TakeOrder("Espresso"); TakeOrder("Espresso"); TakeOrder("Black Coffee"); TakeOrder("Capucino"); TakeOrder("Capucino"); TakeOrder("Black Coffee"); Console.WriteLine("\nTotal Orders made: " + ordersMade); Console.WriteLine("\nTotal Flavor objects made: " + flavorFactory.GetTotalFlavorsMade()); } private static void TakeOrder(string aFlavor) { Order o = flavorFactory.GetOrder(aFlavor); // 将咖啡卖给客人 o.Serve(); ordersMade++; } } 六、 咖啡屋的例子 在前面的咖啡摊项目里,由于没有供客人坐的桌子,所有的咖啡均没有环境的影 响。换言之,咖啡仅有内蕴状态,也就是咖啡的种类,而没有外蕴状态。 下面考虑一个规模稍稍大一点的咖啡屋(Coffee Shop)项目。屋子里有很多的桌 子供客人坐,系统除了需要提供咖啡的"风味"之外,还需要跟踪咖啡被送到哪一 个桌位上,因此,咖啡就有了桌子作为外蕴状态。 由于外蕴状态的存在,没有外蕴状态的单纯享元模式不再符合要求。系统的设计 可以利用有外蕴状态的单纯享元模式。系统的代码如下: using System; using System.Collections; public abstract class Order { // 将咖啡卖给客人 public abstract void Serve(Table table); // 返回咖啡的名字 public abstract string GetFlavor(); } public class Flavor : Order { private string flavor; // 构造函数,内蕴状态以参数方式传入 public Flavor(string flavor) { this.flavor = flavor; } // 返回咖啡的名字 public override string GetFlavor() { return this.flavor; } // 将咖啡卖给客人 public override void Serve(Table table) { Console.WriteLine("Serving table {0} with flavor {1}", table.Number, flavor); } } public class FlavorFactory { private Hashtable flavors = new Hashtable(); public Order GetOrder(string key) { if(! flavors.ContainsKey(key)) flavors.Add(key, new Flavor(key)); return ((Order)flavors[key]); } public int GetTotalFlavorsMade() { return flavors.Count; } } public class Table { private int number; public Table(int number) { this.number = number; } public int Number { get { return number; } } } public class Client { private static FlavorFactory flavorFactory; private static int ordersMade = 0; public static void Main( string[] args ) { flavorFactory = new FlavorFactory(); TakeOrder("Black Coffee"); TakeOrder("Capucino"); TakeOrder("Espresso"); TakeOrder("Capucino"); TakeOrder("Espresso"); TakeOrder("Black Coffee"); TakeOrder("Espresso"); TakeOrder("Espresso"); TakeOrder("Black Coffee"); TakeOrder("Capucino"); TakeOrder("Capucino"); TakeOrder("Black Coffee"); Console.WriteLine("\nTotal Orders made: " + ordersMade); Console.WriteLine("\nTotal Flavor objects made: " + flavorFactory.GetTotalFlavorsMade()); } private static void TakeOrder(string aFlavor) { Order o = flavorFactory.GetOrder(aFlavor); // 将咖啡卖给客人 o.Serve(new Table(++ordersMade)); } } 七、 享元模式应当在什么情况下使用 当以下所有的条件都满足时,可以考虑使用享元模式: 一个系统有大量的对象。 这些对象耗费大量的内存。 这些对象的状态中的大部分都可以外部化。 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一 个组都可以仅用一个对象代替。 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。 满足以上的这些条件的系统可以使用享元对象。 最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗 费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。 八、 享元模式的优点和缺点 享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所 付出的代价也是很高的: 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这 使得程序的逻辑复杂化。 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。 外观模式(Facade Pattern) 2009-01-03 14:11 一、 门面(Facade)模式 外部与一个子系统的通信必须通过一个统一的门面(Facade)对象进行,这就是 门面模式。 医院的例子 用一个例子进行说明,如果把医院作为一个子系统,按照部门职能,这个系统 可以划分为挂号、门诊、划价、化验、收费、取药等。看病的病人要与这些部 门打交道,就如同一个子系统的客户端与一个子系统的各个类打交道一样,不 是一件容易的事情。 首先病人必须先挂号,然后门诊。如果医生要求化验,病人必须首先划价,然 后缴款,才能到化验部门做化验。化验后,再回到门诊室。 解决这种不便的方法便是引进门面模式。可以设置一个接待员的位置,由接待 员负责代为挂号、划价、缴费、取药等。这个接待员就是门面模式的体现,病 人只接触接待员,由接待员负责与医院的各个部门打交道。 什么是门面模式 门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面 (Facade)对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。 就如同医院的接待员一样,门面模式的门面类将客户端与子系统的内部复杂性 分隔开,使得客户端只需要与门面对象打交道,而不需要与子系统内部的很多 对象打交道。 二、 门面模式的结构 门面模式是对象的结构模式。门面模式没有一个一般化的类图描述,下图演示 了一个门面模式的示意性对象图: 在这个对象图中,出现了两个角色: 门面(Facade)角色:客户端可以调用这个角色的方法。此角色知晓相关的(一个 或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来 的请求委派到相应的子系统去。 子系统(subsystem)角色:可以同时有一个或者多个子系统。每一个子系统都不 是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用, 或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅 仅是另外一个客户端而已。 三、 门面模式的实现 一个系统可以有几个门面类 【GOF】的书中指出:在门面模式中,通常只需要一个门面类,并且此门面类只 有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只能有 一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个 系统有好几个子系统的话,每一个子系统有一个门面类,整个系统可以有数个 门面类。 为子系统增加新行为 初学者往往以为通过继承一个门面类便可在子系统中加入新的行为,这是错误 的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向 子系统加入新的行为。 四、 在什么情况下使用门面模式 为一个复杂子系统提供一个简单接口 提高子系统的独立性 在层次化结构中,可以使用 Facade 模式定义系统中每一层的入口。 五、 一个例子 我们考察一个保安系统的例子,以说明门面模式的功效。一个保安系统由两个 录像机、三个电灯、一个遥感器和一个警报器组成。保安系统的操作人员需要 经常将这些仪器启动和关闭。 不使用门面模式的设计 首先,在不使用门面模式的情况下,操作这个保安系统的操作员必须直接操作 所有的这些部件。下图所示就是在不使用门面模式的情况下系统的设计图。 可以看出,Client 对象需要引用到所有的录像机(Camera)、电灯(Light)、感 应器(Sensor)和警报器(Alarm)对象。代码如下: using System; public class Camera { public void TurnOn() { Console.WriteLine("Turning on the camera."); } public void TurnOff() { Console.WriteLine("Turning off the camera."); } public void Rotate(int degrees) { Console.WriteLine("Rotating the camera by {0} degrees.", degrees); } } public class Light { public void TurnOff() { Console.WriteLine("Turning on the light."); } public void TurnOn() { Console.WriteLine("Turning off the light."); } public void ChangeBulb() { Console.WriteLine("changing the light-bulb."); } } public class Sensor { public void Activate() { Console.WriteLine("Activating the sensor."); } public void Deactivate() { Console.WriteLine("Deactivating the sensor."); } public void Trigger() { Console.WriteLine("The sensor has triggered."); } } public class Alarm { public void Activate() { Console.WriteLine("Activating the alarm."); } public void Deactivate() { Console.WriteLine("Deactivating the alarm."); } public void Ring() { Console.WriteLine("Ringing the alarm."); } public void StopRing() { Console.WriteLine("Stop the alarm."); } } public class Client { private static Camera camera1, camera2; private static Light light1, light2, light3; private static Sensor sensor; private static Alarm alarm; static Client() { camera1 = new Camera(); camera2 = new Camera(); light1 = new Light(); light2 = new Light(); light3 = new Light(); sensor = new Sensor(); alarm = new Alarm(); } public static void Main( string[] args ) { camera1.TurnOn(); camera2.TurnOn(); light1.TurnOn(); light2.TurnOn(); light3.TurnOn(); sensor.Activate(); alarm.Activate(); } } 六、 使用门面模式的设计 一个合情合理的改进方法就是准备一个系统的控制台,作为保安系统的用户界 面。如下图所示: 程序代码如下: using System; public class Camera { public void TurnOn() { Console.WriteLine("Turning on the camera."); } public void TurnOff() { Console.WriteLine("Turning off the camera."); } public void Rotate(int degrees) { Console.WriteLine("Rotating the camera by {0} degrees.", degrees); } } public class Light { public void TurnOff() { Console.WriteLine("Turning on the light."); } public void TurnOn() { Console.WriteLine("Turning off the light."); } public void ChangeBulb() { Console.WriteLine("changing the light-bulb."); } } public class Sensor { public void Activate() { Console.WriteLine("Activating the sensor."); } public void Deactivate() { Console.WriteLine("Deactivating the sensor."); } public void Trigger() { Console.WriteLine("The sensor has triggered."); } } public class Alarm { public void Activate() { Console.WriteLine("Activating the alarm."); } public void Deactivate() { Console.WriteLine("Deactivating the alarm."); } public void Ring() { Console.WriteLine("Ringing the alarm."); } public void StopRing() { Console.WriteLine("Stop the alarm."); } } public class SecurityFacade { private static Camera camera1, camera2; private static Light light1, light2, light3; private static Sensor sensor; private static Alarm alarm; static SecurityFacade() { camera1 = new Camera(); camera2 = new Camera(); light1 = new Light(); light2 = new Light(); light3 = new Light(); sensor = new Sensor(); alarm = new Alarm(); } public void Activate() { camera1.TurnOn(); camera2.TurnOn(); light1.TurnOn(); light2.TurnOn(); light3.TurnOn(); sensor.Activate(); alarm.Activate(); } public void Deactivate() { camera1.TurnOff(); camera2.TurnOff(); light1.TurnOff(); light2.TurnOff(); light3.TurnOff(); sensor.Deactivate(); alarm.Deactivate(); } } public class Client { private static SecurityFacade security; public static void Main( string[] args ) { security = new SecurityFacade(); security.Activate(); Console.WriteLine("\n--------------------\n"); security.Deactivate(); } } 一、迭代器模式:Iterator Pattern Define:Provide a way to access the elements of an aggregate object seque ntially without exposing its underlying representation. 定义:提供一种方式可以连续的访问几何对象的所有元素而无须关注内在的描述方式。 二、UML 图 • Iterator (AbstractIterator) • 定义一个访问和操作元素的接口 • ConcreteIterator (Iterator) • 实现迭代器的接口 • keeps track of the current position in the traversal of the aggregate. • Aggregate (AbstractCollection) • 定义一个接口用于创建一个迭代器对象 • ConcreteAggregate (Collection) • 实现迭代器创建的接口并且返回一个合适的 ConcreteIterator 实例对象 三、迭代器模式实例代码 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace Iterator_Pattern { /// /// MainApp startup class for Structural /// Iterator Design Pattern. /// class MainApp { 四、一个使用迭代器模式的例子 该例子借助迭代器模式实现以下操作:迭代访问 items 的元素并且跳过每个迭代过程中 it ems 中指定的数字。 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace Iterator_Pattern { /// /// MainApp startup class for Real-World /// Iterator Design Pattern. /// class MainApp { 观察者模式组图(Observer Pattern) 2008-11-22 11:31 描述: 在设计一组依赖的对象与它们所依赖的对象之间一致(同步)的交流模型时, 观察者模式(Observer Pattern)很有用。它可以使依赖对象的状态与它们所依 赖的对象的状态保持同步。这组依赖的对象指的是观察者(Observer),它们所 依赖的对象称为主题(Subject)。为了实现观察者(Observer)的状态与主题 (Subject)保持同步,观察者模式(Observer Pattern) 推荐采用发布者--订阅者(publisher--subscriber)模型,以使这组观察 者(Observer)和主题(Subject)对象之间有清晰的界限。 典型的观察者(Observer)是一个依赖于或者关注于主题对象的状态的对象。 一个主题可以有一个或者多个观察者。这些观察者在主体的状态发生变化时,需 要得到通知。 由于给定主体的观察者链表需要动态的变化,因此一个主题不能维护一个静 态的观察者链表。因此关注于主题状态的任何对象都需要明确地注册自己为主体 的一个观察者。主题状态发生的变化,都需要通知所有的以注册的观察者。从主 题接到通知以后,每一个观察者查询主题,使自己的状态与主题的同步。因此一 个主题扮演着发布者的角色,发布信息到所有的以订阅的观察者。 换句话说,主题和它的观察者之间包含了一对多的关系。当主题的实例的状 态发生变化时,所有的依赖于它的观察者都会得到通知并更新自己。每一个观察 者对象需要向主题注册,当主题的状态发生变化的时候得到通知。一个观察者可 以注册或者订阅多个主题。当观察者不希望再得到通知时,它可以向主题进行注 销。 为了实现这种机制: (1) 主题需要为注册和注销通知提供一个接口。 (2) 下面的两点也需要满足: A、 拉模型(In the pull model)--主题需要提供一个接口,可以使观 察者查询主题获得需要的状态信息来更新自己的状态。 B、 推模型(In the push model)--主题发送观察者可能关注的状态信 息。 (3) 观察者需要提供一个可以从主题接受通知的接口。 类图(图 33.1)描述了为满足于以上需求,不同类的结构和它们之间的关 联关系。 Figure 33.1: Generic Class Association When the Observer Pattern Is Applied 从这个类图可以看到: (1) 所有的主题需要提供一个类似于 Observable 接口的实现。 (2) 所有的观察者需要提供一个类似于 Observer 接口的实现。 在应用观察者模式时,有几种变体。这就会产生不同类型的主题--观察者模 式,例如,观察者仅关注主体特定类型的变化等。 增加新的观察者: 应用观察者模式以后,在不影响主题类的情况下,可以动态的加入不同的观 察者。同样,主题的状态变化逻辑改变时,观察者也不会受到影响。 例子: 为了管理一个卖厂多个分类产品,让我们建立一个销售报表系统。这个系统 有以下特征: (1) 用户可以选择一个他们感兴趣的分类 (2) 在选择了一个分类以后,需要显示下面的两种类型的报表。 A、 月度报表(Monthly report)--所选分类当月的所有交易清单。 B、 年度累积额(YTD sales chart)--以月为单位显示选择分类的年度累 积额图。 (3) 当一个不同的分类被选择时,两种报表的数据会被刷新,显示当 前所选分类的报表。 为了实现以上期望的功能,我们很容易的看到两个报表对象依赖于持有用户 选择分类的对象。应用观察者模式于此场景,我们可以设计一个介于持有用户选 择分类的对象和两个报表对象之间一个一致(同步)的交流模型。 让我们定义三个类,它们的功能如表 33.1 所描述: Table 33.1: Subject-Observer Classes public interface Observable { public void notifyObservers(); public void register(Observer obs); public void unRegister(Observer obs); } ReportManager 类(Listing33.1)提供了声明在 Observable 接口中方法的 实现。两个依赖于ReportManager 的报表对象使用这些方法注册它们自己为观察 者。ReportManager 把这些注册的观察者保存到 observersList 矢量(vector) 中。当前选择的分类构成了ReportManager 对象的状态,它以实例变量的形式保 存在变量 department 中。当为 department 设置一个新的值时(也就是 ReportManager 对象的状态改变),notifyObservers 方法被调用。作为 notifyObservers 方法的一部分,ReportManager 调用注册为观察者的 refreshData(Observable)方法。 Listing 33.1: ReportManager Class public class ReportManager extends JFrame implements Observable { … … private Vector observersList; private String department; public ReportManager() throws Exception { … … observersList = new Vector(); … … } public void register(Observer obs) { //Add to the list of Observers observersList.addElement(obs); } public void unRegister(Observer obs) { //remove from the list of Observers } public void notifyObservers() { //Send notify to all Observers for (int i = 0; i < observersList.size(); i++) { Observer observer = (Observer) observersList.elementAt(i); observer.refreshData(this); } } public String getDepartment() { return department; } public void setDepartment(String dept) { department = dept; } class ButtonHandler implements ActionListener { ReportManager subject; public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals(ReportManager.EXIT)) { System.exit(1); } if (e.getActionCommand().equals(ReportManager.SET_OK)) { String dept = (String) cmbDepartmentList.getSelectedItem(); //change in state subject.setDepartment(dept); subject.notifyObservers(); } } public ButtonHandler() { } public ButtonHandler(ReportManager manager) { subject = manager; } } }//end of class 除了提供 Observable 接口方法的实现,ReportManager 还显示了必要的用 户接口,允许用户选择一个特定的、关注的分类。 让我们定义接口 Observer 的两个实现:MonthlyReport 和 YTDChart 类 public interface Observer { public void refreshData(Observable subject); } Figure 33.2: Observer Class Hierarchy Listing 33.2: MonthlyReport Class as an Observer public class MonthlyReport extends JFrame implements Observer { … … private ReportManager objReportManager; public MonthlyReport(ReportManager inp_objReportManager) throws Exception { super("Observer Pattern -- Example"); objReportManager = inp_objReportManager; //Create controls … … //Create Labels … … objReportManager.register(this); } public void refreshData(Observable subject) { if (subject == objReportManager) { //get subject's state String department = objReportManager.getDepartment(); lblTransactions.setText( "Current Month Transactions - " + department); Vector trnList = getCurrentMonthTransactions(department); String content = ""; for (int i = 0; i < trnList.size(); i++) { content = content + trnList.elementAt(i).toString() + "\n"; } taTransactions.setText(content); } } private Vector getCurrentMonthTransactions(String department ) { Vector v = new Vector(); FileUtil futil = new FileUtil(); Vector allRows = futil.fileToVector("Transactions.date"); //current month Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); int month = cal.get(Calendar.MONTH) + 1; String searchStr = department + "," + month + ","; int j = 1; for (int i = 0; i < allRows.size(); i++) { String str = (String) allRows.elementAt(i); if (str.indexOf(searchStr) > ?1) { StringTokenizer st = new StringTokenizer(str, ","); st.nextToken();//bypass the department str = " " + j + ". " + st.nextToken() + "/" + st.nextToken() + "~~~" + st.nextToken() + "Items" + "~~~" + st.nextToken() + " Dollars"; j++; v.addElement(str); } } return v; } }//end of class ReportManager 利用这个接口通知它的所有观察者。 主题--观察者的关联(Subject--Observer Association) 通常,一个客户首先需要创建一个主题(ReportManager)实例,当一个观 察者(例如:MonthlyReport,YTDChart)对象被创建。客户把主题ReportManager 实例的引用传递给观察者的构造函数,观察者将自身注册到当前主题实例上。 //Client Code public class SupervisorView { … … public static void main(String[] args) throws Exception { //Create the Subject ReportManager objSubject = new ReportManager(); //Create Observers new MonthlyReport(objSubject); new YTDChart(objSubject); } }//end of class 类之间的关联描述如下: Figure 33.3: Example Application--Class Association 逻辑流程: (1) 使用 ReportManager 用户接口,当用户选择一个特定的分类并且点 击 OK 按钮时,ReportManager 的内部状态被被改变(例如,ReportManager 实例 变量 department 的值发生改变)。 (2) 新的状态一旦被设置,ReportManager 调用两个注册的观察者 MonthlyReport 和 YTDChart 的 refreshData(Observable)方法。 (3) 作为 refreshData 方法的一部分,两个 report 对象需要: A、 检查以确保调用 refreshData 方法的主题和观察者这册的主题是同 一个主题。这就避免了观察者响应不必要的调用。 B、 使用 getDepartment 方法查询 ReportManager 的当前状态。 C、 从数据文件中提取响应的数据显示。 Figure 33.4: MonthlyReport View 当 ReportManager 的状态变化逻辑实现需要改变时,任何观察者不受影响。 同样,当一个新的观察者被加入时,ReportManager 类不需要任何变化。 介绍 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立 地改变它们之间的交互。 示例 有一个 Message 实体类,某个对象对它的操作有 Send()和 Insert()方法,现在用一个中介对象来封装这一系列的对 象交互。 MessageModel using System; using System.Collections.Generic; using System.Text; namespace Pattern.Mediator { /// /// Message 实体类 /// public class MessageModel { /// /// 构造函数 /// /// Message 内容 /// Message 发布时间 public MessageModel(string msg, DateTime pt) { this._message = msg; this._publishTime = pt; } private string _message; /// /// Message 内容 /// public string Message { get { return _message; } set { _message = value; } } private DateTime _publishTime; /// /// Message 发布时间 /// public DateTime PublishTime { get { return _publishTime; } set { _publishTime = value; } } } } AbstractMessageMediator using System; using System.Collections.Generic; using System.Text; namespace Pattern.Mediator { /// /// 抽象中介者(Mediator) /// public abstract class AbstractMessageMediator { /// /// 注册一个操作 Message 的对象 /// /// AbstractMessage public abstract void Register(AbstractMessage AbstractMessage); /// /// 发送 Message /// /// 来自 UserId /// 发送到 UserId /// Message 实体对象 /// public abstract string Send(string from, string to, MessageModel mm); } } MessageMediator using System; using System.Collections.Generic; using System.Text; namespace Pattern.Mediator { /// /// 中介者(ConcreteMediator) /// public class MessageMediator : AbstractMessageMediator { private Dictionary _dictionary = new Dictionary (); /// /// 注册一个操作 Message 的对象 /// /// AbstractMessage public override void Register(AbstractMessage abstractMessage) { if (!_dictionary.ContainsKey(abstractMessage.UserId)) { _dictionary.Add(abstractMessage.UserId, abstractMessage); } abstractMessage.AbstractMessageMediator = this; } /// /// 发送 Message /// /// 来自 UserId /// 发送到 UserId /// Message 实体对象 /// public override string Send(string from, string to, MessageModel mm) { AbstractMessage abstractMessage = _dictionary[to]; if (abstractMessage != null) { return abstractMessage.Insert(from, mm); } else { return null; } } } } AbstractMessage using System; using System.Collections.Generic; using System.Text; namespace Pattern.Mediator { /// /// 操作 Message 抽象类(Colleague) /// public abstract class AbstractMessage { private AbstractMessageMediator _abstractMessageMediator; private string _userId; /// /// 构造函数 /// /// UserId public AbstractMessage(string userId) { this._userId = userId; } /// /// UserId /// public string UserId { get { return _userId; } } /// /// 中介者 /// public AbstractMessageMediator AbstractMessageMediator { get { return _abstractMessageMediator; } set { _abstractMessageMediator = value; } } /// /// 发送 Message(由客户端调用) /// /// 发送到 UserId /// Message 实体对象 /// public string Send(string to, MessageModel mm) { return _abstractMessageMediator.Send(_userId, to, mm); } /// /// 接受 Message(由中介者调用) /// /// 来自 UserId /// Message 实体对象 /// public abstract string Insert(string from, MessageModel mm); } } SqlMessage using System; using System.Collections.Generic; using System.Text; namespace Pattern.Mediator { /// /// Sql 方式操作 Message(ConcreteColleague) /// public class SqlMessage : AbstractMessage { /// /// 构造函数 /// /// UserId public SqlMessage(string userId) : base(userId) { } /// /// 接受 Message(由中介者调用) /// /// 来自 UserId /// Message 实体对象 /// public override string Insert(string from, MessageModel mm) { return "Sql 方式插入 Message(" + from + "发送给" + base.UserId + ")" + " - 内容:" + mm.Message + " - 时间:" + mm.PublishTime.ToString(); } } } XmlMessage using System; using System.Collections.Generic; using System.Text; namespace Pattern.Mediator { /// /// Xml 方式操作 Message(ConcreteColleague) /// public class XmlMessage : AbstractMessage { /// /// 构造函数 /// /// UserId public XmlMessage(string userId) : base(userId) { } /// /// 接受 Message(由中介者调用) /// /// 来自 UserId /// Message 实体对象 /// public override string Insert(string from, MessageModel mm) { return "Xml 方式插入 Message(" + from + "发送给" + base.UserId + ")" + " - 内容:" + mm.Message + " - 时间:" + mm.PublishTime.ToString(); } } } Test using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using Pattern.Mediator; public partial class Mediator : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { AbstractMessageMediator messageMediator = new MessageMediator(); AbstractMessage user1 = new SqlMessage("user1"); AbstractMessage user2 = new SqlMessage("user2"); AbstractMessage user3 = new XmlMessage("user3"); AbstractMessage user4 = new XmlMessage("user4"); messageMediator.Register(user1); messageMediator.Register(user2); messageMediator.Register(user3); messageMediator.Register(user4); Response.Write(user1.Send("user2", new MessageModel("你好!", DateTim e.Now))); Response.Write("
"); Response.Write(user2.Send("user1", new MessageModel("我不好!", DateTi me.Now))); Response.Write("
"); Response.Write(user1.Send("user2", new MessageModel("不好就不好吧。 ", DateTime.Now))); Response.Write("
"); Response.Write(user3.Send("user4", new MessageModel("吃了吗?", DateTi me.Now))); Response.Write("
"); Response.Write(user4.Send("user3", new MessageModel("没吃,你请我? ", DateTime.Now))); Response.Write("
"); Response.Write(user3.Send("user4", new MessageModel("不请。", DateTim e.Now))); Response.Write("
"); } } 运行结果 Sql 方式插入 Message(user1 发送给 user2) - 内容:你好! - 时间:2007-5-19 23:43:19 Sql 方式插入 Message(user2 发送给 user1) - 内容:我不好! - 时间:2007-5-19 23:43:19 Sql 方式插入 Message(user1 发送给 user2) - 内容:不好就不好吧。 - 时间:2007-5-19 23:43:19 Xml 方式插入 Message(user3 发送给 user4) - 内容:吃了吗? - 时间:2007-5-19 23:43:19 Xml 方式插入 Message(user4 发送给 user3) - 内容:没吃,你请我? - 时间:2007-5-19 23:43:19 Xml 方式插入 Message(user3 发送给 user4) - 内容:不请。 - 时间:2007-5-19 23:43:19 备忘录模式(Memento pattern) 2007-03-30 13:08 一、引子 俗话说:世上难买后悔药。所以凡事讲究个“三思而后行”,但总常见有人 做“痛心疾首”状:当初我要是……。如果真的有《大话西游》中能时光倒流的 “月光宝盒”,那这世上也许会少一些伤感与后悔——当然这只能是痴人说梦 了。 但是在我们手指下的程序世界里,却有的后悔药买。今天我们要讲的备忘录 模式便是程序世界里的“月光宝盒”。 二、定义与结构 备忘录(Memento)模式又称标记(Token)模式。GOF 给备忘录模式的定义 为:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存 这个状态。这样以后就可将该对象恢复到原先保存的状态。 在讲命令模式的时候,我们曾经提到利用中间的命令角色可以实现 undo、 redo 的功能。从定义可以看出备忘录模式是专门来存放对象历史状态的,这对 于很好的实现 undo、redo 功能有很大的帮助。所以在命令模式中 undo、redo 功能可以配合备忘录模式来实现。 其实单就实现保存一个对象在某一时刻的状态的功能,还是很简单的——将 对象中要保存的属性放到一个专门管理备份的对象中,需要的时候则调用约定好 的方法将备份的属性放回到原来的对象中去。但是你要好好看看为了能让你的备 份对象访问到原对象中的属性,是否意味着你就要全部公开或者包内公开对象原 本私有的属性呢?如果你的做法已经破坏了封装,那么就要考虑重构一下了。 备忘录模式只是 GOF 对“恢复对象某时的原有状态”这一问题提出的通用 方案。因此在如何保持封装性上——由于受到语言特性等因素的影响,备忘录模 式并没有详细描述,只是基于 C++阐述了思路。那么基于 Java 的应用应该怎样 来保持封装呢?我们将在实现一节里面讨论。 来看下“月光宝盒”备忘录模式的组成部分: 1) 备忘录(Memento)角色:备忘录角色存储“备忘发起角色”的内部状态。 “备忘发起角色”根据需要决定备忘录角色存储“备忘发起角色”的哪些内部 状态。为了防止“备忘发起角色”以外的其他对象访问备忘录。备忘录实际上有 两个接口,“备忘录管理者角色”只能看到备忘录提供的窄接口——对于备忘录 角色中存放的属性是不可见的。“备忘发起角色”则能够看到一个宽接口——能 够得到自己放入备忘录角色中属性。 2) 备忘发起(Originator)角色:“备忘发起角色”创建一个备忘录,用 以记录当前时刻它的内部状态。在需要时使用备忘录恢复内部状态。 3) 备忘录管理者(Caretaker)角色:负责保存好备忘录。不能对备忘录的 内容进行操作或检查。 备忘录模式的类图真是再简单不过了: 三、举例 按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘 录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。而对于其他 角色则是不可见的。GOF在书中以 C++为例进行了探讨。但是在Java 中没有提供 类似于 C++中友元的概念。在 Java 中怎样才能保持备忘录角色的封装呢? 下面对三种在 Java 中可保存封装的方法进行探讨。 第一种就是采用两个不同的接口类来限制访问权限。这两个接口类中,一个 提供比较完备的操作状态的方法,我们称它为宽接口;而另一个则可以只是一个 标示,我们称它为窄接口。备忘录角色要实现这两个接口类。这样对于“备忘发 起角色”采用宽接口进行访问,而对于其他的角色或者对象则采用窄接口进行访 问。 这种实现比较简单,但是需要人为的进行规范约束——而这往往是没有力度 的。 第二种方法便很好的解决了第一种的缺陷:采用内部类来控制访问权限。将 备忘录角色作为“备忘发起角色”的一个私有内部类。好处我不详细解释了,看 看代码吧就明白了。下面的代码是一个完整的备忘录模式的教学程序。它便采用 了第二种方法来实现备忘录模式。 还有一点值得指出的是,在下面的代码中,对于客户程序来说“备忘录管理 者角色”是不可见的,这样简化了客户程序使用备忘录模式的难度。下面采用 “备忘发起角色”来调用访问“备忘录管理者角色”,也可以参考门面模式在客 户程序与备忘录角色之间添加一个门面角色。 class Originator{ //这个是要保存的状态 private int state= 90; //保持一个“备忘录管理者角色”的对象 private Caretaker c = new Caretaker(); //读取备忘录角色以恢复以前的状态 public void setMemento(){ Memento memento = (Memento)c.getMemento(); state = memento.getState(); System.out.println("the state is "+state+" now"); } //创建一个备忘录角色,并将当前状态属性存入,托给“备忘录管理者角 色”存放。 public void createMemento(){ c.saveMemento(new Memento(state)); } //this is other business methods //they maybe modify the attribute state public void modifyState4Test(int m){ state = m; System.out.println("the state is "+state+" now"); } //作为私有内部类的备忘录角色,它实现了窄接口,可以看到在第二种方法 中宽接口已经不再需要 //注意:里面的属性和方法都是私有的 private class Memento implements MementoIF{ private int state ; private Memento(int state){ this.state = state ; } private int getState(){ return state; } } } //测试代码——客户程序 public class TestInnerClass{ public static void main(String[] args){ Originator o = new Originator(); o.createMemento(); o.modifyState4Test(80); o.setMemento(); } } //窄接口 interface MementoIF{} //“备忘录管理者角色” class Caretaker{ private MementoIF m ; public void saveMemento(MementoIF m){ this.m = m; } public MementoIF getMemento(){ return m; } } 第三种方式是不太推荐使用的:使用 clone 方法来简化备忘录模式。由于 Java 提供了 clone 机制,这使得复制一个对象变得轻松起来。使用了clone 机制的备 忘录模式,备忘录角色基本可以省略了,而且可以很好的保持对象的封装。但是 在为你的类实现 clone 方法时要慎重啊。 在上面的教学代码中,我们简单的模拟了备忘录模式的整个流程。在实际应 用中,我们往往需要保存大量“备忘发起角色”的历史状态。这时就要对我们的 “备忘录管理者角色”进行改造,最简单的方式就是采用容器来按照顺序存放备 忘录角色。这样就可以很好的实现 undo、redo 功能了。 四、适用情况 从上面的讨论可以看出,使用了备忘录模式来实现保存对象的历史状态可以 有效地保持封装边界。使用备忘录可以避免暴露一些只应由“备忘发起角色”管 理却又必须存储在“备忘发起角色”之外的信息。把“备忘发起角色”内部信息 对其他对象屏蔽起来, 从而保持了封装边界。 但是如果备份的“备忘发起角色”存在大量的信息或者创建、恢复操作非常 频繁,则可能造成很大的开销。 GOF 在《设计模式》中总结了使用备忘录模式的前提: 1) 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能 恢复到先前的状态。 2) 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现 细节并破坏对象的封装性。 设计模式(17)-Chain of Responsibility Pattern Posted on 2005-11-22 11:13 Piccolo Goo 阅读(163) 评论(0) 编辑 收藏 网摘 所属分类: Design Patterns 行为模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为模式 不仅仅是关于类和对象的,而且是关于它们之间的相互作用的。 行为模式分为类的行为模式和对象的行为模式两种。 • 类的行为模式:类的行为模式使用继承关系在几个类之问分配行为。 • 对象的行为模式:对象的行为模式则使用对象的聚合来分配行为。 在后面将要介绍的行为模式包括以下几种: • Chain of Resp.(责任链模式)A way of passing a request between a chain of objects • Command(命令模式)Encapsulate a command request as an object • Interpreter(解释器模式)A way to include language elements in a program • Iterator(迭代子模式)Sequentially access the elements of a collection • Mediator(中介者模式)Defines simplified communication between classes • Memento(备忘录模式)Capture and restore an object's internal state • Observer(观察者模式)A way of notifying change to a number of classes • State(状态模式)Alter an object's behavior when its state changes • Strategy(策略模式)Encapsulates an algorithm inside a class • Template Method(模版方法模式)Defer the exact steps of an algorithm to a subclass • Visitor(访问者模式)Defines a new operation to a class without change 一、 职责链(Chain of Responsibility)模式 责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其 下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请 求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不 影响客户端的情况下动态地重新组织链和分配责任。 从击鼓传花谈起 击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地 方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束 在某人手中,则该人就得饮酒。 击鼓传花便是责任链模式的应用。责任链可能是一条直线、一个环链或者一个树结构的一部分。 二、 责任链模式的结构 责任链模式涉及到的角色如下所示: 抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法, 以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。 具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者 将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下 家。 三、 责任链模式的示意性源代码 // Chain of Responsibility pattern -- Structural example using System; // "Handler" abstract class Handler { // Fields protected Handler successor; // Methods public void SetSuccessor( Handler successor ) { this.successor = successor; } abstract public void HandleRequest( int request ); } // "ConcreteHandler1" class ConcreteHandler1 : Handler { // Methods override public void HandleRequest( int request ) { if( request >= 0 && request < 10 ) Console.WriteLine("{0} handled request {1}", this, request ); else if( successor != null ) successor.HandleRequest( request ); } } // "ConcreteHandler2" class ConcreteHandler2 : Handler { // Methods override public void HandleRequest( int request ) { if( request >= 10 && request < 20 ) Console.WriteLine("{0} handled request {1}", this, request ); else if( successor != null ) successor.HandleRequest( request ); } } // "ConcreteHandler3" class ConcreteHandler3 : Handler { // Methods override public void HandleRequest( int request ) { if( request >= 20 && request < 30 ) Console.WriteLine("{0} handled request {1}", this, request ); else if( successor != null ) successor.HandleRequest( request ); } } /// /// Client test /// public class Client { public static void Main( string[] args ) { // Setup Chain of Responsibility Handler h1 = new ConcreteHandler1(); Handler h2 = new ConcreteHandler2(); Handler h3 = new ConcreteHandler3(); h1.SetSuccessor(h2); h2.SetSuccessor(h3); // Generate and process request int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 }; foreach( int request in requests ) h1.HandleRequest( request ); } } 四、 纯的与不纯的责任链模式 一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一个是承担责任, 二是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传 的情况。 在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模 式里面,一个请求可以最终不被任何接收端对象所接收。纯的责任链模式的例子是不容易找到的, 一般看到的例子均是不纯的责任链模式的实现。 五、 责任链模式的实际应用案例 下面的责任链模式代码演示了不同职务的人根据所设定的权限对一个购买请求作出决策或将其 交给更高的决策者。 // Chain of Responsibility pattern -- Real World example using System; // "Handler" abstract class Approver { // Fields protected string name; protected Approver successor; // Constructors public Approver( string name ) { this.name = name; } // Methods public void SetSuccessor( Approver successor ) { this.successor = successor; } abstract public void ProcessRequest( PurchaseRequest request ); } // "ConcreteHandler" class Director : Approver { // Constructors public Director ( string name ) : base( name ) {} // Methods override public void ProcessRequest( PurchaseRequest request ) { if( request.Amount < 10000.0 ) Console.WriteLine( "{0} {1} approved request# {2}", this, name, request.Number); else if( successor != null ) successor.ProcessRequest( request ); } } // "ConcreteHandler" class VicePresident : Approver { // Constructors public VicePresident ( string name ) : base( name ) {} // Methods override public void ProcessRequest( PurchaseRequest request ) { if( request.Amount < 25000.0 ) Console.WriteLine( "{0} {1} approved request# {2}", this, name, request.Number); else if( successor != null ) successor.ProcessRequest( request ); } } // "ConcreteHandler" class President : Approver { // Constructors public President ( string name ) : base( name ) {} // Methods override public void ProcessRequest( PurchaseRequest request ) { if( request.Amount < 100000.0 ) Console.WriteLine( "{0} {1} approved request# {2}", this, name, request.Number); else Console.WriteLine( "Request# {0} requires " + "an executive meeting!", request.Number ); } } // Request details class PurchaseRequest { // Member Fields private int number; private double amount; private string purpose; // Constructors public PurchaseRequest( int number, double amount, string purpose ) { this.number = number; this.amount = amount; this.purpose = purpose; } // Properties public double Amount { get{ return amount; } set{ amount = value; } } public string Purpose { get{ return purpose; } set{ purpose = value; } } public int Number { get{ return number; } set{ number = value; } } } /// /// ChainApp Application /// public class ChainApp { public static void Main( string[] args ) { // Setup Chain of Responsibility Director Larry = new Director( "Larry" ); VicePresident Sam = new VicePresident( "Sam" ); President Tammy = new President( "Tammy" ); Larry.SetSuccessor( Sam ); Sam.SetSuccessor( Tammy ); // Generate and process different requests PurchaseRequest rs = new PurchaseRequest( 2034, 350.00, "Supplies" ); Larry.ProcessRequest( rs ); PurchaseRequest rx = new PurchaseRequest( 2035, 32590.10, "Project X " ); Larry.ProcessRequest( rx ); PurchaseRequest ry = new PurchaseRequest( 2036, 122100.00, "Project Y " ); Larry.ProcessRequest( ry ); } } 六、 责任链模式的实现 责任链模式并不创建责任链。责任链的创建必须由系统的其它部分创建出来。 责任链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。一个 链可以是一条线,一个树,也可以是一个环。如下图所示,责任链是一个树结构的一部分。 Command 模式 Step by Step 简介 提起 Command 模式,我想没有什么比遥控器的例子更能说明问题了,本文将通过 它来一步步实现 GOF 的 Command 模式。 我们先看下这个遥控器程序的需求:假如我们需要为家里的电器设计一个远程遥 控器,通过这个控制器,我们可以控制电器(诸如灯、风扇、空调等)的开关。我 们的控制器上有一系列的按钮,分别对应家中的某个电器,当我们在遥控器上按 下“On”时,电器打开;当我们按下“Off”时,电器关闭。 好了,让我们开始 Command 模式之旅吧。 HardCoding 的实现方式 控制器的实现 一般来说,考虑问题通常有两种方式:从最复杂的情况考虑,也就是尽可能的深 谋远虑,设计之初就考虑到程序的可维护性、扩展性;还有就是从最简单的情况 考虑,不考虑扩展,客户要求什么,我们就做个什么,至于以后有什么新的需求, 等以后再说。当然这两种方式各有优劣,本文我们从最简单的情况开始考虑。 我们假设控制器只能控制三个电器,分别是:灯、电扇、门(你就当是电子门好 了^^)。那么我们的控制器应该有三组,共六个按钮,每一组按钮分别有“On”, “Off”按钮。同时,我们规定,第一组按钮对应灯,第二组按钮对应电扇,第 三组则对应门,那么控制器应该就像这样: 类的设计 好了,控制器大致是这么个样子了,那么 灯、电扇、门又是什么样子呢?如果 你看过前面几节的模式,你可能会以为此时又要为它们创建一个基类或者接口, 然后供它们继承或实现。现在让我们先看看我们想要控制的电器是什么样子的: 很抱歉,你遗憾地发现,它们的接口完全不同 ,我们没有办法对它们进行抽象, 但是因为我们此刻仅考虑客户最原始的需求(最简单的情况),那么我们大可以直 接将它们复合到 遥控器(ControlPanel) 中 NOTE: 关于接口,有狭义的含义:就是一个声明为 interface 的类型。还有一 个广义的含义:就是对象暴露给外界的方法、属性,所以一个抽象类也可以称作 一个接口。这里,说它们的接口不同,意思是说:这三个电器暴露给外界的方法 完全不同。 注意到,PressOn 方法,它代表着某一个按键被按下,并接受一个 int 类型的参 数:SlotNo,它代表是第几个键被按下。显然,SlotNo 的取值为 0 到 2。对于 PressOff 则是完全相同的设计。 代码实现 namespace Command { // 定义灯 public class Light { public void TurnOn(){ Console .WriteLine("The light is turned on." ); } public void TurnOff() { Console .WriteLine("The light is turned off." ); } } // 定义风扇 public class Fan { public void Start() { Console .WriteLine("The fan is starting." ); } public void Stop() { Console .WriteLine("The fan is stopping." ); } } // 定义门 public class Door { public void Open() { Console .WriteLine("The door is open for you." ); } public void Shut() { Console .WriteLine("The door is closed for safety" ); } } // 定义遥控器 public class ControlPanel { private Light light; private Fan fan; private Door door; public ControlPanel(Light light, Fan fan, Door door) { this .light = light; this .fan = fan; this .door = door; } // 点击 On 按钮时的操作。slotNo,第几个按钮被按 public void PressOn(int slotNo){ switch (slotNo) { case 0: light.TurnOn(); break ; case 1: fan.Start(); break ; case 2: door.Open(); break ; } } // 点击 Off 按钮时的操作。 public void PressOff(int slotNo) { switch (slotNo) { case 0: light.TurnOff(); break ; case 1: fan.Stop(); break ; case 2: door.Shut(); break ; } } } class Program { static void Main(string [] args) { Light light = new Light (); Fan fan = new Fan (); Door door = new Door (); ControlPanel panel = new ControlPanel (light, fan, door); panel.PressOn(0); // 按第一个 On 按钮,灯被打开了 panel.PressOn(2); // 按第二个 On 按钮,门被打开了 panel.PressOff(2); // 按第二个 Off 按钮,门被关闭 了 } } } 输出为: The light is turned on. The door is open for you. The door is closed for safety 存在问题 这个解决方案虽然能解决当前的问题,但是几乎没有任何扩展性可言。或者说, 被调用者(Receiver:灯、电扇、门)与它们的调用者(Invoker:遥控器)是紧耦 合的。遥控器不仅需要确切地知道它能控制哪些电器,并且需要知道这些电器由 哪些方法可供调用。 • 如果我们需要调换一下按钮所控制的电器的次序,比如说我们需要让按钮 1 不再控制灯,而是控制门,那么我们需要修改 PressOn 和 PressOff 方 法中的 Switch 语句。 • 如果我们需要给遥控器多添一个按钮,以使它多控制一个电器,那么遥控 器的字段、构造函数、PressOn、PressOff 方法都要修改。 • 如果我们不给遥控器多添按钮,但是要求它可以控制 10 个或者电器,换 言之,就是我们可以动态分配某个按钮控制哪个电器,这样的设计看上去 简直无法完成。 HardCoding 的另一实现 新设计方案 在考虑新的方案以前,我们先回顾前面的设计,第三个问题似乎暗示着我们的遥 控器不够好,思考一下,我们发现可以这样设计遥控器: 对比一下,我们看到可以通过左侧可以上下活动的阀门来控制当前遥控器控制的 是哪个电器(按照图中当前显示,控制的是灯),在选定了阀门后,我们可以再通 过 On,Off 按钮来对电器进行控制。此时,我们需要多添一个方法,通过它来控 制阀门(进而选择想要控制的电器)。我们管这个方法叫做 SetDevice()。那么我 们的设计变成下图所示: NOTE: 在图中,以及现实世界中,阀门所能控制的电器数总是有限的,但在程 序中,可以是无限的,就看你有多少个诸如 light 的电器类了 注意到几点变化: • 因为我们假设遥控器可以控制的电器是无限多的,所以这里不能指定具体 电器类型,因为在 C#中所有类型均继承自 Object,我们将 SetDevice() 方法接受的参数设置成为 Object。 • ControlPanel 不知道它将控制哪个类,所以图中ControlPanel 和 Light、 Door、Fan 没有联系。 • PressOn()和 PressOff()方法不再需要参数,因为很明显,只有一组 On 和 Off 按钮。 代码实现 namespace Command { public class Light { // 略 } public class Fan { // 略 } public class Door { // 略 } // 定义遥控器 public class ControlPanel { private Object device; // 点击 On 按钮时的操作。 public void PressOn() { Light light = device as Light; if (light != null ) light.TurnOn(); Fan fan = device as Fan; if (fan != null ) fan.Start(); Door door = device as Door; if (door != null ) door.Open(); } // 点击 Of 按钮时的操作。 public void PressOff() { Light light = device as Light; if (light != null ) light.TurnOff(); Fan fan = device as Fan; if (fan != null ) fan.Stop(); Door door = device as Door; if (door != null ) door.Shut(); } // 设置阀门控制哪个电器 public void SetDevice(Object device) { this .device = device; } } class Program { static void Main(string [] args) { Light light = new Light (); Fan fan = new Fan (); ControlPanel panel = new ControlPanel (); panel.SetDevice(light); // 设置阀门控制灯 panel.PressOn(); // 打开灯 panel.PressOff(); // 关闭灯 panel.SetDevice(fan); // 设置阀门控制电扇 panel.PressOn(); // 打开门 } } } 存在问题 我们首先可以看到,这个方案似乎解决了第一种设计的大多数问题,除了一点点 瑕疵: • 尽管我们可以控制任意多的设备,但是我们每添加一个可以控制的设备, 仍需要修改 PressOn()和 PressOff()方法。 • 在 PressOn()和 PressOff()方法中,需要对所有可能控制的电器进行类型 转换,无疑效率低下。 封装调用 问题分析 我们的处境似乎一筹莫展,想不到更好的办法来解决。这时候,让我们先回头再 观察一下 ControlPanel 的 PressOn()和 PressOff()代码。 // 点击 On 按钮时的操作。 public void PressOn() { Light light = device as Light; if (light != null ) light.TurnOn(); Fan fan = device as Fan; if (fan != null ) fan.Start(); Door door = device as Door; if (door != null ) door.Open(); } 我们发现 PressOn()和 PressOff()方法在每次添加新设备时需要作修改,而实际 上改变的是对对象方法的调用,因为不管有多少个 if 语句,只会调用其中某个 不为 null 的对象的一个方法。 然后我们再回顾一下 OO 的思想,Encapsulate what varies(封装变化)。我们想是不是应该有办法将这变化的这部分(方法的调 用)封装起来呢? 在考虑如何封装之前,我们假设已经有一个类,把它封装起来了,我们管这个类 叫做 Command,那么这个类该如何使用呢? 我们先考虑一下它的构成,因为它要封装各个对象的方法,所以,它应该暴露出 一个方法,这个方法既可以代表 light.TurnOn(),也可以代表 fan.Start(), 还可以代表 door.Open(),让我们给这个方法起个名字,叫做 Execute()。 好了,现在我们有了 Command 类,还有了一个万金油的 Execute()方法,现在, 我们修改 PressOn()方法,让它通过这个 Command 类来控制电器(调用各个类的 方法)。 // 点击 On 按钮时的操作。 public void PressOn() { command.Execute(); } 哇,是不是有点简单的过分了!?但就是这么简单,可我们还是发现了两个问题: 1. Command 应该能知道它调用的是哪个电器类的哪个方法,这暗示我们 Command 类应该保存对于具体电器类的一个引用。 2. 我们的 ControlPanel 应该有两个 Command,一个 Command 对应于所有开 启的操作(我们管它叫 onCommand),一个Command 对应所有关闭的操作(我 们管它叫 offCommand)。 同时,我们的 SetDevice(object)方法,也应该改成 SetCommand(onCommand,offCommand)。好了,现在让我们看看新版ControlPanel 的全景图吧。 Command 类型的实现 显然,我们应该能看出:onCommand实体变量(instance variable)和offCommand 变量属于 Command 类型,同时,上面我们已经讨论过 Command 类应该具有一个 Execute()方法,除此以外,它还需要可以保存对各个对象的引用,通过Execute() 方法可以调用其引用的对象的方法。 那么我们按照这个思路,来看下开灯这一操作(调用 light 对象的 TurnOn()方法) 的 Command 对象应该是什么样的: public class LightOnCommand { Light light; public Command(Light light){ this .light = light; } public void Execute(){ light.TurnOn(); } } 再看下开电扇(调用 fan 对象的 Start()方法)的 Command 对象应该是什么样的: public class FanStartCommand { Fan fan; public Command(Fan fan){ this .fan = fan; } public void Execute(){ fan.Start(); } } 这样显然是不行的,它没有解决任何的问题,因为 FanStartCommand 和 LightOnCommand 是不同的类型,而我们的 ControlPanel要求对于所有打开的操 作应该只接受一个类型的 Command 的。但是经过我们上面的讨论,我们已经知道 所有的 Command 都有一个 Execute()方法,我们何不定义一个接口来解决这个问 题呢? OK,现在我们已经完成了全部的设计,让我们先看一下最终的 UML 图,再进行代 码实现吧(简单起见,只加入了灯和电扇)。 我们先看下这张图说明了什么,以及发生的顺序: 1. ConsoleApplication,也就是我们的应用程序,它创建电器 Fan、Light 对象,以及 LightOnCommand 和 FanStartCommand。 2. LightOnCommand、FanStartCommand 实现了 ICommand 接口,它保存着对 于 Fan 和 Light 的引用,并通过 Execute()调用 Fan 和 Light 的方法。 3. ControlPanel 复合了 Command 对象,通过调用Command 的 Execute()方法, 间接调用了 Light 的 TurnOn()方法或者是 Fan 的 Stop()方法。 它们之间的时序图是这样的: 可以看出:通过引入 Command 对象,ControlPanel 对于它实际调用的对象 Fan 或者 Light 是一无所知的,它只知道当 On 按下的时候就调用 onCommand 的 Execute()方法;当Off 按下的时候就调用 offCommand 的 Execute()方法。Light 和 Fan 当然更不知道谁在调用它。通过这种方式,我们实现了调用者(Invoker, 遥控器 ControlPanel) 和被调用者(Receiver,电扇 Fan 等)的解耦。如果将来 我们需要对这个 ControlPanel 进行扩展,只需要再添加一个实现了ICommand 接 口的对象就可以了,对于 ControlPanel 无需做任何修改。 代码实现 namespace Command { // 定义空调,用于测试给遥控器添新控制类型 public class AirCondition { public void Start() { Console .WriteLine("The AirCondition is turned on." ); } public void SetTemperature(int i) { Console .WriteLine("The temperature is set to " + i); } public void Stop() { Console .WriteLine("The AirCondition is turned off." ); } } // 定义 Command 接口 public interface ICommand { void Execute(); } // 定义开空调命令 public class AirOnCommand : ICommand { AirCondition airCondition; public AirOnCommand(AirCondition airCondition) { this .airCondition = airCondition; } public void Execute() { //注意,你可以在 Execute()中添加多个方 法 airCondition.Start(); airCondition.SetTemperature(16); } } // 定义关空调命令 public class AirOffCommand : ICommand { AirCondition airCondition; public AirOffCommand(AirCondition airCondition) { this .airCondition = airCondition; } public void Execute() { airCondition.Stop(); } } // 定义遥控器 public class ControlPanel { private ICommand onCommand; private ICommand offCommand; public void PressOn() { onCommand.Execute(); } public void PressOff() { offCommand.Execute(); } public void SetCommand(ICommand onCommand,ICommand offCommand) { this .onCommand = onCommand; this .offCommand = offCommand; } } class Program { static void Main(string [] args) { // 创建遥控器对象 ControlPanel panel = new ControlPanel (); AirCondition airCondition = new AirCondition (); // 创建空调对象 // 创建 Command 对象,传递空调对象 ICommand onCommand = new AirOnCommand (airCondition); ICommand offCommand = new AirOffCommand (airCondition); // 设置遥控器的 Command panel.SetCommand(onCommand, offCommand); panel.PressOn(); //按下 On 按钮,开空调,温度调到 16 度 panel.PressOff(); //按下 Off 按钮,关空调 } } } Command 模式 实际上,我们上面做的这一切,实现了另一个设计模式:Command 模式。现在又 到了给出官方定义的时候了。每次到了这部分我就不知道该怎么写了,写的人太 多了,资料也太多了,我相信你看到这里对 Command 模式已经比较清楚了,所以 我还是一如既往地从简吧。 Command 模式的正式定义:将一个请求封装为一个对象,从而使你可用不同的请 求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。 它的 静态图 是这样的: 它的 时序图 是这样的: 可以和我们前面的图对比一下,对于这两个图,除了改了个名字外基本没变,我 就不再说明了,也留给你一点思考的空间。 总结 本文简单地介绍了 GOF 的 Commmand 模式,我们通过一个简单的范例家电遥控器 实现了这一模式。 我们首先了解了不使用此模式的 HardCoding 方式的实现方法,讨论了它的缺点; 然后又换了另一种改进了的实现方法,再次讨论了它的不足。 然后,我们通过 将对象的调用封装到一个 Command 对象中的方式,巧妙地完成了设计。最后,我 们给出了 Command 模式的正式定义。 本文仅仅简要介绍了 Command 模式,它的高级应用:取消操作(UnDo)、事务支持 (Transaction)、队列请求(Queuing Request) 以后有时间了会再写文章。 希望这篇文章能对你有所帮助! 一、 模板方法(Template Method)模式 准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方 法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩 余的逻辑有不同的实现。这就是模版方法模式的用意。 很多人可能没有想到,模版方法模式实际上是所有模式中最为常见的几个模式之一,而且很 多人可能使用过模版方法模式而没有意识到自己已经使用了这个模式。模版方法模式是基于 继承的代码复用的基本技术,模版方法模式的结构和用法也是面向对象设计的核心。 模版方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算 法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步 骤的方法称做基本方法(primitive method);而将这些基本法方法总汇起来的方法叫做 模版方法(template method),这个设计模式的名字就是从此而来。 二、 模版方法模式的结构 模版方法模式的静态结构如下图所示。 这里涉及到两个角色: • 抽象模版(AbstractClass)角色有如下的责任: 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶 级逻辑的组成步骤。 定义并实现了一个模版方法。这个模版方法一般是一个具体方法,它给出了一个顶级逻辑的 骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一 些具体方法。 • 具体模版(ConcreteClass)角色有如下的责任: 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。 每一个抽象模版角色都可以有任意多个具体模版角色与之对应,而每一个具体模版角色都可 以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现 各不相同。 三、 模板方法模式的示意性代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Template_Method_Pattern { // "AbstractClass" abstract class AbstractClass { // Methods abstract public void PrimitiveOperation1(); abstract public void PrimitiveOperation2(); // The Template method public void TemplateMethod() { Console WriteLine("In AbstractClass TemplateMethod()"); 四、 继承作为复用的工具 使用继承作为复用的手段必须慎重,C#语言的设计师对使用继承作为复用的工具有着不同 层次上的认识。 不知其一 首先,初学 C#的程序员可能不知道什么是继承,或者认为"继承"是高深的工具。那时候, 大部分的功能复用都是通过委派进行的。 知其一、不知其二 然后慢慢地,他们发现在 C#语言里实现继承并不困难,并且初步认识到继承可以使子类一 下子得到基类的行为。这时他们就会跃跃欲试了,试图使用继承作为功能复用的主要工具, 并把原来应当使用委派的地方,改为使用继承,这时继承就有被滥用的危险。 知其二 很多面向对象的设计专家从 1986 年就开始警告继承关系被滥用的可能。有一些面向对象的 编程语言,如 SELF 语言,甚至将类的继承关系从语言的功能中取消掉,改为完全使用委派。 其他的设计师虽然不提倡彻底取消继承,但无一例外地鼓励在设计中尽可能使甩委派关系代 替继承关系。比如在【GOF95】一书中,状态模式、策略模式、装饰模式、桥梁模式以及 抽象工厂模式均是将依赖于继承的实现转换为基于对象的组合和聚合的实现,这些模式的要 点就是使用委派关系代替继承关系。 知其三 是不是继承就根本不该使用呢?事实上对数据的抽象化、继承、封装和多态性并称 C#和其 他绝大多数的面向对象语言的几项最重要的特性。继承不应当被滥用,并不意味着继承根本 就不该使用。因为继承容易被滥用就彻底抛弃继承,无异于因噎废食。 继承使得类型的等级结构易于理解、维护和扩展,而类型的等级结构非常适合于抽象化的设 计、实现和复用。尽管【GOF95】所给出的设计模式基本上没有太多基于继承的模式,很 多模式都是用继承的办法定义、实现接口的。多数的设计模式都描写一个以抽象类作为基类, 以具体类作为实现的等级结构,比如适配器模式、合成模式、桥梁模式、状态模式等。 模版方法模式则更进了一步:此模式鼓励恰当地使用继承。此模式可以用来改写一些拥有相 同功能的相关的类,将可复用的一般性的行为代码移到基类里面,而把特殊化的行为代码移 到子类里面。 因此,熟悉模版方法模式便成为一个重新学习继承的好地方。 五、 一个实际应用模板方法的例子 下面的例子演示了数据库访问的模板方法。实际应用时,请确保 C 盘根目录下有 nwind. mdb 这个 Access 数据库(可以从 Office 的安装目录下找到。中文版用户的请注意字段名 可能有所不同)。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.OleDb; namespace Template_Method_Pattern { // "AbstractClass" abstract class DataObject { // Methods abstract public void Connect(); abstract public void Select(); abstract public void Process(); abstract public void Disconnect(); 六、 模版方法模式中的方法 模版方法中的方法可以分为两大类:模版方法(Template Method)和基本方法(Primi tive Method)。 模版方法 一个模版方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行 为的方法。这个模版方法一般会在抽象类中定义,并由子类不加以修改地完全继承下来。 基本方法 基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Met hod)和钩子方法(Hook Method)。 抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在 C#语言里一个抽象方法以 a bstract 关键字标示出来。 具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或置换。在 C#语言里面, 一个具体方法没有 abstract 关键字。 钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现 是一个空实现,作为方法的默认实现。(Visual FoxPro 中项目向导建立的项目会使用一 个 AppHook 类实现监视项目成员变化,调整系统结构的工作。)钩子方法的名字通常以 d o 开始。 七、 重构的原则 在对一个继承的等级结构做重构时,一个应当遵从的原则便是将行为尽量移动到结构的高 端,而将状态尽量移动到结构的低端。 1995 年,Auer 曾在文献【AUER95】中指出: 1. 应当根据行为而不是状态定义一个类。也就是说,一个类的实现首先建立在行为的 基础之上,而不是建立在状态的基础之上。 2. 在实现行为时,是用抽象状态而不是用具体状态。如果一个行为涉及到对象的状态 时,使用间接的引用而不是直接的引用。换言之,应当使用取值方法而不是直接引 用属性。 3. 给操作划分层次。一个类的行为应当放到一个小组核心方法(Kernel Methods) 里面,这些方法可以很方便地在子类中加以置换。 4. 将状态属性的确认推迟到子类中。不要在抽象类中过早地声明属性变量,应将它们 尽量地推迟到子类中去声明。在抽象超类中,如果需要状态属性的话,可以调用抽 象的取值方法,而将抽象的取值方法的实现放到具体子类中。 如果能够遵从这样的原则,那么就可以在等级结构中将接口与实现分隔开来,将抽象与具体 分割开来,从而保证代码可以最大限度地被复用。这个过程实际上是将设计师引导到模版方 法模式上去。 设计 Strategy Pattern 一、 策略(Strategy)模式 策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类 中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情 况下发生变化。 假设现在要设计一个贩卖各类书籍的电子商务网站的购物车(Shopping Cat)系 统。一个最简单的情况就是把所有货品的单价乘上数量,但是实际情况肯定比这 要复杂。比如,本网站可能对所有的教材类图书实行每本一元的折扣;对连环画 类图书提供每本 7%的促销折扣,而对非教材类的计算机图书有 3%的折扣;对 其余的图书没有折扣。由于有这样复杂的折扣算法,使得价格计算问题需要系统 地解决。 使用策略模式可以把行为和环境分割开来。环境类负责维持和查询行为类,各种 算法则在具体策略类(ConcreteStrategy)中提供。由于算法和环境独立开来, 算法的增减、修改都不会影响环境和客户端。当出现新的促销折扣或现有的折扣 政策出现变化时,只需要实现新的策略类,并在客户端登记即可。策略模式相当 于"可插入式(Pluggable)的算法"。 二、 策略模式的结构 策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同 的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为 一个抽象策略类的子类。用一句话来说,就是:"准备一组算法,并将每一个算 法封装起来,使得它们可以互换。" 策略又称做政策(Policy)模式【GOF95】。下面是一个示意性的策略模式结构 图: 这个模式涉及到三个角色: 环境(Context)角色:持有一个 Strategy 类的引用。 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。 此角色给出所有的具体策略类所需的接口。 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。 三、 示意性源代码 // Strategy pattern -- Structural example using System; // "Strategy" abstract class Strategy { // Methods abstract public void AlgorithmInterface(); } // "ConcreteStrategyA" class ConcreteStrategyA : Strategy { // Methods override public void AlgorithmInterface() { Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()"); } } // "ConcreteStrategyB" class ConcreteStrategyB : Strategy { // Methods override public void AlgorithmInterface() { Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()"); } } // "ConcreteStrategyC" class ConcreteStrategyC : Strategy { // Methods override public void AlgorithmInterface() { Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()"); } } // "Context" class Context { // Fields Strategy strategy; // Constructors public Context( Strategy strategy ) { this.strategy = strategy; } // Methods public void ContextInterface() { strategy.AlgorithmInterface(); } } /**//// /// Client test /// public class Client { public static void Main( string[] args ) { // Three contexts following different strategies Context c = new Context( new ConcreteStrategyA() ); c.ContextInterface(); Context d = new Context( new ConcreteStrategyB() ); d.ContextInterface(); Context e = new Context( new ConcreteStrategyC() ); e.ContextInterface(); } } 四、 何时使用何种具体策略角色 在学习策略模式时,学员常问的一个问题是:为什么不能从策略模式中看出哪一 个具体策略适用于哪一种情况呢? 答案非常简单,策略模式并不负责做这个决定。换言之,应当由客户端自己决定 在什么情况下使用什么具体策略角色。策略模式仅仅封装算法,提供新算法插入 到已有系统中,以及老算法从系统中"退休"的方便,策略模式并不决定在何时使 用何种算法。 五、 一个实际应用策略模式的例子 下面的例子利用策略模式在排序对象中封装了不同的排序算法,这样以便允许客 户端动态的替换排序策略(包括 Quicksort、Shellsort 和 Mergesort)。 // Strategy pattern -- Real World example using System; using System.Collections; // "Strategy" abstract class SortStrategy { // Methods abstract public void Sort( ArrayList list ); } // "ConcreteStrategy" class QuickSort : SortStrategy { // Methods public override void Sort(ArrayList list ) { list.Sort(); // Default is Quicksort Console.WriteLine("QuickSorted list "); } } // "ConcreteStrategy" class ShellSort : SortStrategy { // Methods public override void Sort(ArrayList list ) { //list.ShellSort(); Console.WriteLine("ShellSorted list "); } } // "ConcreteStrategy" class MergeSort : SortStrategy { // Methods public override void Sort( ArrayList list ) { //list.MergeSort(); Console.WriteLine("MergeSorted list "); } } // "Context" class SortedList { // Fields private ArrayList list = new ArrayList(); private SortStrategy sortstrategy; // Constructors public void SetSortStrategy( SortStrategy sortstrategy ) { this.sortstrategy = sortstrategy; } // Methods public void Sort() { sortstrategy.Sort( list ); } public void Add( string name ) { list.Add( name ); } public void Display() { foreach( string name in list ) Console.WriteLine( " " + name ); } } /**//// /// StrategyApp test /// public class StrategyApp { public static void Main( string[] args ) { // Two contexts following different strategies SortedList studentRecords = new SortedList( ); studentRecords.Add( "Samual" ); studentRecords.Add( "Jimmy" ); studentRecords.Add( "Sandra" ); studentRecords.Add( "Anna" ); studentRecords.Add( "Vivek" ); studentRecords.SetSortStrategy( new QuickSort() ); studentRecords.Sort(); studentRecords.Display(); } } 六、 在什么情况下应当使用策略模式 在下面的情况下应当考虑使用策略模式: 1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使 用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2. 一个系统需要动态地在几种算法中选择一种。那么这些算法可以包装到一个 个的具体算法类里面,而这些具体算法类都是一个抽象算法类的子类。换言之, 这些具体算法类均有统一的接口,由于多态性原则,客户端可以选择使用任何一 个具体算法类,并只持有一个数据类型是抽象算法类的对象。 3. 一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户 端涉及到不必要接触到的复杂的和只与算法有关的数据。 4. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多 重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体 策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象设 计的概念。 七、 策略模式的优点和缺点 策略模式有很多优点和缺点。它的优点有: 1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算 法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代 码。 2. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。 如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一 个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算 法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法 或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行 为变得不可能。 3. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它 把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统 统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。 策略模式的缺点有: 1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着 客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模 式只适用于客户端知道所有的算法或行为的情况。 2. 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客 户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。 换言之,可以使用享元模式来减少对象的数量。 八、 其它 策略模式与很多其它的模式都有着广泛的联系。Strategy 很容易和 Bridge 模式 相混淆。虽然它们结构很相似,但它们却是为解决不同的问题而设计的。Strategy 模式注重于算法的封装,而 Bridge 模式注重于分离抽象和实现,为一个抽象体 系提供不同的实现。Bridge 模式与 Strategy 模式都很好的体现了"Favor composite over inheritance"的观点。 访问者模式(Visitor Pattern) 2007-03-30 12:50 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操 作需要修改的话,接受这个操作的数据结构则可以保持不变。 问题提出 System.Collection 命名空间下提供了大量集合操作对象。但大多数情况下处理 的都是同类对象的聚集。换言之,在聚集上采取的操作都是一些针对同类型对象 的同类操作。但是如果针对一个保存有不同类型对象的聚集采取某种操作该怎么 办呢? 粗看上去,这似乎不是什么难题。可是如果需要针对一个包含不同类型元素的聚 集采取某种操作,而操作的细节根据元素的类型不同而有所不同时,就会出现必 须对元素类型做类型判断的条件转移语句。这个时候,使用访问者模式就是一个 值得考虑的解决方案。 访问者模式 访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操 作之间的耦合解脱开,使得操作集合可以相对自由地演化。 数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入 节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做"双重 分派"。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。 双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而 不仅仅是其中的一者。 二、 访问者模式的结构 如下图所示,这个静态图显示了有两个具体访问者和两个具体节点的访问者模式 的设计,必须指出的是,具体访问者的数目与具体节点的数目没有任何关系,虽 然在这个示意性的系统里面两者的数目都是两个。 访问者模式涉及到抽象访问者角色、具体访问者角色、抽象节点角色、具体节点 角色、结构对象角色以及客户端角色。 • 抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有 的具体元素角色必须实现的接口。 • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接 口,也就是抽象访问者所声明的各个访问操作。 • 抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一 个参量。 • 具体节点(Node)角色:实现了抽象元素所规定的接受操作。 • 结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构 中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问 每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列 (List)或集合(Set)。 三、 示意性源代码 using System; using System.Collections; // "Visitor" abstract class Visitor { // Methods abstract public void VisitConcreteElementA( ConcreteElementA concreteElementA); abstract public void VisitConcreteElementB( ConcreteElementB concreteElementB); } // "ConcreteVisitor1" class ConcreteVisitor1 : Visitor { // Methods override public void VisitConcreteElementA( ConcreteElementA concreteElementA) { Console.WriteLine("{0} visited by {1}", concreteElementA, this); } override public void VisitConcreteElementB( ConcreteElementB concreteElementB) { Console.WriteLine("{0} visited by {1}", concreteElementB, this); } } // "ConcreteVisitor2" class ConcreteVisitor2 : Visitor { // Methods override public void VisitConcreteElementA( ConcreteElementA concreteElementA) { Console.WriteLine("{0} visited by {1}", concreteElementA, this); } override public void VisitConcreteElementB( ConcreteElementB concreteElementB) { Console.WriteLine("{0} visited by {1}", concreteElementB, this); } } // "Element" abstract class Element { // Methods abstract public void Accept(Visitor visitor); } // "ConcreteElementA" class ConcreteElementA : Element { // Methods override public void Accept(Visitor visitor) { visitor.VisitConcreteElementA(this); } public void OperationA() { } } // "ConcreteElementB" class ConcreteElementB : Element { // Methods override public void Accept(Visitor visitor) { visitor.VisitConcreteElementB(this); } public void OperationB() { } } // "ObjectStructure" class ObjectStructure { // Fields private ArrayList elements = new ArrayList(); // Methods public void Attach(Element element) { elements.Add(element); } public void Detach(Element element) { elements.Remove(element); } public void Accept(Visitor visitor) { foreach (Element e in elements) e.Accept(visitor); } } /// /// Client test /// public class Client { public static void Main(string[] args) { // Setup structure ObjectStructure o = new ObjectStructure(); o.Attach(new ConcreteElementA()); o.Attach(new ConcreteElementB()); // Create visitor objects ConcreteVisitor1 v1 = new ConcreteVisitor1(); ConcreteVisitor2 v2 = new ConcreteVisitor2(); // Structure accepting visitors o.Accept(v1); o.Accept(v2); } } 结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中就是节点 ConcreteElementA 和节点 ConcreteElementB。首先ConcreteElementA 会被访问 到,这个访问是由以下的操作组成的: 1. ConcreteElementA 对象的接受方法被调用,并将VisitorA 对象本身传入; 2. ConcreteElementA 对象反过来调用 VisitorA 对象的访问方法,并将 ConcreteElementA 对象本身传入; 3. VisitorA 对象调用 ConcreteElementA 对象的商业方法 operationA( )。 从而就完成了双重分派过程,接着,ConcreteElementB 会被访问,这个访问的 过程和 ConcreteElementA 被访问的过程是一样的。 因此,结构对象对聚集元素的遍历过程就是对聚集中所有的节点进行委派的过 程,也就是双重分派的过程。换言之,系统有多少个节点就会发生多少个双重分 派过程。 四、 一个实际应用 Visitor 模式的例子 以下的例子演示了 Employee 对象集合允许被不同的 Visitor(IncomeVisitor 与 VacationVisitor)访问其中的内容。 using System; using System.Collections; // "Visitor" abstract class Visitor { // Methods abstract public void Visit(Element element); } // "ConcreteVisitor1" class IncomeVisitor : Visitor { // Methods public override void Visit(Element element) { Employee employee = ((Employee)element); // Provide 10% pay raise employee.Income *= 1.10; Console.WriteLine("{0}'s new income: {1:C}", employee.Name, employee.Income); } } // "ConcreteVisitor2" class VacationVisitor : Visitor { public override void Visit(Element element) { Employee employee = ((Employee)element); // Provide 3 extra vacation days employee.VacationDays += 3; Console.WriteLine("{0}'s new vacation days: {1}", employee.Name, employee.VacationDays); } } // "Element" abstract class Element { // Methods abstract public void Accept(Visitor visitor); } // "ConcreteElement" class Employee : Element { // Fields string name; double income; int vacationDays; // Constructors public Employee(string name, double income, int vacationDays) { this.name = name; this.income = income; this.vacationDays = vacationDays; } // Properties public string Name { get { return name; } set { name = value; } } public double Income { get { return income; } set { income = value; } } public int VacationDays { get { return vacationDays; } set { vacationDays = value; } } // Methods public override void Accept(Visitor visitor) { visitor.Visit(this); } } // "ObjectStructure" class Employees { // Fields private ArrayList employees = new ArrayList(); // Methods public void Attach(Employee employee) { employees.Add(employee); } public void Detach(Employee employee) { employees.Remove(employee); } public void Accept(Visitor visitor) { foreach (Employee e in employees) e.Accept(visitor); } } /**/ /// /// VisitorApp test /// public class VisitorApp { public static void Main(string[] args) { // Setup employee collection Employees e = new Employees(); e.Attach(new Employee("Hank", 25000.0, 14)); e.Attach(new Employee("Elly", 35000.0, 16)); e.Attach(new Employee("Dick", 45000.0, 21)); // Create two visitors IncomeVisitor v1 = new IncomeVisitor(); VacationVisitor v2 = new VacationVisitor(); // Employees are visited e.Accept(v1); e.Accept(v2); } } 五、 在什么情况下应当使用访问者模式 有意思的是,在很多情况下不使用设计模式反而会得到一个较好的设计。换言之, 每一个设计模式都有其不应当使用的情况。访问者模式也有其不应当使用的情 况,让我们 先看一看访问者模式不应当在什么情况下使用。 倾斜的可扩展性 访问者模式仅应当在被访问的类结构非常稳定的情况下使用。换言之,系统很少 出现需要加入新节点的情况。如果出现需要加入新节点的情况,那么就必须在每 一个访问对象里加入一个对应于这个新节点的访问操作,而这是对一个系统的大 规模修改,因而是违背"开一闭"原则的。 访问者模式允许在节点中加入新的方法,相应的仅仅需要在一个新的访问者类中 加入此方法,而不需要在每一个访问者类中都加入此方法。 显然,访问者模式提供了倾斜的可扩展性设计:方法集合的可扩展性和类集合的 不可扩展性。换言之,如果系统的数据结构是频繁变化的,则不适合使用访问者 模式。 "开一闭"原则和对变化的封装 面向对象的设计原则中最重要的便是所谓的"开一闭"原则。一个软件系统的设计 应当尽量做到对扩展开放,对修改关闭。达到这个原则的途径就是遵循"对变化 的封装"的原则。这个原则讲的是在进行软件系统的设计时,应当设法找出一个 软件系统中会变化的部分,将之封装起来。 很多系统可以按照算法和数据结构分开,也就是说一些对象含有算法,而另一些 对象含有数据,接受算法的操作。如果这样的系统有比较稳定的数据结构,又有 易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算 法操作的增加变得容易。 反过来,如果这样一个系统的数据结构对象易于变化,经常要有新的数据对象增 加进来的话,就不适合使用访问者模式。因为在访问者模式中增加新的节点很困 难,要涉及到在抽象访问者和所有的具体访问者中增加新的方法。 六、 使用访问者模式的优点和缺点 访问者模式有如下的优点: 1. 访问者模式使得增加新的操作变得很容易。如果一些操作依赖于一个复杂 的结构对象的话,那么一般而言,增加新的操作会很复杂。而使用访问者 模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。 2. 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个 的节点类中。 3. 访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员 类。迭代子只能访问属于同一个类型等级结构的成员对象,而不能访问属 于不同等级结构的对象。访问者模式可以做到这一点。 4. 积累状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以 在访问的过程中将执行操作的状态积累在自己内部,而不是分散到很多的 节点对象中。这是有益于系统维护的优点。 访问者模式有如下的缺点: 1. 增加新的节点类变得很困难。每增加一个新的节点都意味着要在抽象访问 者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的 具体操作。 2. 破坏封装。访问者模式要求访问者对象访问并调用每一个节点对象的操 作,这隐含了一个对所有节点对象的要求:它们必须暴露一些自己的操作 和内部状态。不然,访问者的访问就变得没有意义。由于访问者对象自己 会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这 也是破坏封装的。 State Pattern 收藏 Definition-Allow an object to alter its behavior when its internal state changes. The obj ect will appear to change its class. //UML Class Diagram When to use? • Suppose an object is always in one of several known states • The state an object is in determines the behavior of several methods • Could use if/case statements in each method Compared to Strategy Pattern Strategy Pattern typically configures context classes with a behavior or algorithm. But S tate Pattern allows a context to change its behavior as the state of the context change s. Watch out: Using the State Pattern will typically result in a greater number of classes in your designed. Notes • Can use singletons for instances of each state class – State objects don’t encapsulate state, so can be shared • Easy to add new states – New states can extend other states • Override only selected functions Example: //Class Diagram //Code //Main.java class TcpConnection { TcpState tcpCurrentState; TcpEstablishedState establishedState; TcpListenState tcpListenState; TcpClosedState tcpClosedState; public TcpConnection() { establishedState=new TcpEstablishedState(this); tcpListenState=new TcpListenState(this); tcpClosedState=new TcpClosedState(this); tcpCurrentState=tcpClosedState; } public void Open() { tcpCurrentState.Open(); } public void Close() { tcpCurrentState.Close(); } public void Acknowledge() { tcpCurrentState.Acknowledge(); } public void SetState(TcpState tcpState) { tcpCurrentState = tcpState; } public TcpState GetEstablishState() { return this.establishedState; } public TcpState GetListenState() { return this.tcpListenState; } public TcpState GetClosedState() { return this.tcpClosedState; } } interface TcpState { void Open(); void Close(); void Acknowledge(); } class TcpEstablishedState implements TcpState { TcpConnection tcpConn; public TcpEstablishedState(TcpConnection tcpConn) { this.tcpConn = tcpConn; } public void Open() { System.out.println("the connection has been established!"); } public void Close() { System.out.println("Closing..."); tcpConn.SetState(tcpConn.GetClosedState()); } public void Acknowledge() { System.out.println("acknowledge...and then listening ..."); tcpConn.SetState(tcpConn.GetListenState()); } } class TcpListenState implements TcpState { TcpConnection tcpConn; public TcpListenState(TcpConnection tcpConn) { this.tcpConn = tcpConn; } public void Open() { System.out.println("the connection has been established!"); } public void Close() { System.out.println("Closing..."); tcpConn.SetState(tcpConn.GetClosedState()); } public void Acknowledge() { System.out.println("acknowledge...continued listen..."); } } class TcpClosedState implements TcpState { TcpConnection tcpConn; public TcpClosedState(TcpConnection tcpConn) { this.tcpConn = tcpConn; } public void Open() { System.out.println("the connection is establishing..."); tcpConn.SetState(tcpConn.GetEstablishState()); } public void Close() { System.out.println("the connection has been Closed.Don't close again. "); } public void Acknowledge() { System.out.println("the connection has been Closed so that it could no t acknowledge any msg"); } } public class Main { public static void main(String[] args) { // TODO Auto-generated method stub TcpConnection tcpConn=new TcpConnection(); //right tcpConn.Open(); tcpConn.Acknowledge(); tcpConn.Acknowledge(); tcpConn.Acknowledge(); tcpConn.Close(); //error tcpConn.Close(); //right tcpConn.Open(); tcpConn.Close(); } }
还剩213页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

aixin8013

贡献于2015-06-13

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf