.Net23种设计模式lzpnet

在C#项目开发过程中,很多情况下您已经使用了某些模式,但或许您并不知道自己所使用的这种解决方案是一种已经被总结归纳的模式。

工厂、策略、桥接、模板方法、代理等等23种Gof经典模式是属于设计模式,设计模式的粒度相对较小,基本上用于提高模块内部的可扩展性和可维护性需求

三层、MVC、IoC/DI等属于体系结构模式,粒度比设计模式大,它是从项目的整体角度去看项目的架构。设计需要合理性,架构同样追求合理性,这就是架构模式的用途。

C#的经典样例petshop中,采用分层思想构架了整个网站,这里的分层就是体系结构模式;而在数据库访问层,则采用工厂模式来泛化数据库操作,使得业务层不需要关心您现在的数据库是SQLserver的,还是oracle的。这就是设计模式的使用。

模式应用不是一两句话能够说清楚的,也不是一天两天能够体会的,需要楼主慢慢体会与学习。

二、分类目录:

创建型:

1.单件模式(SingletonPattern)

2.抽象工厂(AbstractFactory)

3.建造者模式(Builder)

4.工厂方法模式(FactoryMethod)

5.原型模式(Prototype)

结构型:

6.适配器模式(AdapterPattern)

7.桥接模式(BridgePattern)

8.装饰模式(DecoratorPattern)

9.组合模式(CompositePattern)

10.外观模式(FacadePattern)

11.享元模式(FlyweightPattern)

12.代理模式(ProxyPattern)

13.模板方法(TemplateMethod)

14.命令模式(CommandPattern)

15.迭代器模式(IteratorPattern)

行为型:

16.观察者模式(ObserverPattern)

17.解释器模式(InterpreterPattern)

18.中介者模式(MediatorPattern)

19.职责链模式(ChainofResponsibilityPattern)

20.备忘录模式(MementoPattern)

21.策略模式(StrategyPattern)

22.访问者模式(VisitorPattern)

23.状态模式(StatePattern)

C#23种设计模式

ChinaDocument4Colors

Howareyou

动机(Motivation):在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。如何绕过常规的构造器,提供一种机制来保证一个类只创建一个实例?这应该是类设计者的责任,而不是类使用者的责任。结构图:意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。------<<设计模式>>GOF生活的例子:适用性:(1)当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。(2)当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。代码实现:(1)单线程Singleton实现

classSingleThread_Singleton{privatestaticSingleThread_Singletoninstance=null;privateSingleThread_Singleton(){}publicstaticSingleThread_SingletonInstance{get{if(instance==null){instance=newSingleThread_Singleton();}returninstance;}}}

以上代码在单线程情况下不会出现任何问题。但是在多线程的情况下却不是安全的。如两个线程同时运行到if(instance==null)判断是否被实例化,一个线程判断为True后,在进行创建instance=newSingleThread_Singleton();之前,另一个线程也判断(instance==null),结果也为True.这样就就违背了Singleton模式的原则(保证一个类仅有一个实例)。怎样在多线程情况下实现Singleton?(2)多线程Singleton实现:

1classMultiThread_Singleton2{3privatestaticvolatileMultiThread_Singletoninstance=null;4privatestaticobjectlockHelper=newobject();5privateMultiThread_Singleton(){}6publicstaticMultiThread_SingletonInstance7{8get9{10if(instance==null)11{12lock(lockHelper)13{14if(instance==null)15{16instance=newMultiThread_Singleton();17}18}19}20returninstance;21}22}23

此程序对多线程是安全的,使用了一个辅助对象lockHelper,保证只有一个线程创建实例(如果instance为空,保证只有一个线程instance=newMultiThread_Singleton();创建唯一的一个实例)。(DoubleCheck)请注意一个关键字volatile,如果去掉这个关键字,还是有可能发生线程不是安全的。volatile保证严格意义的多线程编译器在代码编译时对指令不进行微调。(3)静态Singleton实现

3classStatic_Singleton4{5publicstaticreadonlyStatic_Singletoninstance=newStatic_Singleton();6privateStatic_Singleton(){}7}以上代码展开等同于

1classStatic_Singleton2{3publicstaticreadonlyStatic_Singletoninstance;4staticStatic_Singleton()5{6instance=newStatic_Singleton();7}8privateStatic_Singleton(){}9}

由此可以看出,完全符合Singleton的原则。优点:简洁,易懂缺点:不可以实现带参数实例的创建

常规的对象创建方法:

//创建一个Road对象Roadroad=newRoad();

new的问题:实现依赖,不能应对“具体实例化类型”的变化。解决思路:封装变化点-----哪里变化,封装哪里潜台词:如果没有变化,当然不需要额外的封装!工厂模式的缘起变化点在“对象创建”,因此就封装“对象创建”面向接口编程----依赖接口,而非依赖实现最简单的解决方法:

1classRoadFactory{2publicstaticRoadCreateRoad()3{4returnnewRoad();5}6}7//创建一个Road对象8Roadroad=roadFactory.CreateRoad();

创建一系列相互依赖对象的创建工作:假设一个游戏开场景:我们需要构造"道路"、"房屋"、"地道","从林"...等等对象工厂方法如下:

1classRoadFactory2{3publicstaticRoadCreateRoad()4{5returnnewRoad();6}7publicstaticBuildingCreateBuilding()8{9returnnewBuilding();10}11publicstaticTunnelCreateTunnel()12{13returnnewTunnel();14}15publicstaticJungleCreateJungle()16{17returnnewJungle();18}19}

调用方式如下:

1Roadroad=RoadFactory.CreateRoad();3Buildingbuilding=RoadFactory.CreateBuilding();4Tunneltunnel=RoadFactory.CreateTunnel();5Junglejungle=RoadFactory.CreateJungle();

1abstractclassAbstractFactory2{3publicabstractAbstractProductACreateProductA();4publicabstractAbstractProductBCreateProductB();5}

1abstractclassAbstractProductA2{3publicabstractvoidInteract(AbstractProductBb);4}

1abstractclassAbstractProductB2{3publicabstractvoidInteract(AbstractProductAa);4}

1classClient2{3privateAbstractProductAAbstractProductA;4privateAbstractProductBAbstractProductB;5publicClient(AbstractFactoryfactory)6{7AbstractProductA=factory.CreateProductA();8AbstractProductB=factory.CreateProductB();9}10publicvoidRun()11{12AbstractProductB.Interact(AbstractProductA);13AbstractProductA.Interact(AbstractProductB);14}15}

1classConcreteFactory1:AbstractFactory2{3publicoverrideAbstractProductACreateProductA()4{5returnnewProductA1();6}7publicoverrideAbstractProductBCreateProductB()8{9returnnewProductB1();10}11}

1classConcreteFactory2:AbstractFactory2{3publicoverrideAbstractProductACreateProductA()4{5returnnewProdcutA2();6}7publicoverrideAbstractProductBCreateProductB()8{9returnnewProductB2();10}11}

1classProductA1:AbstractProductA2{3publicoverridevoidInteract(AbstractProductBb)4{5Console.WriteLine(this.GetType().Name+"interactwith"+b.GetType().Name);6}7}

1classProductB1:AbstractProductB2{3publicoverridevoidInteract(AbstractProductAa)4{5Console.WriteLine(this.GetType().Name+"interactwith"+a.GetType().Name);6}7}

1classProdcutA2:AbstractProductA2{3publicoverridevoidInteract(AbstractProductBb)4{5Console.WriteLine(this.GetType().Name+"interactwith"+b.GetType().Name);6}7}

1classProductB2:AbstractProductB2{3publicoverridevoidInteract(AbstractProductAa)4{5Console.WriteLine(this.GetType().Name+"interactwith"+a.GetType().Name);6}7}

1publicstaticvoidMain()2{3//Abstractfactory14AbstractFactoryfactory1=newConcreteFactory1();5Clientc1=newClient(factory1);6c1.Run();7//Abstractfactory28AbstractFactoryfactory2=newConcreteFactory2();9Clientc2=newClient(factory2);10c2.Run();11}

AbstractFactory注意的几点:如果不存在”多系列对象创建“的需求变化,则没必要应用AbstractFactory模式,静态工厂方法足矣。"系列对象"指的是这些对象之间有相互依赖、或作用的关系。例如游戏开发场景中的"道路"与"房屋"依赖,“道路”与“地道”的依赖。AbstractFactory模式主要在于应对"新系列"的需求变动。其缺点在于难以应对”新对象“的需求变动。AbstractFactory模式经常和FactoryMethod模式共同组合来应对“对象创建”的需求变化

Builder模式的缘起:假设创建游戏中的一个房屋House设施,该房屋的构建由几部分组成,且各个部分富于变化。如果使用最直观的设计方法,每一个房屋部分的变化,都将导致房屋构建的重新修正.....动机(Motivation):在软件系统中,有时候面临一个"复杂对象"的创建工作,其通常由各个部分的子对象用一定算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合到一起的算法却相对稳定。如何应对种变化呢?如何提供一种"封装机制"来隔离出"复杂对象的各个部分"的变化,从而保持系统中的"稳定构建算法"不随需求的改变而改变?意图(Intent):将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。-------《设计模式》GOF结构图(Struct):协作(Collaborations):生活中的例子:适用性:1.当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。2.当构造过程必须允许被构造的对象有不同的表示时。实例代码:Builder类:

1publicabstractclassBuilder2{3publicabstractvoidBuildDoor();4publicabstractvoidBuildWall();5publicabstractvoidBuildWindows();6publicabstractvoidBuildFloor();7publicabstractvoidBuildHouseCeiling();89publicabstractHouseGetHouse();10}

Director类:这一部分是组合到一起的算法(相对稳定)。

1publicclassDirector2{3publicvoidConstruct(Builderbuilder)4{5builder.BuildWall();6builder.BuildHouseCeiling();7builder.BuildDoor();8builder.BuildWindows();9builder.BuildFloor();10}11}

ChineseBuilder类

1publicclassChineseBuilder:Builder2{3privateHouseChineseHouse=newHouse();4publicoverridevoidBuildDoor()5{6Console.WriteLine("thisDoor'sstyleofChinese");7}8publicoverridevoidBuildWall()9{10Console.WriteLine("thisWall'sstyleofChinese");11}12publicoverridevoidBuildWindows()13{14Console.WriteLine("thisWindows'sstyleofChinese");15}16publicoverridevoidBuildFloor()17{18Console.WriteLine("thisFloor'sstyleofChinese");19}20publicoverridevoidBuildHouseCeiling()21{22Console.WriteLine("thisCeiling'sstyleofChinese");23}24publicoverrideHouseGetHouse()25{26returnChineseHouse;27}28}

RomanBuilder类:

1classRomanBuilder:Builder2{3privateHouseRomanHouse=newHouse();4publicoverridevoidBuildDoor()5{6Console.WriteLine("thisDoor'sstyleofRoman");7}8publicoverridevoidBuildWall()9{10Console.WriteLine("thisWall'sstyleofRoman");11}12publicoverridevoidBuildWindows()13{14Console.WriteLine("thisWindows'sstyleofRoman");15}16publicoverridevoidBuildFloor()17{18Console.WriteLine("thisFloor'sstyleofRoman");19}20publicoverridevoidBuildHouseCeiling()21{22Console.WriteLine("thisCeiling'sstyleofRoman");23}24publicoverrideHouseGetHouse()25{26returnRomanHouse;27}28}

ChineseBuilder和RomanBuilder这两个是:这个复杂对象的两个部分经常面临着剧烈的变化。

1publicclassClient2{3publicstaticvoidMain(string[]args)4{5Directordirector=newDirector();67Builderinstance;89Console.WriteLine("PleaseEnterHouseNo:");1011stringNo=Console.ReadLine();1213stringhouseType=ConfigurationSettings.AppSettings["No"+No];1415instance=(Builder)Assembly.Load("House").CreateInstance("House."+houseType);1617director.Construct(instance);1819Househouse=instance.GetHouse();20house.Show();2122Console.ReadLine();23}24}

1234567Builder模式的几个要点:Builder模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的乘法,而复杂对象的各个部分则经常变化。Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。AbstractFactory模式解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化。Builder械通常和Composite模式组合使用

耦合关系:动机(Motivation):在软件系统中,由于需求的变化,"这个对象的具体实现"经常面临着剧烈的变化,但它却有比较稳定的接口。如何应对这种变化呢?提供一种封装机制来隔离出"这个易变对象"的变化,从而保持系统中"其它依赖的对象"不随需求的变化而变化。意图(Intent):定义一个用户创建对象的接口,让子类决定实例哪一个类。FactoryMethod使一个类的实例化延迟到子类。----------《设计模式》GOF结构图(Struct):生活实例:适用性:1.当一个类不知道它所必须创建的对象类的时候。2.当一个类希望由它子类来指定它所创建对象的时候。3.当类将创建对象的职责委托给多个帮助子类中的某个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。实例代码:CarFactory类:

1publicabstractclassCarFactory2{3publicabstractCarCarCreate();4}

Car类:

1publicabstractclassCar2{3publicabstractvoidStartUp();4publicabstractvoidRun();5publicabstractvoidStop();67}

HongQiCarFactory类:

1publicclassHongQiCarFactory:CarFactory2{3publicoverrideCarCarCreate()4{5returnnewHongQiCar();6}7}

BMWCarFactory类:

1publicclassBMWCarFactory:CarFactory2{3publicoverrideCarCarCreate()4{5returnnewBMWCar();6}7}

HongQiCar类:

1publicclassHongQiCar:Car2{3publicoverridevoidStartUp()4{5Console.WriteLine("TestHongQiCarstart-upspeed!");6}7publicoverridevoidRun()8{9Console.WriteLine("TheHongQiCarrunisveryquickly!");10}11publicoverridevoidStop()12{13Console.WriteLine("Theslowstoptimeis3second");14}15}

BMWCar类:

1publicclassBMWCar:Car2{3publicoverridevoidStartUp()4{5Console.WriteLine("TheBMWCarstart-upspeedisveryquickly");6}7publicoverridevoidRun()8{9Console.WriteLine("TheBMWCarrunisquitelyfastandsafe!!!");10}11publicoverridevoidStop()12{13Console.WriteLine("Theslowstoptimeis2second");14}15}

app.config

1234567

Program类:

1classProgram2{3staticvoidMain(string[]args)4{5Console.WriteLine("PleaseEnterFactoryMethodNo:");6Console.WriteLine("******************************");7Console.WriteLine("noFactoryMethod");8Console.WriteLine("1HongQiCarFactory");9Console.WriteLine("2BMWCarFactory");10Console.WriteLine("******************************");11intno=Int32.Parse(Console.ReadLine().ToString());12stringfactoryType=ConfigurationManager.AppSettings["No"+no];13//CarFactoryfactory=newHongQiCarFactory();14CarFactoryfactory=(CarFactory)Assembly.Load("FactoryMehtod").CreateInstance("FactoryMehtod."+factoryType);;15Carcar=factory.CarCreate();16car.StartUp();17car.Run();18car.Stop();1920}21}

FactoryMethod模式的几个要点:FactoryMethod模式主要用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。FactoryMethod模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。FactoryMehtod模式解决"单个对象"的需求变化,AbstractFactory模式解决"系列对象"的需求变化,Builder模式解决"对象部分"的需求变化

依赖关系倒置:动机(Motivate):在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。如何应对这种变化?如何向“客户程序(使用这些对象的程序)"隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?意图(Intent):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。------《设计模式》GOF结构图(Struct):生活例子:适用性:

1.当一个系统应该独立于它的产品创建,构成和表示时;

2.当要实例化的类是在运行时刻指定时,例如,通过动态装载;

3.为了避免创建一个与产品类层次平行的工厂类层次时;

4.当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

示意性代码例子:

1publicabstractclassNormalActor2{3publicabstractNormalActorclone();4}

1publicclassNormalActorA:NormalActor2{3publicoverrideNormalActorclone()4{5Console.WriteLine("NormalActorAiscall");6return(NormalActor)this.MemberwiseClone();78}9}

适配(转换)的概念无处不在......适配,即在不改变原有实现的基础上,将原先不兼容的接口转换为兼容的接口。例如:二转换为三箱插头,将高电压转换为低电压等。动机(Motivate):在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。那么如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?这就是本文要说的Adapter模式。意图(Intent):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。-------《设计模式》GOF结构(Struct):图1:对象适配器图2:类适配器生活中的例子:适用性:

1.系统需要使用现有的类,而此类的接口不符合系统的需要。

2.想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。

3.(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。示意性代码实例:

1interfaceIStack2{3voidPush(objectitem);4voidPop();5objectPeek();6}

1//对象适配器(Adapter与Adaptee组合的关系)2publicclassAdapter:IStack//适配对象3{4ArrayListadaptee;//被适配的对象5publicAdapter()6{7adaptee=newArrayList();8}9publicvoidPush(objectitem)10{11adaptee.Add(item);12}13publicvoidPop()14{15adaptee.RemoveAt(adaptee.Count-1);16}17publicobjectPeek()18{19returnadaptee[adaptee.Count-1];20}21}

类适配器

1publicclassAdapter:ArrayList,IStack2{3publicvoidPush(objectitem)4{5this.Add(item);6}7publicvoidPop()8{9this.RemoveAt(this.Count-1);10}11publicobjectPeek()12{13returnthis[this.Count-1];14}15}

动机(Motivate):在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?意图(Intent):将抽象部分与实现部分分离,使它们都可以独立的变化。------《设计模式》GOF结构图(Struct):生活中的例子:

我想大家小时候都有用蜡笔画画的经历吧。红红绿绿的蜡笔一大盒,根据想象描绘出格式图样。而毛笔下的国画更是工笔写意,各展风采。而今天我们的故事从蜡笔与毛笔说起。

设想要绘制一幅图画,蓝天、白云、绿树、小鸟,如果画面尺寸很大,那么用蜡笔绘制就会遇到点麻烦。毕竟细细的蜡笔要涂出一片蓝天,是有些麻烦。如果有可能,最好有套大号蜡笔,粗粗的蜡笔很快能涂抹完成。至于色彩吗,最好每种颜色来支粗的,除了蓝天还有绿地呢。这样,如果一套12种颜色的蜡笔,我们需要两套24支,同种颜色的一粗一细。呵呵,画还没画,开始做梦了:要是再有一套中号蜡笔就更好了,这样,不多不少总共36支蜡笔。

再看看毛笔这一边,居然如此简陋:一套水彩12色,外加大中小三支毛笔。你可别小瞧这"简陋"的组合,画蓝天用大毛笔,画小鸟用小毛笔,各具特色。

呵呵,您是不是已经看出来了,不错,我今天要说的就是Bridge模式。为了一幅画,我们需要准备36支型号不同的蜡笔,而改用毛笔三支就够了,当然还要搭配上12种颜料。通过Bridge模式,我们把乘法运算3×12=36改为了加法运算3+12=15,这一改进可不小。那么我们这里蜡笔和毛笔到底有什么区别呢?

实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。【GOF95】桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。关键就在于能否脱耦。蜡笔的颜色和蜡笔本身是分不开的,所以就造成必须使用36支色彩、大小各异的蜡笔来绘制图画。而毛笔与颜料能够很好的脱耦,各自独立变化,便简化了操作。在这里,抽象层面的概念是:"毛笔用颜料作画",而在实现时,毛笔有大中小三号,颜料有红绿蓝等12种,于是便可出现3×12种组合。每个参与者(毛笔与颜料)都可以在自己的自由度上随意转换。

蜡笔由于无法将笔与颜色分离,造成笔与颜色两个自由度无法单独变化,使得只有创建36种对象才能完成任务。Bridge模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。代码实现:

1abstractclassBrush2{3protectedColorc;4publicabstractvoidPaint();56publicvoidSetColor(Colorc)7{this.c=c;}8}

1classBigBrush:Brush2{3publicoverridevoidPaint()4{Console.WriteLine("Usingbigbrushandcolor{0}painting",c.color);}5}

1classSmallBrush:Brush2{3publicoverridevoidPaint()4{Console.WriteLine("Usingsmallbrushandcolor{0}painting",c.color);}5}

1classColor2{3publicstringcolor;4}

1classRed:Color2{3publicRed()4{this.color="red";}5}

1classGreen:Color2{3publicGreen()4{this.color="green";}5}

1classBlue:Color2{3publicBlue()4{this.color="blue";}5}

1classProgram2{3publicstaticvoidMain()4{5Brushb=newBigBrush();6b.SetColor(newRed());7b.Paint();8b.SetColor(newBlue());9b.Paint();10b.SetColor(newGreen());11b.Paint();1213b=newSmallBrush();14b.SetColor(newRed());15b.Paint();16b.SetColor(newBlue());17b.Paint();18b.SetColor(newGreen());19b.Paint();20}

适用性:1.如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。

2.设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。

3.一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。

4.虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。Bridge要点:1.Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。

2.所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同平台上的不同型号。

3.Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。

4.Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。

子类复子类,子类何其多

假如我们需要为游戏中开发一种坦克,除了各种不同型号的坦克外,我们还希望在不同场合中为其增加以下一种或多种功能;比如红外线夜视功能,比如水陆两栖功能,比如卫星定位功能等等。按类继承的作法如下:

1//抽象坦克2publicabstractclassTank3{4publicabstractvoidShot();5publicabstractvoidRun();6}

各种型号:

1//T50型号2publicclassT50:Tank3{4publicoverridevoidShot()5{6Console.WriteLine("T50坦克平均每秒射击5发子弹");7}8publicoverridevoidRun()9{10Console.WriteLine("T50坦克平均每时运行30公里");11}12}

1//T75型号2publicclassT75:Tank3{4publicoverridevoidShot()5{6Console.WriteLine("T75坦克平均每秒射击10发子弹");7}8publicoverridevoidRun()9{10Console.WriteLine("T75坦克平均每时运行35公里");11}12}

1//T90型号2publicclassT90:Tank3{4publicoverridevoidShot()5{6Console.WriteLine("T90坦克平均每秒射击10发子弹");7}8publicoverridevoidRun()9{10Console.WriteLine("T90坦克平均每时运行40公里");11}12}

各种不同功能的组合:比如IA具有红外功能接口、IB具有水陆两栖功能接口、IC具有卫星定位功能接口。

1//T50坦克各种功能的组合2publicclassT50A:T50,IA3{4//具有红外功能5}6publicclassT50B:T50,IB7{8//具有水陆两栖功能9}10publicclassT50C:T50,IC11{1213}14publicclassT50AB:T50,IA,IB15{}18publicclassT50AC:T50,IA,IC19{}20publicclassT50BC:T50,IB,IC21{}22publicclassT50ABC:T50,IA,IB,IC23{}

12//T75各种不同型号坦克各种功能的组合3publicclassT75A:T75,IA4{5//具有红外功能6}7publicclassT75B:T75,IB8{9//具有水陆两栖功能10}11publicclassT75C:T75,IC12{13//具有卫星定位功能14}15publicclassT75AB:T75,IA,IB16{17//具有红外、水陆两栖功能18}19publicclassT75AC:T75,IA,IC20{21//具有红外、卫星定位功能22}23publicclassT75BC:T75,IB,IC24{25//具有水陆两栖、卫星定位功能26}27publicclassT75ABC:T75,IA,IB,IC28{29//具有红外、水陆两栖、卫星定位功能30}

12//T90各种不同型号坦克各种功能的组合3publicclassT90A:T90,IA4{5//具有红外功能6}7publicclassT90B:T90,IB8{9//具有水陆两栖功能10}11publicclassT90C:T90,IC12{13//具有卫星定位功能14}15publicclassT90AB:T90,IA,IB16{17//具有红外、水陆两栖功能18}19publicclassT90AC:T90,IA,IC20{21//具有红外、卫星定位功能22}23publicclassT90BC:T90,IB,IC24{25//具有水陆两栖、卫星定位功能26}27publicclassT90ABC:T90,IA,IB,IC28{29//具有红外、水陆两栖、卫星定位功能30}

由此可见,如果用类继承实现,子类会爆炸式地增长。动机(Motivate):上述描述的问题根源在于我们“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态物质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能组合)会导致更多子类的膨胀(多继承)。如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?意图(Intent):动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。------《设计模式》GOF结构图(Struct):生活中的例子:适用性:需要扩展一个类的功能,或给一个类增加附加责任。需要动态地给一个对象增加功能,这些功能可以再动态地撤销。需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。实现代码:

1namespaceDecorator2{3publicabstractclassTank4{5publicabstractvoidShot();6publicabstractvoidRun();7}8}

1namespaceDecorator2{3publicclassT50:Tank4{5publicoverridevoidShot()6{7Console.WriteLine("T50坦克平均每秒射击5发子弹");8}9publicoverridevoidRun()10{11Console.WriteLine("T50坦克平均每时运行30公里");12}13}14}

1namespaceDecorator2{3publicclassT75:Tank4{5publicoverridevoidShot()6{7Console.WriteLine("T75坦克平均每秒射击10发子弹");8}9publicoverridevoidRun()10{11Console.WriteLine("T75坦克平均每时运行35公里");12}13}14}

1namespaceDecorator2{3publicclassT90:Tank4{5publicoverridevoidShot()6{7Console.WriteLine("T90坦克平均每秒射击10发子弹");8}9publicoverridevoidRun()10{11Console.WriteLine("T90坦克平均每时运行40公里");12}13}14}

1namespaceDecorator2{3publicabstractclassDecorator:Tank//DoAs接口继承非实现继承4{5privateTanktank;//Hasa对象组合6publicDecorator(Tanktank)7{8this.tank=tank;9}10publicoverridevoidShot()11{12tank.Shot();13}14publicoverridevoidRun()15{16tank.Run();17}18}19}20

12namespaceDecorator3{4publicclassDecoratorA:Decorator5{6publicDecoratorA(Tanktank):base(tank)7{8}9publicoverridevoidShot()10{11//Dosomeextension//功能扩展且有红外功能12base.Shot();13}14publicoverridevoidRun()15{1617base.Run();18}19}20}

1namespaceDecorator2{3publicclassDecoratorB:Decorator4{5publicDecoratorB(Tanktank):base(tank)6{7}8publicoverridevoidShot()9{10//Dosomeextension//功能扩展且有水陆两栖功能11base.Shot();12}13publicoverridevoidRun()14{1516base.Run();17}18}19}20

1namespaceDecorator2{3publicclassDecoratorC:Decorator4{5publicDecoratorC(Tanktank):base(tank)6{7}8publicoverridevoidShot()9{10//Dosomeextension//功能扩展且有卫星定位功能11base.Shot();12}13publicoverridevoidRun()14{1516base.Run();17}1819}20}

1classProgram2{3staticvoidMain(string[]args)4{5Tanktank=newT50();6DecoratorAda=newDecoratorA(tank);//且有红外功能7DecoratorBdb=newDecoratorB(da);//且有红外和水陆两栖功能8DecoratorCdc=newDecoratorC(db);//且有红外、水陆两栖、卫星定们三种功能9dc.Shot();10dc.Run();11}12}

Decorator模式的几个要点:通过采用组合、而非继承的手法,Decorator模式实现了在运行时动态地扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了单独使用继承带来的“灵活性差"和"多子类衍生问题"。Component类在Decorator模式中充当抽象接口的角色,不应该去实现具体的行为。而且Decorator类对于Component类应该透明---换言之Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能。Decorator类在接口上表现为is-aComponent的继承关系,即Decorator类继承了Component类所且有的接口。但在实现上又表现hasaComponent的组合关系,即Decorator类又使用了另外一个Component类。我们可以使用一个或者多个Decorator对象来“装饰”一个Component对象,且装饰后的对象仍然是一个Component对象。Decorator模式并非解决”多子类衍生的多继承“问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”------是为“装饰”的含义。Decorator在.NET(Stream)中的应用:可以看到,BufferedStream和CryptoStream其实就是两个包装类,这里的Decorator模式省略了抽象装饰角色(Decorator),示例代码如下:

1classProgram23{45publicstaticvoidMain(string[]args)67{89MemoryStreamms=1011newMemoryStream(newbyte[]{100,456,864,222,567});12131415//扩展了缓冲的功能1617BufferedStreambuff=newBufferedStream(ms);18192021//扩展了缓冲,加密的功能2223CryptoStreamcrypto=newCryptoStream(buff);2425}2627}

通过反编译,可以看到BufferedStream类的代码(只列出部分),它是继承于Stream类:

1publicsealedclassBufferedStream:Stream23{45//Methods67privateBufferedStream();89publicBufferedStream(Streamstream);1011publicBufferedStream(Streamstream,intbufferSize);1213//Fields1415privateint_bufferSize;1617privateStream_s;1819}

动机(Motivate):组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。意图(Intent):将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。-----------《设计模式》GOF结构图(Struct):生活中的例子:适用性:

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

1publicabstractclassGraphics2{3protectedstring_name;45publicGraphics(stringname)6{7this._name=name;8}9publicabstractvoidDraw();10}1112publicclassPicture:Graphics13{14publicPicture(stringname)15:base(name)16{}17publicoverridevoidDraw()18{19//20}2122publicArrayListGetChilds()23{24//返回所有的子对象25}26}

而其他作为树枝构件,实现代码如下:

1publicclassLine:Graphics2{3publicLine(stringname)4:base(name)5{}67publicoverridevoidDraw()8{9Console.WriteLine("Drawa"+_name.ToString());10}11}1213publicclassCircle:Graphics14{15publicCircle(stringname)16:base(name)17{}1819publicoverridevoidDraw()20{21Console.WriteLine("Drawa"+_name.ToString());22}23}2425publicclassRectangle:Graphics26{27publicRectangle(stringname)28:base(name)29{}3031publicoverridevoidDraw()32{33Console.WriteLine("Drawa"+_name.ToString());34}35}

现在我们要对该图像元素进行处理:在客户端程序中,需要判断返回对象的具体类型到底是基本图像元素,还是复合图像元素。如果是复合图像元素,我们将要用递归去处理,然而这种处理的结果却增加了客户端程序与复杂图像元素内部结构之间的依赖,那么我们如何去解耦这种关系呢?我们希望的是客户程序可以像处理基本图像元素一样来处理复合图像元素,这就要引入Composite模式了,需要把对于子对象的管理工作交给复合图像元素,为了进行子对象的管理,它必须提供必要的Add(),Remove()等方法,类结构图如下:示意代码:

这样引入Composite模式后,客户端程序不再依赖于复合图像元素的内部实现了。然而,我们程序中仍然存在着问题,因为Line,Rectangle,Circle已经没有了子对象,它是一个基本图像元素,因此Add(),Remove()的方法对于它来说没有任何意义,而且把这种错误不会在编译的时候报错,把错误放在了运行期,我们希望能够捕获到这类错误,并加以处理,稍微改进一下我们的程序:

1publicclassLine:Graphics2{3publicLine(stringname)4:base(name)5{}67publicoverridevoidDraw()8{9Console.WriteLine("Drawa"+_name.ToString());10}11publicoverridevoidAdd(Graphicsg)12{13//抛出一个我们自定义的异常14}15publicoverridevoidRemove(Graphicsg)16{17//抛出一个我们自定义的异常18}19}

1publicabstractclassGraphics2{3protectedstring_name;45publicGraphics(stringname)6{7this._name=name;8}9publicabstractvoidDraw();10}1112publicclassPicture:Graphics13{14protectedArrayListpicList=newArrayList();1516publicPicture(stringname)17:base(name)18{}19publicoverridevoidDraw()20{21Console.WriteLine("Drawa"+_name.ToString());2223foreach(GraphicsginpicList)24{25g.Draw();26}27}2829publicvoidAdd(Graphicsg)30{31picList.Add(g);32}33publicvoidRemove(Graphicsg)34{35picList.Remove(g);36}37}3839publicclassLine:Graphics40{41publicLine(stringname)42:base(name)43{}4445publicoverridevoidDraw()46{47Console.WriteLine("Drawa"+_name.ToString());48}49}5051publicclassCircle:Graphics52{53publicCircle(stringname)54:base(name)55{}5657publicoverridevoidDraw()58{59Console.WriteLine("Drawa"+_name.ToString());60}61}6263publicclassRectangle:Graphics64{65publicRectangle(stringname)66:base(name)67{}6869publicoverridevoidDraw()70{71Console.WriteLine("Drawa"+_name.ToString());72}73}

这种方式属于安全式的Composite模式,在这种方式下,虽然避免了前面所讨论的错误,但是它也使得叶子节点和树枝构件具有不一样的接口。这种方式和透明式的Composite各有优劣,具体使用哪一个,需要根据问题的实际情况而定。通过Composite模式,客户程序在调用Draw()的时候不用再去判断复杂图像元素中的子对象到底是基本图像元素,还是复杂图像元素,看一下简单的客户端调用:

1publicclassApp2{3publicstaticvoidMain()4{5Pictureroot=newPicture("Root");67root.Add(newLine("Line"));8root.Add(newCircle("Circle"));910Rectangler=newRectangle("Rectangle");11root.Add(r);1213root.Draw();

Composite模式实现要点:

1.Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。

2.将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复内部实现结构——发生依赖关系,从而更能“应对变化”。

4.Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。

动机(Motivate):在软件开发系统中,客户程序经常会与复杂系统的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化。那么如何简化客户程序与子系统之间的交互接口?如何将复杂系统的内部子系统与客户程序之间的依赖解耦?意图(Intent):为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。--------《设计模式》GOF结构图(Struct):适用性:

1.为一个复杂子系统提供一个简单接口。

2.提高子系统的独立性。

3.在层次化结构中,可以使用Facade模式定义系统中每一层的入口。生活中的例子:代码实现:我们平时的开发中其实已经不知不觉的在用Faade模式,现在来考虑这样一个抵押系统,当有一个客户来时,有如下几件事情需要确认:到银行子系统查询他是否有足够多的存款,到信用子系统查询他是否有良好的信用,到贷款子系统查询他有无贷款劣迹。只有这三个子系统都通过时才可进行抵押。我们先不考虑Faade模式,那么客户程序就要直接访问这些子系统,分别进行判断。类结构图下:在这个程序中,我们首先要有一个顾客类,它是一个纯数据类,并无任何操作,示意代码:

1//顾客类2publicclassCustomer3{4privatestring_name;56publicCustomer(stringname)7{8this._name=name;9}1011publicstringName12{13get{return_name;}14}15}

下面这三个类均是子系统类,示意代码:

1//银行子系统2publicclassBank3{4publicboolHasSufficientSavings(Customerc,intamount)5{6Console.WriteLine("Checkbankfor"+c.Name);7returntrue;8}9}1011//信用子系统12publicclassCredit13{14publicboolHasGoodCredit(Customerc)15{16Console.WriteLine("Checkcreditfor"+c.Name);17returntrue;18}19}2021//贷款子系统22publicclassLoan23{24publicboolHasNoBadLoans(Customerc)25{26Console.WriteLine("Checkloansfor"+c.Name);27returntrue;28}29}

看客户程序的调用:

1//客户程序2publicclassMainApp3{4privateconstint_amount=12000;56publicstaticvoidMain()7{8Bankbank=newBank();9Loanloan=newLoan();10Creditcredit=newCredit();1112Customercustomer=newCustomer("AnnMcKinsey");1314booleligible=true;1516if(!bank.HasSufficientSavings(customer,_amount))17{18eligible=false;19}20elseif(!loan.HasNoBadLoans(customer))21{22eligible=false;23}24elseif(!credit.HasGoodCredit(customer))25{26eligible=false;27}2829Console.WriteLine("\n"+customer.Name+"hasbeen"+(eligible"Approved":"Rejected"));30Console.ReadLine();31}32}

可以看到,在不用Faade模式的情况下,客户程序与三个子系统都发生了耦合,这种耦合使得客户程序依赖于子系统,当子系统化时,客户程序也将面临很多变化的挑战。一个合情合理的设计就是为这些子系统创建一个统一的接口,这个接口简化了客户程序的判断操作。看一下引入Faade模式后的类结构图:变外观类Mortage的实现如下:

1/外观类2publicclassMortgage3{4privateBankbank=newBank();5privateLoanloan=newLoan();6privateCreditcredit=newCredit();78publicboolIsEligible(Customercust,intamount)9{10Console.WriteLine("{0}appliesfor{1:C}loan\n",11cust.Name,amount);1213booleligible=true;1415if(!bank.HasSufficientSavings(cust,amount))16{17eligible=false;18}19elseif(!loan.HasNoBadLoans(cust))20{21eligible=false;22}23elseif(!credit.HasGoodCredit(cust))24{25eligible=false;26}2728returneligible;29}30}

顾客类和子系统类的实现仍然如下:

1//银行子系统2publicclassBank3{4publicboolHasSufficientSavings(Customerc,intamount)5{6Console.WriteLine("Checkbankfor"+c.Name);7returntrue;8}9}1011//信用证子系统12publicclassCredit13{14publicboolHasGoodCredit(Customerc)15{16Console.WriteLine("Checkcreditfor"+c.Name);17returntrue;18}19}2021//贷款子系统22publicclassLoan23{24publicboolHasNoBadLoans(Customerc)25{26Console.WriteLine("Checkloansfor"+c.Name);27returntrue;28}29}3031//顾客类32publicclassCustomer33{34privatestringname;3536publicCustomer(stringname)37{38this.name=name;39}4041publicstringName42{43get{returnname;}44}45}

而此时客户程序的实现:

1//客户程序类2publicclassMainApp3{4publicstaticvoidMain()5{6//外观7Mortgagemortgage=newMortgage();89Customercustomer=newCustomer("AnnMcKinsey");10booleligable=mortgage.IsEligible(customer,125000);1112Console.WriteLine("\n"+customer.Name+13"hasbeen"+(eligable"Approved":"Rejected"));14Console.ReadLine();15}16}

可以看到引入Faade模式后,客户程序只与Mortgage发生依赖,也就是Mortgage屏蔽了子系统之间的复杂的操作,达到了解耦内部子系统与客户程序之间的依赖。

.NET架构中的Faade模式

Faade模式在实际开发中最多的运用当属开发N层架构的应用程序了,一个典型的N层结构如下:

在这个架构中,总共分为四个逻辑层,分别为:用户层UI,业务外观层BusinessFaade,业务规则层BusinessRule,数据访问层DataAccess。其中BusinessFaade层的职责如下:

l从“用户”层接收用户输入

l如果请求需要对数据进行只读访问,则可能使用“数据访问”层

l将请求传递到“业务规则”层

l将响应从“业务规则”层返回到“用户”层

l在对“业务规则”层的调用之间维护临时状态

对这一架构最好的体现就是Duwamish示例了。在该应用程序中,有部分操作只是简单的从数据库根据条件提取数据,不需要经过任何处理,而直接将数据显示到网页上,比如查询某类别的图书列表。而另外一些操作,比如计算定单中图书的总价并根据顾客的级别计算回扣等等,这部分往往有许多不同的功能的类,操作起来也比较复杂。如果采用传统的三层结构,这些商业逻辑一般是会放在中间层,那么对内部的这些大量种类繁多,使用方法也各异的不同的类的调用任务,就完全落到了表示层。这样势必会增加表示层的代码量,将表示层的任务复杂化,和表示层只负责接受用户的输入并返回结果的任务不太相称,并增加了层与层之间的耦合程度。于是就引入了一个Faade层,让这个Facade来负责管理系统内部类的调用,并为表示层提供了一个单一而简单的接口。看一下Duwamish结构图:

从图中可以看到,UI层将请求发送给业务外观层,业务外观层对请求进行初步的处理,判断是否需要调用业务规则层,还是直接调用数据访问层获取数据。最后由数据访问层访问数据库并按照来时的步骤返回结果到UI层,来看具体的代码实现。

在获取商品目录的时候,WebUI调用业务外观层:

1productSystem=newProductSystem();2categorySet=productSystem.GetCategories(categoryID);

业务外观层直接调用了数据访问层:

1publicCategoryDataGetCategories(intcategoryId)2{3//4//Checkpreconditions5//6ApplicationAssert.CheckCondition(categoryId>=0,"InvalidCategoryId",ApplicationAssert.LineNumber);7//8//Retrievethedata9//10using(CategoriesaccessCategories=newCategories())11{12returnaccessCategories.GetCategories(categoryId);13}1415}

在添加订单时,UI调用业务外观层:

1publicvoidAddOrder()2{3ApplicationAssert.CheckCondition(cartOrderData!=null,"Orderrequiresdata",ApplicationAssert.LineNumber);45//Writetracelog.6ApplicationLog.WriteTrace("Duwamish7.Web.Cart.AddOrder:\r\nCustomerId:"+7cartOrderData.Tables[OrderData.CUSTOMER_TABLE].Rows[0][OrderData.PKID_FIELD].ToString());8cartOrderData=(newOrderSystem()).AddOrder(cartOrderData);9}

业务外观层调用业务规则层:

1publicOrderDataAddOrder(OrderDataorder)2{3//4//Checkpreconditions5//6ApplicationAssert.CheckCondition(order!=null,"Orderisrequired",ApplicationAssert.LineNumber);78(newBusinessRules.Order()).InsertOrder(order);9returnorder;10}

业务规则层进行复杂的逻辑处理后,再调用数据访问层:

Facade模式的个要点:从客户程序的角度来看,Facade模式不仅简化了整个组件系统的接口,同时对于组件内部与外部客户程序来说,从某种程度上也达到了一种“解耦”的效果----内部子系统的任何变化不会影响到Facade接口的变化。Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facdae很多时候更是一种架构设计模式。注意区分Facade模式、Adapter模式、Bridge模式与Decorator模式。Facade模式注重简化接口,Adapter模式注重转换接口,Bridge模式注重分离接口(抽象)与其实现,Decorator模式注重稳定接口的前提下为对象扩展功能。

LINQ是什么?它是LanguageIntegratedQuery。当我们要对数据库表进行查询的时候,我们一定会编写"select*fromsometablewhereID=.."的语句。好,那我们现在根据LINQ的语法,完全可以将我们熟悉的SQL中像"select","from","where"等语句在.NETFramework环境中顺利使用并且大大提高开发的效率。

下面我就牛刀小试,做个demo看看。

2.下载安装待完毕。

3.新建一个"LINQConsoleApplication"项目。

4.输入代码如下:

1usingSystem;2usingSystem.Collections.Generic;3usingSystem.Text;4usingSystem.Query;5usingSystem.Xml.XLinq;6usingSystem.Data.DLinq;78namespaceLINQConsoleApplication19{10classProgram11{12staticvoidMain(string[]args)13{14string[]aBunchOfWords={"One","Two","Hello","World",1516"Four","Five"};17varresult=18fromsinaBunchOfWords//querythestringarray19wheres.Length==5//forallwordswithlength=520selects;//andreturnthestring21foreach(varsinresult){22Console.WriteLine(s);//print23}24}25}26}运行结果如下:HelloWorldprintanykeytocontinue...

面向对象的代价面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。但是,在某些特殊的应用中下,由于对象的数量太大,采用面向对象会给系统带来难以承受的内存开销。比如:图形应用中的图元等对象、字处理应用中的字符对象等。动机(Motivate):采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价--------主要指内存需求方面的代价。如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?意图(Intent):运用共享技术有效地支持大量细粒度的对象。-------《设计模式》GOF结构(Struct):适用性:

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

1、一个系统有大量的对象。

2、这些对象耗费大量的内存。

3、这些对象的状态中的大部分都可以外部化。

4、这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。

5、软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

好了,现在看到的这段代码可以说是很好地符合了面向对象的思想,但是同时我们也为此付出了沉重的代价,那就是性能上的开销,可以想象,在一篇文档中,字符的数量远不止几百个这么简单,可能上千上万,内存中就同时存在了上千上万个Charactor对象,这样的内存开销是可想而知的。进一步分析可以发现,虽然我们需要的Charactor实例非常多,这些实例之间只不过是状态不同而已,也就是说这些实例的状态数量是很少的。所以我们并不需要这么多的独立的Charactor实例,而只需要为每一种Charactor状态创建一个实例,让整个字符处理软件共享这些实例就可以了。看这样一幅示意图:现在我们看到的A,B,C三个字符是共享的,也就是说如果文档中任何地方需要这三个字符,只需要使用共享的这三个实例就可以了。然而我们发现单纯的这样共享也是有问题的。虽然文档中的用到了很多的A字符,虽然字符的symbol等是相同的,它可以共享;但是它们的pointSize却是不相同的,即字符在文档中中的大小是不相同的,这个状态不可以共享。为解决这个问题,首先我们将不可共享的状态从类里面剔除出去,即去掉pointSize这个状态(只是暂时的J),类结构图如下所示:

好,现在类里面剩下的状态都可以共享了,下面我们要做的工作就是控制Charactor类的创建过程,即如果已经存在了“A”字符这样的实例,就不需要再创建,直接返回实例;如果没有,则创建一个新的实例。如果把这项工作交给Charactor类,即Charactor类在负责它自身职责的同时也要负责管理Charactor实例的管理工作,这在一定程度上有可能违背类的单一职责原则,因此,需要一个单独的类来做这项工作,引入CharactorFactory类,结构图如下:

1//"CharactorFactory"2publicclassCharactorFactory3{4//Fields5privateHashtablecharactors=newHashtable();67//Constructor8publicCharactorFactory()9{10charactors.Add("A",newCharactorA());11charactors.Add("B",newCharactorB());12charactors.Add("C",newCharactorC());13}1415//Method16publicCharactorGetCharactor(stringkey)17{18Charactorcharactor=charactors[key]asCharactor;1920if(charactor==null)21{22switch(key)23{24case"A":charactor=newCharactorA();break;25case"B":charactor=newCharactorB();break;26case"C":charactor=newCharactorC();break;27//28}29charactors.Add(key,charactor);30}31returncharactor;32}33}

到这里已经完全解决了可以共享的状态(这里很丑陋的一个地方是出现了switch语句,但这可以通过别的办法消除,为了简单期间我们先保持这种写法)。下面的工作就是处理刚才被我们剔除出去的那些不可共享的状态,因为虽然将那些状态移除了,但是Charactor对象仍然需要这些状态,被我们剥离后这些对象根本就无法工作,所以需要将这些状态外部化。首先会想到一种比较简单的解决方案就是对于不能共享的那些状态,不需要去在Charactor类中设置,而直接在客户程序代码中进行设置,类结构图如下:

1publicclassProgram2{3publicstaticvoidMain()4{5Charactorca=newCharactorA();6Charactorcb=newCharactorB();7Charactorcc=newCharactorC();89//显示字符1011//设置字符的大小ChangeSize();12}1314publicvoidChangeSize()15{16//在这里设置字符的大小17}18}

按照这样的实现思路,可以发现如果有多个客户端程序使用的话,会出现大量的重复性的逻辑,用重构的术语来说是出现了代码的坏味道,不利于代码的复用和维护;另外把这些状态和行为移到客户程序里面破坏了封装性的原则。再次转变我们的实现思路,可以确定的是这些状态仍然属于Charactor对象,所以它还是应该出现在Charactor类中,对于不同的状态可以采取在客户程序中通过参数化的方式传入。类结构图如下:

可以看到这样的实现明显优于第一种实现思路。好了,到这里我们就到到了通过Flyweight模式实现了优化资源的这样一个目的。在这个过程中,还有如下几点需要说明:

1.引入CharactorFactory是个关键,在这里创建对象已经不是new一个Charactor对象那么简单,而必须用工厂方法封装起来。

2.在这个例子中把Charactor对象作为Flyweight对象是否准确值的考虑,这里只是为了说明Flyweight模式,至于在实际应用中,哪些对象需要作为Flyweight对象是要经过很好的计算得知,而绝不是凭空臆想。

3.区分内外部状态很重要,这是享元对象能做到享元的关键所在。

到这里,其实我们的讨论还没有结束。有人可能会提出如下问题,享元对象(Charactor)在这个系统中相对于每一个内部状态而言它是唯一的,这跟单件模式有什么区别呢?这个问题已经很好回答了,那就是单件类是不能直接被实例化的,而享元类是可以被实例化的。事实上在这里面真正被设计为单件的应该是享元工厂(不是享元)类,因为如果创建很多个享元工厂的实例,那我们所做的一切努力都是白费的,并没有减少对象的个数。修改后的类结构图如下:

1//"CharactorFactory"2publicclassCharactorFactory3{4//Fields5privateHashtablecharactors=newHashtable();67privateCharactorFactoryinstance;8//Constructor9privateCharactorFactory()10{11charactors.Add("A",newCharactorA());12charactors.Add("B",newCharactorB());13charactors.Add("C",newCharactorC());14}1516//Property17publicCharactorFactoryInstance18{19get20{21if(instance!=null)22{23instance=newCharactorFactory();24}25returninstance;26}27}2829//Method30publicCharactorGetCharactor(stringkey)31{32Charactorcharactor=charactors[key]asCharactor;3334if(charactor==null)35{36switch(key)37{38case"A":charactor=newCharactorA();break;39case"B":charactor=newCharactorB();break;40case"C":charactor=newCharactorC();break;41//42}43charactors.Add(key,charactor);44}45returncharactor;46}47}

.NET框架中的应用:Flyweight更多时候的时候一种底层的设计模式,在我们的实际应用程序中使用的并不是很多。在.NET中的String类型其实就是运用了Flyweight模式。可以想象,如果每次执行strings1=“abcd”操作,都创建一个新的字符串对象的话,内存的开销会很大。所以.NET中如果第一次创建了这样的一个字符串对象s1,下次再创建相同的字符串s2时只是把它的引用指向“abcd”,这样就实现了“abcd”在内存中的共享。可以通过下面一个简单的程序来演示s1和s2的引用是否一致:

1publicclassProgram2{3publicstaticvoidMain(string[]args)4{5strings1="abcd";6strings2="abcd";78Console.WriteLine(Object.ReferenceEquals(s1,s2));910Console.ReadLine();11}12}

Flyweight实现要点:

1.面向对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。

2.Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。

1publicclassMath2{3publicdoubleAdd(doublex,doubley)4{5returnx+y;6}78publicdoubleSub(doublex,doubley)9{10returnx-y;11}1213publicdoubleMul(doublex,doubley)14{15returnx*y;16}1718publicdoubleDev(doublex,doubley)19{20returnx/y;21}22}

如果说这个计算程序部署在我们本地计算机上,使用就非常之简单了,我们也就不用去考虑Proxy模式了。但现在问题是这个Math类并没有部署在我们本地,而是部署在一台服务器上,也就是说Math类根本和我们的客户程序不在同一个地址空间之内,我们现在要面对的是跨越Internet这样一个网络障碍:这时候调用Math类的方法就没有下面那么简单了,因为我们更多的还要去考虑网络的问题,对接收到的结果解包等一系列操作。

1publicclassApp2{3publicstaticvoidMain()4{5Mathmath=newMath();67//对接收到的结果数据进行解包89doubleaddresult=math.Add(2,3);1011doublesubresult=math.Sub(6,4);1213doublemulresult=math.Mul(2,3);1415doubledevresult=math.Dev(2,3);16}17}

为了解决由于网络等障碍引起复杂性,就引出了Proxy模式,我们使用一个本地的代理来替Math类打点一切,即为我们的系统引入了一层间接层,示意图如下:我们在MathProxy中对实现Math数据类的访问,让MathProxy来代替网络上的Math类,这样我们看到MathProxy就好像是本地Math类,它与客户程序处在了同一地址空间内:

1publicclassMathProxy2{3privateMathmath=newMath();45//以下的方法中,可能不仅仅是简单的调用Math类的方法67publicdoubleAdd(doublex,doubley)8{9returnmath.Add(x,y);10}1112publicdoubleSub(doublex,doubley)13{14returnmath.Sub(x,y);15}1617publicdoubleMul(doublex,doubley)18{19returnmath.Mul(x,y);20}2122publicdoubleDev(doublex,doubley)23{24returnmath.Dev(x,y);25}26}

现在可以说我们已经实现了对Math类的代理,存在的一个问题是我们在MathProxy类中调用了原实现类Math的方法,但是Math并不一定实现了所有的方法,为了强迫Math类实现所有的方法,另一方面,为了我们更加透明的去操作对象,我们在Math类和MathProxy类的基础上加上一层抽象,即它们都实现与IMath接口,示意图如下:

1publicinterfaceIMath2{3doubleAdd(doublex,doubley);45doubleSub(doublex,doubley);67doubleMul(doublex,doubley);89doubleDev(doublex,doubley);10}1112Math类和MathProxy类分别实现IMath接口:1314publicclassMathProxy:IMath15{16//17}1819publicclassMath:IMath20{21//22}

此时我们在客户程序中就可以像使用Math类一样来使用MathProxy类了:

1publicclassApp2{3publicstaticvoidMain()4{5MathProxyproxy=newMathProxy();67doubleaddresult=proxy.Add(2,3);89doublesubresult=proxy.Sub(6,4);1011doublemulresult=proxy.Mul(2,3);1213doubledevresult=proxy.Dev(2,3);14}15}

到这儿整个使用Proxy模式的过程就完成了,回顾前面我们的解决方案,无非是在客户程序和Math类之间加了一个间接层,这也是我们比较常见的解决问题的手段之一。另外,对于程序中的接口Imath,并不是必须的,大多数情况下,我们为了保持对对象操作的透明性,并强制实现类实现代理类所要调用的所有的方法,我们会让它们实现与同一个接口。但是我们说代理类它其实只是在一定程度上代表了原来的实现类,所以它们有时候也可以不实现于同一个接口。代理模式实现要点:

2.虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载;代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。

3.Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。

5.Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

6.防火墙(Firewall)代理:保护目标,不让恶意用户接近。

7.同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。

8.智能引用(SmartReference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。

1publicclassmyclass2{3publicvirtualintmyint()4{5///函数体;6}7}8classmyclass1:myclass9{10publicoverrideintmyint()11{12//函数休;13}14}

1publicabstractclassmyclass2{3publicabstractintmyint();4}5publicclassmyclass1:myclass6{7publicoverrideintmyint()8{9//函数体;10}11}

[.NET(C#)]把attribute翻译成特性,用来标识类,方法把property翻译为属性,性质,用于存取类的字段把markup翻译成标记,tag还是翻译成标签比较好

[.NET(C#)].NETFramework的核心是其运行库的执行环境。称为公共语言运行库(CLR)或.NET运行库.通常将在CLR的控制下运行的代码称为托管代码(managedcode).在CLR执行开发的源代码之前,需要编译它们为中间语言(IL),CLR再把IL编译为平台专用的代码。

程序集(assembly)是包含编译好的,面向.NETFramework的代码的逻辑单元.可执行代码和库代码使用相同的程序集结构.程序集的一个重要特性是它们包含的元数据描述了对应代码中定义的类型和方法.

[.NET(C#)]ASP页面有时显示比较慢,因为服务器端代码是解释性的不是编译的.由于ASP代码不是结构化的所以难于维护,加上ASP不支持错误处理和语法检查。而ASP.NET页面是结构化的。每个页面都是一个继承了.NET类System.Web.UI.Page的类。另外ASP.NET的后台编码功能允许进一步采用结构化的方式.页面请求是和WEB服务器在编译后高速缓存ASP.NET页面。

方法参数上的ref方法参数关键字使方法引用传递到方法的同一个变量。当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用ref参数,必须将参数作为ref参数显式传递到方法。ref参数的值被传递到ref参数。传递到ref参数的参数必须最先初始化。将此方法与out参数相比,后者的参数在传递到out参数之前不必显式初始化。属性不是变量,不能作为ref参数传递。

两者都是按地址传递的,使用后都将改变原来的数值。ref可以把参数的数值传递进函数,但是out是要把参数清空就是说你无法把一个数值从out传递进去的,out进去后,参数的数值为空,所以你必须初始化一次。两个的区别:ref是有进有出,out是只出不进。

ADO。NET相对于ADO等主要有什么改进?1:ado.net不依赖于oledb提供程序,而是使用.net托管提供的程序,2:不使用com3:不在支持动态游标和服务器端游4:,可以断开connection而保留当前数据集可用5:强类型转换6:xml支持

[.NET(C#)]C#中,stringstr=null与stringstr="",说明区别。stringstr=""初始化对象分配空间而stringstr=null初始化对象

[.NET(C#)]DataGrid的Datasouse可以连接什么数据源DataTableDataViewDataSetDataViewManager任何实现IListSource接口的组件任何实现IList接口的组件

[.NET(C#)]概述反射和序列化反射:公共语言运行库加载器管理应用程序域。这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。

序列化:序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

[.NET(C#)]可访问性级别有哪几种public访问不受限制。protected访问仅限于包含类或从包含类派生的类型。internal访问仅限于当前程序集。protectedinternal访问仅限于从包含类派生的当前程序集或类型。private访问仅限于包含类型。

[.NET(C#)]O/RMapping的原理:利用反射,配置将对象和数据库表映射。

[.NET(C#)]sealed修饰符有什么特点sealed修饰符表示密封,用于类时,表示该类不能再被继承不能和abstract同时使用,因为这两个修饰符在含义上互相排斥用于方法和属性时,表示该方法或属性不能再被继承,必须和override关键字一起使用因为使用sealed修饰符的方法或属性肯定是基类中相应的虚成员通常用于实现第三方类库时不想被客户端继承,或用于没有必要再继承的类以防止滥用继承造成层次结构体系混乱恰当的利用sealed修饰符也可以提高一定的运行效率,因为不用考虑继承类会重写该成员

3.内部结构:结构:没有默认的构造函数,但是可以添加构造函数没有析构函数没有abstract和sealed(因为不能继承)不能有protected修饰符可以不使用new初始化在结构中初始化实例字段是错误的类:有默认的构造函数有析构函数可以使用abstract和sealed有protected修饰符必须使用new初始化

接口(interface)是抽像类的变体。在接口中,所有方法都是抽像的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽像的,没有一个有程序体。接口只可以定义staticfinal成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。

当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对像上调用接口的方法。由于有抽像类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof运算符可以用来决定某对象的类是否实现了接口。

接口可以继承接口。抽像类可以实现(implements)接口抽像类是否可继承实体类(concreteclass),但前提是实体类必须有明确的构造函数。

[.NET(C#)]什么叫应用程序域?什么是托管代码?什么是强类型系统?什么是装箱和拆箱?什么是重载?CTS、CLS和CLR分别作何解释?

应用程序域:应用程序域为安全性、可靠性、版本控制以及卸载程序集提供了隔离边界。应用程序域通常由运行库宿主创建,运行库宿主负责在运行应用程序之前引导公共语言运行库。应用程序域提供了一个更安全、用途更广的处理单元,公共语言运行库可使用该单元提供应用程序之间的隔离。应用程序域可以理解为一种轻量级进程。起到安全的作用。占用资源小。

托管代码:使用基于公共语言运行库的语言编译器开发的代码称为托管代码;托管代码具有许多优点,例如:跨语言集成、跨语言异常处理、增强的安全性、版本控制和部署支持、简化的组件交互模型、调试和分析服务等。

装箱和拆箱:从值类型接口转换到引用类型:装箱。从引用类型转换到值类型:拆箱。装箱和拆箱使值类型能够被视为对象。对值类型装箱将把该值类型打包到Object引用类型的一个实例中。这使得值类型可以存储于垃圾回收堆中。拆箱将从对象中提取值类型。

重载:每个类型成员都有一个唯一的签名。方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成。只要签名不同,就可以在一种类型内定义具有相同名称的多种方法。当定义两种或多种具有相同名称的方法时,就称作重载。

CTS通用类型系统(commontypesystem):一种确定公共语言运行库如何定义、使用和管理类型的规范。

CLR公共语言运行库:.NETFramework提供了一个称为公共语言运行库的运行时环境.它运行代码并提供使开发过程更轻松的服务。

CLS公共语言规范:要和其他对象完全交互,而不管这些对象是以何种语言实现的.对象必须只向调用方公开那些它们必须与之互用的所有语言的通用功能。为此定义了公共语言规范(CLS),它是许多应用程序所需的一套基本语言功能。

[.NET(C#)]值类型和引用类型的区别?基于值类型的变量直接包含值。将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身。

值类型引用类型内存分配地点分配在栈中分配在堆中效率效率高,不需要地址转换效率低,需要进行地址转换内存回收使用完后,立即回收使用完后,不是立即回收,等待GC回收赋值操作进行复制,创建一个同值新对象只是对原有对象的引用函数参数与返回值是对象的复制是原有对象的引用,并不产生新的对象类型扩展不易扩展容易扩展,方便与类型扩展

[.NET(C#)]如何理解委托委托类似于C++函数指针,但它是类型安全的。委托允许将方法作为参数进行传递。委托可用于定义回调方法。委托可以链接在一起;例如,可以对一个事件调用多个方法。方法不需要与委托签名精确匹配。有关更多信息,请参见协变和逆变。C#2.0版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。

[.NET(C#)]C#中的接口和类有什么异同。异:不能直接实例化接口。接口不包含方法的实现。接口、类和结构可从多个接口继承。但是C#只支持单继承:类只能从一个基类继承实现。类定义可在不同的源文件之间进行拆分。

同:接口、类和结构可从多个接口继承。接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。接口可以包含事件、索引器、方法和属性。一个类可以实现多个接口。

[.NET(C#)]ASP.net的身份验证方式有哪些Windows身份验证提供程序提供有关如何将Windows身份验证与MicrosoftInternet信息服务(IIS)身份验证结合使用来确保ASP.NET应用程序安全的信息。

[.NET(C#)]活动目录的作用ActiveDirectory存储了有关网络对象的信息,并且让管理员和用户能够轻松地查找和使用这些信息。ActiveDirectory使用了一种结构化的数据存储方式,并以此作为基础对目录信息进行合乎逻辑的分层组织。

[.NET(C#)]解释一下UDDI、WSDL的意义及其作用UDDI:统一描述、发现和集成协议(UDDI,UniversalDescription,DiscoveryandIntegration)是一套基于Web的、分布式的、为Web服务提供的信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web服务注册以使得别的企业能够发现的访问协议的实现标准。UDDI提供了一组基于标准的规范用于描述和发现服务,还提供了一组基于因特网的实现。

WSDL:WSDL描述Web服务的公共接口。这是一个基于XML的关于如何与Web服务通讯和使用的服务描述;服务URL和命名空间网络服务的类型(可能还包括SOAP的函数调用,正像我所说过的,WSDL足够自如地去描述网络服务的广泛内容)有效函数列表每个函数的参数每个参数的类型每个函数的返回值及其数据类型

[.NET(C#)]什么是SOAP,有哪些应用。答:SOAP(SimpleObjectAccessProtocol)简单对象访问协议是在分散或分布式的环境中交换信息并执行远程过程调用的协议,是一个基于XML的协议。使用SOAP,不用考虑任何特定的传输协议(最常用的还是HTTP协议)可以允许任何类型的对象或代码,在任何平台上,以任何一直语言相互通信。这种相互通信采用的是XML格式的消息。

SOAP也被称作XMLP,为两个程序交换信息提供了一种标准的工作机制。在各类机构之间通过电子方式相互协作的情况下完全有必要为此制定相应的标准。

最后SOAP规范还定义了HTTP消息是怎样传输SOAP消息的。MSMQ、SMTP、TCP/IP都可以做SOAP的传输协议。

SOAP是一种轻量级协议,用于在分散型、分布式环境中交换结构化信息。SOAP利用XML技术定义一种可扩展的消息处理框架,它提供了一种可通过多种底层协议进行交换的消息结构。这种框架的设计思想是要独立于任何一种特定的编程模型和其他特定实现的语义。

SOAP定义了一种方法以便将XML消息从A点传送到B点。为此,它提供了一种基于XML且具有以下特性的消息处理框架:1)可扩展2)可通过多种底层网络协议使用3)独立于编程模型。

[.NET(C#)]如何部署一个ASP.net页面VS2005和VS2003都有发布机制。2003可以发布然后再复制部署。VS2005基本上可以直接部署到对应位置。

[.NET(C#)]GC是什么为什么要有GC答:GC是垃圾收集器。程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc()Runtime.getRuntime().gc()不过在C#中不能直接实现Finalize方法,而是在析构函数中调用基类的Finalize()方法

[.NET(C#)]列举ASP.NET页面之间传递值的几种方式。并说出他们的优缺点。答.1).使用QueryString,如....id=1;response.Redirect()....2).使用Session变量3).使用Server.Transfer

session(viewstate)简单,但易丢失application全局cookie简单,但可能不支持,可能被伪造inputttype="hidden"简单,可能被伪造url参数简单,显示于地址栏,长度有限数据库稳定,安全,但性能相对弱

[.NET(C#)]C#中索引器的实现过程,可以用任何类型进行索引?(比如数字)

[.NET(C#)]CTS、CLS、CLR分别作何解释?CTS:通用语言系统。CLS:通用语言规范。CLR:公共语言运行库。

[.NET(C#)].net中读写数据库需要用到那些类?他们的作用?DataSet:数据存储器。DataCommand:执行语句命令。DataAdapter:数据的集合,用语填充。

[.NET(C#)]在.net中,配件的意思是:程序集。(中间语言,源数据,资源,装配清单)

[.NET(C#)]常用的调用WebService的方法有哪些?答:1.使用WSDL.exe命令行工具。2.使用VS.NET中的AddWebReference菜单选项

[.NET(C#)]微软.NET构架下remoting和webservice两项技术的理解以及实际中的应用。.netRemoting的工作原理是:服务器端向客户端发送一个进程编号,一个程序域编号,以确定对象的位置。WS主要是可利用HTTP,穿透防火墙。而Remoting可以利用TCP/IP,二进制传送提高效率。remoting是.net中用来跨越machine,process,appdomain进行方法调用的技术对于三成结构的程序,就可以使用remoting技术来构建.它是分布应用的基础技术.相当于以前的DCOMWebService是一种构建应用程序的普通模型并能在所有支持internet网通讯的操作系统上实施。WebService令基于组件的开发和web的结合达到最佳

[.NET(C#)]启动一个线程是用run()还是start()答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态。这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

[.NET(C#)]构造器Constructor是否可被override构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。

[.NET(C#)]abstract的method不可同时是static,不可同时是native,不可同时是synchronized

[.NET(C#)]进程和线程的区别:进程是系统进行资源分配和调度的单位;线程是CPU调度和分派的单位.一个进程可以有多个线程,这些线程共享这个进程的资源。

[.NET(C#)]堆和栈的区别:栈:由编译器自动分配、释放。在函数体中定义的变量通常在栈上。堆:一般由程序员分配释放。用new、malloc等分配内存函数分配得到的就是在堆上。

[.NET(C#)]成员变量和成员函数前加static的作用:它们被称为常成员变量和常成员函数,又称为类成员变量和类成员函数。分别用来反映类的状态。比如类成员变量可以用来统计类实例的数量,类成员函数负责这种统计的动作。

[.NET(C#)]在c#中using和new这两个关键字有什么意义:using引入名称空间或者使用非托管资源new新建实例或者隐藏父类方法

[.NET(C#)]什么是code-Behind技术。答:ASPX,RESX和CS三个后缀的文件,这个就是代码分离.实现了HTML代码和服务器代码分离.方便代码编写和整理.

[.NET(C#)]XML与HTML的主要区别1.XML是区分大小写字母的,HTML不区分。2.在HTML中,如果上下文清楚地显示出段落或者列表键在何处结尾,那么你可以省略

或者之类的结束标记。在XML中,绝对不能省略掉结束标记。3.在XML中,拥有单个标记而没有匹配的结束标记的元素必须用一个/字符作为结尾。这样分析器就知道不用查找结束标记了。4.在XML中,属性值必须分装在引号中。在HTML中,引号是可用可不用的。5.在HTML中,可以拥有不带值的属性名。在XML中,所有的属性都必须带有相应的值。

[.NET(C#)].net的错误处理机制是什么?答:.net错误处理机制采用try->catch->finally结构.发生错误时,层层上抛,直到找到匹配的Catch为止。

[.NET(C#)]error和exception有什么区别:error表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。exception表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。

[.NET(C#)]UDP连接和TCP连接的异同:前者只管传,不管数据到不到,无须建立连接.后者保证传输的数据准确,须要连结.

[.NET(C#)]C#中所有对象共同的基类是:System.Object.

[.NET(C#)]System.String和System.StringBuilder有什么区别?System.String是不可变的字符串。String类是final类故不可以继承。System.StringBuilder存放了一个可变的字符串,并提供一些对这个字符串修改的方法。

[.NET(C#)]const和readonly有什么区别?const可以用于局部常量readonly实际是类的initonly字段,显然不能是局部的。

无处不在的TemplateMethod如果你只想掌握一种设计模式,那么它就是TemplateMethod!动机(Motivate):变化-----是软件设计的永恒主题,如何管理变化带来的复杂性?设计模式的艺术性和复杂度就在于如何分析,并发现系统中的变化和稳定点,并使用特定的设计方法来应对这种变化。意图(Intent):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。-------《设计模式》GOF结构图(Struct):适用性:

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

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

3.控制子类扩展。模板方法只在特定点调用“Hook”操作,这样就只允许在这些点进行扩展。生活中的例子:代码实现:

假如我们需要简单的读取Northwind数据库中的表的记录并显示出来。对于数据库操作,我们知道不管读取的是哪张表,它一般都应该经过如下这样的几步:

1.连接数据库(Connect)

2.执行查询命令(Select)

3.显示数据(Display)

4.断开数据库连接(Disconnect)

这些步骤是固定的,但是对于每一张具体的数据表所执行的查询却是不一样的。显然这需要一个抽象角色,给出顶级行为的实现。如下图:TemplateMethod模式的实现方法是从上到下,我们首先给出顶级框架DataAccessObject的实现逻辑:

1publicabstractclassDataAccessObject23{4protectedstringconnectionString;56protectedDataSetdataSet;78protectedvirtualvoidConnect()910{11connectionString=1213"Server=.;UserId=sa;Password=;Database=Northwind";1415}1617protectedabstractvoidSelect();1819protectedabstractvoidDisplay();202122protectedvirtualvoidDisconnect()2324{25connectionString="";26}2728//The"TemplateMethod"2930publicvoidRun()3132{33Connect();3435Select();3637Display();3839Disconnect();40}41}

显然在这个顶级的框架DataAccessObject中给出了固定的轮廓,方法Run()便是模版方法,TemplateMethod模式也由此而得名。而对于Select()和Display()这两个抽象方法则留给具体的子类去实现,如下图:

1classCategories:DataAccessObject23{4protectedoverridevoidSelect()5{6stringsql="selectCategoryNamefromCategories";78SqlDataAdapterdataAdapter=newSqlDataAdapter(910sql,connectionString);1112dataSet=newDataSet();1314dataAdapter.Fill(dataSet,"Categories");1516}1718protectedoverridevoidDisplay()1920{2122Console.WriteLine("Categories----");2324DataTabledataTable=dataSet.Tables["Categories"];2526foreach(DataRowrowindataTable.Rows)2728{2930Console.WriteLine(row["CategoryName"].ToString());3132}3334Console.WriteLine();3536}37}

1classProducts:DataAccessObject23{4protectedoverridevoidSelect()56{7stringsql="selecttop10ProductNamefromProducts";89SqlDataAdapterdataAdapter=newSqlDataAdapter(1011sql,connectionString);1213dataSet=newDataSet();1415dataAdapter.Fill(dataSet,"Products");1617}1819protectedoverridevoidDisplay()2021{2223Console.WriteLine("Products----");2425DataTabledataTable=dataSet.Tables["Products"];2627foreach(DataRowrowindataTable.Rows)2829{30Console.WriteLine(row["ProductName"].ToString());3132}3334Console.WriteLine();3536}3738}

再来看看客户端程序的调用,不需要再去调用每一个步骤的方法:

1publicclassApp23{4staticvoidMain()5{67DataAccessObjectdao;8910dao=newCategories();1112dao.Run();131415dao=newProducts();1617dao.Run();1819//Waitforuser2021Console.Read();2223}2425}

在上面的例子中,需要注意的是:

1.对于Connect()和Disconnect()方法实现为了virtual,而Select()和Display()方法则为abstract,这是因为如果这个方法有默认的实现,则实现为virtual,否则为abstract。

3.在一开始我们提到了不管读的是哪张数据表,它们都有共同的操作步骤,即共同点。因此可以说TemplateMethod模式的一个特征就是剥离共同点。TemplateMehtod实现要点:

1.TemplateMethod模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。

2.除了可以灵活应对子步骤的变化外,“不用调用我,让我来调用你(Don'tcallme,letmecallyou)”的反向控制结构是TemplateMethod的典型应用。“Don’tcallme.Letmecallyou”是指一个父类调用一个子类的操作,而不是相反。

3.在具体实现方面,被TemplateMethod调用的虚方法可以具有实现,也可以没有任何实现(抽象方法,纯虚方法),但一般推荐将它们设置为protected方法。

代码实例如下:

new运算符

1.用于创建对象和调用构造函数

例:Class_TestMyClass=newClass_Test();

2.也用于为值类型调用默认的构造函数

例:intmyInt=newint();

myInt初始化为0,它是int类型的默认值。该语句的效果等同于:intmyInt=0;

3.不能重载new运算符。

4.如果new运算符分配内存失败,则它将引发OutOfMemoryException异常。

new修饰符

请看下面的类:

1publicclassMyClass23{45publicintx;67publicvoidInvoke(){}89}10

1publicclassMyDerivedC:MyClass23{45newpublicvoidInvoke(){}67}8

但是,因为字段x不是通过类似名隐藏的,所以不会影响该字段。

通过继承隐藏名称采用下列形式之一:1.引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。2.引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。3.引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。4.在同一成员上同时使用new和override是错误的。

示例

在该例中,基类MyBaseC和派生类MyDerivedC使用相同的字段名x,从而隐藏了继承字段的值。该例说明了new修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。

1usingSystem;23publicclassMyBaseC45{67publicstaticintx=55;89publicstaticinty=22;1011}12131415publicclassMyDerivedC:MyBaseC1617{1819newpublicstaticintx=100;//Namehiding2021publicstaticvoidMain()2223{2425//Displaytheoverlappingvalueofx:2627Console.WriteLine(x);28293031//Accessthehiddenvalueofx:3233Console.WriteLine(MyBaseC.x);34353637//Displaytheunhiddenmembery:3839Console.WriteLine(y);4041}4243}44

输出

100

55

22

如果移除new修饰符,程序将继续编译和运行,但您会收到以下警告:

Thekeywordnewisrequiredon'MyDerivedC.x'becauseithidesinheritedmember'MyBaseC.x'.

如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用new修饰符修改此嵌套类型。

在该例中,嵌套类MyClass隐藏了基类中具有相同名称的类。该例不仅说明了如何使用完全限定名访问隐藏类成员,同时也说明了如何使用new修饰符消除警告消息。

1usingSystem;23publicclassMyBaseC45{67publicclassMyClass89{1011publicintx=200;1213publicinty;1415}1617}18192021publicclassMyDerivedC:MyBaseC2223{2425newpublicclassMyClass//nestedtypehidingthebasetypemembers2627{2829publicintx=100;3031publicinty;3233publicintz;3435}36373839publicstaticvoidMain()4041{4243//Creatingobjectfromtheoverlappingclass:4445MyClassS1=newMyClass();46474849//Creatingobjectfromthehiddenclass:5051MyBaseC.MyClassS2=newMyBaseC.MyClass();52535455Console.WriteLine(S1.x);5657Console.WriteLine(S2.x);5859}6061}62

200

stringstr=""初始化对象分配空间而stringstr=null初始化对象更详细的解释这样定义后,str1是一个空字符串,空字符串是一个特殊的字符串,只不过这个字符串的值为空,在内存中是有准确的指向的。stringstr2=null,这样定义后,只是定义了一个string类的引用,str2并没有指向任何地方,在使用前如果不实例化的话,将报错。

面向对象是当前计算机界关心的重点,它是90年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。

一、传统开发方法存在问题

1.软件重用性差重用性是指同一事物不经修改或稍加修改就可多次重复使用的性质。软件重用性是软件工程追求的目标之一。

2.软件可维护性差软件工程强调软件的可维护性,强调文档资料的重要性,规定最终的软件产品应该由完整、一致的配置成分组成。在软件开发过程中,始终强调软件的可读性、可修改性和可测试性是软件的重要的质量指标。实践证明,用传统方法开发出来的软件,维护时其费用和成本仍然很高,其原因是可修改性差,维护困难,导致可维护性差。

3.开发出的软件不能满足用户需要用传统的结构化开发大型软件系统涉及各种不同领域的知识,在开发需求模糊或需求动态变化的系统时,所开发出的软件系统往往不能真正满足用户的需要。

用结构化方法开发的软件,其稳定性、可修改性和可重用性都比较差,这是因为结构化方法的本质是功能分解,从代表目标系统整体功能的单个处理着手,自顶向下不断把复杂的处理分解为子处理,这样一层一层的分解下去,直到仅剩下若干个容易实现的子处理功能为止,然后用相应的工具来描述各个最低层的处理。因此,结构化方法是围绕实现处理功能的“过程”来构造系统的。然而,用户需求的变化大部分是针对功能的,因此,这种变化对于基于过程的设计来说是灾难性的。用这种方法设计出来的系统结构常常是不稳定的,用户需求的变化往往造成系统结构的较大变化,从而需要花费很大代价才能实现这种变化。

二、面向对象的基本概念

(1)对象。对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。

(2)对象的状态和行为。对象具有状态,一个对象用数据值来描述它的状态。对象还有操作,用于改变对象的状态,对象及其操作就是对象的行为。对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中

(3)类。具有相同或相似性质的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象。类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。类具有操作,它是对象的行为的抽象,用操作名和实现该操作的方法来描述。

(4)类的结构。在客观世界中有若干类,这些类之间有一定的结构关系。通常有两种主要的结构关系,即一般--具体结构关系,整体--部分结构关系。

①一般——具体结构称为分类结构,也可以说是“或”关系,或者是“isa”关系。②整体——部分结构称为组装结构,它们之间的关系是一种“与”关系,或者是“hasa”关系。

(5)消息和方法。对象之间进行通信的结构叫做消息。在对象的操作中,当一个消息发送给某个对象时,消息包含接收对象去执行某种操作的信息。发送一条消息至少要包括说明接受消息的对象名、发送给该对象的消息名(即对象名、方法名)。一般还要对参数加以说明,参数可以是认识该消息的对象所知道的变量名,或者是所有对象都知道的全局变量名。

类中操作的实现过程叫做方法,一个方法有方法名、参数、方法体。消息传递如图10-1所示。

二、面向对象的特征

(1)对象唯一性。每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变,不同的对象不能有相同的标识。

(2)分类性。分类性是指将具有一致的数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。任何类的划分都是主观的,但必须与具体的应用有关。

(3)继承性。继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。

继承性是面向对象程序设计语言不同于其它语言的最重要的特点,是其他语言所没有的。

在类层次中,子类只继承一个父类的数据结构和方法,则称为单重继承。在类层次中,子类继承了多个父类的数据结构和方法,则称为多重继承。在软件开发中,类的继承性使所建立的软件具有开放性、可扩充性,这是信息组织与分类的行之有效的方法,它简化了对象、类的创建工作量,增加了代码的可重性。采用继承性,提供了类的规范的等级结构。通过类的继承关系,使公共的特性能够共享,提高了软件的重用性。

(4)多态性(多形性)多态性使指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。多态性允许每个对象以适合自身的方式去响应共同的消息。多态性增强了软件的灵活性和重用性。

三、面向对象的要素

(1)抽象。抽象是指强调实体的本质、内在的属性。在系统开发中,抽象指的是在决定如何实现对象之前的对象的意义和行为。使用抽象可以尽可能避免过早考虑一些细节。

类实现了对象的数据(即状态)和行为的抽象。(2)封装性(信息隐藏)。封装性是保证软件部件具有优良的模块性的基础。面向对象的类是封装良好的模块,类定义将其说明(用户可见的外部接口)与实现(用户不可见的内部实现)显式地分开,其内部实现按其具体定义的作用域提供保护。对象是封装的最基本单位。封装防止了程序相互依赖性而带来的变动影响。面向对象的封装比传统语言的封装更为清晰、更为有力。

(3)共享性面向对象技术在不同级别上促进了共享同一类中的共享。同一类中的对象有着相同数据结构。这些对象之间是结构、行为特征的共享关系。在同一应用中共享。在同一应用的类层次结构中,存在继承关系的各相似子类中,存在数据结构和行为的继承,使各相似子类共享共同的结构和行为。使用继承来实现代码的共享,这也是面向对象的主要优点之一。在不同应用中共享。面向对象不仅允许在同一应用中共享信息,而且为未来目标的可重用设计准备了条件。通过类库这种机制和结构来实现不同应用中的信息共享。

4.强调对象结构而不是程序结构

四、面向对象的开发方法

目前,面向对象开发方法的研究已日趋成熟,国际上已有不少面向对象产品出现。面向对象开发方法有Coad方法、Booch方法和OMT方法等。

1.Booch方法

Booch最先描述了面向对象的软件开发方法的基础问题,指出面向对象开发是一种根本不同于传统的功能分解的设计方法。面向对象的软件分解更接近人对客观事务的理解,而功能分解只通过问题空间的转换来获得。

2.Coad方法

Coad方法是1989年Coad和Yourdon提出的面向对象开发方法。该方法的主要优点是通过多年来大系统开发的经验与面向对象概念的有机结合,在对象、结构、属性和操作的认定方面,提出了一套系统的原则。该方法完成了从需求角度进一步进行类和类层次结构的认定。尽管Coad方法没有引入类和类层次结构的术语,但事实上已经在分类结构、属性、操作、消息关联等概念中体现了类和类层次结构的特征。

3.OMT方法

OMT方法是1991年由JamesRumbaugh等5人提出来的,其经典著作为“面向对象的建模与设计”。

该方法是一种新兴的面向对象的开发方法,开发工作的基础是对真实世界的对象建模,然后围绕这些对象使用分析模型来进行独立于语言的设计,面向对象的建模和设计促进了对需求的理解,有利于开发得更清晰、更容易维护的软件系统。该方法为大多数应用领域的软件开发提供了一种实际的、高效的保证,努力寻求一种问题求解的实际方法。

4.UML(UnifiedModelingLanguage)语言

软件工程领域在1995年~1997年取得了前所未有的进展,其成果超过软件工程领域过去15年的成就总和,其中最重要的成果之一就是统一建模语言(UML)的出现。UML将是面向对象技术领域内占主导地位的标准建模语言。UML不仅统一了Booch方法、OMT方法、OOSE方法的表示方法,而且对其作了进一步的发展,最终统一为大众接受的标准建模语言。UML是一种定义良好、易于表达、功能强大且普遍适用的建模语言。它融入了软件工程领域的新思想、新方法和新技术。它的作用域不限于支持面向对象的分析与设计,还支持从需求分析开始的软件开发全过程。

五、面向对象的模型

·对象模型

对象模型表示了静态的、结构化的系统数据性质,描述了系统的静态结构,它是从客观世界实体的对象关系角度来描述,表现了对象的相互关系。该模型主要关心系统中对象的结构、属性和操作,它是分析阶段三个模型的核心,是其他两个模型的框架。

1.对象和类

(1)对象。对象建模的目的就是描述对象。

(2)类。通过将对象抽象成类,我们可以使问题抽象化,抽象增强了模型的归纳能力。

(3)属性。属性指的是类中对象所具有的性质(数据值)。

(4)操作和方法。

操作是类中对象所使用的一种功能或变换。类中的各对象可以共享操作,每个操作都有一个目标对象作为其隐含参数。方法是类的操作的实现步骤。

2.关联和链关联是建立类之间关系的一种手段,而链则是建立对象之间关系的一种手段。

(1)关联和链的含义。链表示对象间的物理与概念联结,关联表示类之间的一种关系,链是关联的实例,关联是链的抽象。

(2)角色。角色说明类在关联中的作用,它位于关联的端点。

(3)受限关联。受限关联由两个类及一个限定词组成,限定词是一种特定的属性,用来有效的减少关联的重数,限定词在关联的终端对象集中说明。限定提高了语义的精确性,增强了查询能力,在现实世界中,常常出现限定词。

3.类的层次结构(1)聚集关系。聚集是一种“整体-部分”关系。在这种关系中,有整体类和部分类之分。聚集最重要的性质是传递性,也具有逆对称性。

聚集可以有不同层次,可以把不同分类聚集起来得到一颗简单的聚集树,聚集树是一种简单表示,比画很多线来将部分类联系起来简单得多,对象模型应该容易地反映各级层次,图10-10表示一个关于微机的多极聚集。

(2)一般化关系。一般化关系是在保留对象差异的同时共享对象相似性的一种高度抽象方式。它是“一般---具体”的关系。一般化类称为你类,具体类又能称为子类,各子类继承了交类的性质,而各子类的一些共同性质和操作又归纳到你类中。因此,一般化关系和继承是同时存在的。一般化关系的符号表示是在类关联的连线上加一个小三角形,如图10-11

4.对象模型(1)模板。模板是类、关联、一般化结构的逻辑组成。(2)对象模型。

对象模型是由一个或若干个模板组成。模板将模型分为若干个便于管理的子块,在整个对象模型和类及关联的构造块之间,模板提供了一种集成的中间单元,模板中的类名及关联名是唯一的。

·动态模型

2.状态状态是对象属性值的抽象。对象的属性值按照影响对象显著行为的性质将其归并到一个状态中去。状态指明了对象对输入事件的响应。

3.状态图

状态图是一个标准的计算机概念,他是有限自动机的图形表示,这里把状态图作为建立动态模型的图形工具。状态图反映了状态与事件的关系。当接收一事件时,下一状态就取决于当前状态和所接收的该事件,由该事件引起的状态变化称为转换。状态图是一种图,用结点表示状态,结点用圆圈表示;圆圈内有状态名,用箭头连线表示状态的转换,上面标记事件名,箭头方向表示转换的方向。

·功能模型

功能模型描述了系统的所有计算。功能模型指出发生了什么,动态模型确定什么时候发生,而对象模型确定发生的客体。功能模型表明一个计算如何从输入值得到输出值,它不考虑计算的次序。功能模型由多张数据流图组成。数据流图用来表示从源对象到目标对象的数据值的流向,它不包含控制信息,控制信息在动态模型中表示,同时数据流图也不表示对象中值的组织,值的组织在对象模型中表示。图10-15给出了一个窗口系统的图标显示的数据流图。

数据流图中包含有处理、数据流、动作对象和数据存储对象。

1.处理数据流图中的处理用来改变数据值。最低层处理是纯粹的函数,一张完整的数据流图是一个高层处理。

2.数据流数据流图中的数据流将对象的输出与处理、处理与对象的输入、处理与处理联系起来。在一个计算机中,用数据流来表示一中间数据值,数据流不能改变数据值。

3.动作对象动作对象是一种主动对象,它通过生成或者使用数据值来驱动数据流图。

4.数据存储对象数据流图中的数据存储是被动对象,它用来存储数据。它与动作对象不一样,数据存储本身不产生任何操作,它只响应存储和访问的要求。

六、面向对象的分析

面向对象分析的目的是对客观世界的系统进行建模。本节以上面介绍的模型概念为基础,结合“银行网络系统”的具体实例来构造客观世界问题的准确、严密的分析模型。分析模型有三种用途:用来明确问题需求;为用户和开发人员提供明确需求;为用户和开发人员提供一个协商的基础,作为后继的设计和实现的框架。

(一)面向对象的分析

系统分析的第一步是:陈述需求。分析者必须同用户一块工作来提炼需求,因为这样才表示了用户的真实意图,其中涉及对需求的分析及查找丢失的信息。下面以“银行网络系统”为例,用面向对象方法进行开发。银行网络系统问题陈述:设计支持银行网络的软件,银行网络包括人工出纳站和分行共享的自动出纳机。每个分理处用分理处计算机来保存各自的帐户,处理各自的事务;各自分理处的出纳站与分理处计算机通信,出纳站录入帐户和事务数据;自动出纳机与分行计算机通信,分行计算机与拨款分理处结帐,自动出纳机与用户接口接受现金卡,与分行计算机通信完成事务,发放现金,打印收据;系统需要记录保管和安全措施;系统必须正确处理同一帐户的并发访问;每个分处理为自己的计算机准备软件,银行网络费用根据顾客和现金卡的数目分摊给各分理处。图10-18给出银行网络系统的示意图。

(二)建立对象模型

首先标识和关联,因为它们影响了整体结构和解决问题的方法,其次是增加属性,进一步描述类和关联的基本网络,使用继承合并和组织类,最后操作增加到类中去作为构造动态模型和功能模型的副产品。

1.确定类

按图10-19所示的过程确定类

查找问题陈述中的所有名词,产生如下的暂定类。软件银行网络出纳员自动出纳机分行分处理分处理计算机帐户事务出纳站事务数据分行计算机现金卡用户现金收据系统顾客费用帐户数据访问安全措施记录保管

在银行网络系统中,模糊类是"系统"、"安全措施"、"记录保管"、"银行网络"等。属于属性的有:"帐户数据"、"收据"、"现金"、"事务数据"。属于实现的如:"访问"、"软件"等。这些均应除去。

2.准备数据字典

为所有建模实体准备一个数据字典。准确描述各个类的精确含义,描述当前问题中的类的范围,包括对类的成员、用法方面的假设或限制。

3.确定关联

两个或多个类之间的相互依赖就是关联。一种依赖表示一种关联,可用各种方式来实现关联,但在分析模型中应删除实现的考虑,以便设计时更为灵活。关联常用描述性动词或动词词组来表示,其中有物理位置的表示、传导的动作、通信、所有者关系、条件的满足等。从问题陈述中抽取所有可能的关联表述,把它们记下来,但不要过早去细化这些表述。

下面是银行网络系统中所有可能的关联,大多数是直接抽取问题中的动词词组而得到的。在陈述中,有些动词词组表述的关联是不明显的。最后,还有一些关联与客观世界或人的假设有关,必须同用户一起核实这种关联,因为这种关联在问题陈述中找不到。

银行网络问题陈述中的关联:·银行网络包括出纳站和自动出纳机;·分行共享自动出纳机;·分理处提供分理处计算机;·分理处计算机保存帐户;·分理处计算机处理帐户支付事务;·分理处拥有出纳站;·出纳站与分理处计算机通信;·出纳员为帐户录入事务;·自动出纳机接受现金卡;·自动出纳机与用户接口;·自动出纳机发放现金;·自动出纳机打印收据;·系统处理并发访问;·分理处提供软件;·费用分摊给分理处。隐含的动词词组:·分行由分理处组成;·分理处拥有帐户;·分行拥有分行计算机;·系统提供记录保管;·系统提供安全;·顾客有现金卡。基于问题域知识的关联:·分理处雇佣出纳员;·现金卡访问帐户。

使用下列标准去掉不必要和不正确的关联:

4.确定属性

(1)对象:若实体的独立存在比它的值重要,那么这个实体不是属性而是对象。如在邮政目录中,"城市"是一个属性,然而在人口普查中,"城市"则被看作是对象。在具体应用中,具有自身性质的实体一定是对象。(2)定词:若属性值取决于某种具体上下文,则可考虑把该属性重新表述为一个限定词。(3)名称:名称常常作为限定词而不是对象的属性,当名称不依赖于上下文关系时,名称即为一个对象属性,尤其是它不惟一时。(4)标识符:在考虑对象模糊性时,引入对象标识符表示,在对象模型中不列出这些对象标识符,它是隐含在对象模型中,只列出存在于应用域的属性。(5)内部值:若属性描述了对外不透明的对象的内部状态,则应从对象模型中删除该属性。(6)细化:忽略那些不可能对大多数操作有影响的属性。

5.使用继承来细化类

6.完善对象模型

对象建模不可能一次就能保证模型是完全正确的,软件开发的整个过程就是一个不断完善的过程。模型的不同组成部分多半是在不同的阶段完成的,如果发现模型的缺陷,就必须返回到前期阶段去修改,有些细化工作是在动态模型和功能模型完成之后才开始进行的。(1)几种可能丢失对象的情况及解决办法:·同一类中存在毫无关系的属性和操作,则分解这个类,使各部分相互关联;·一般化体系不清楚,则可能分离扮演两种角色的类·存在无目标类的操作,则找出并加上失去目标的类;·存在名称及目的相同的冗余关联,则通过一般化创建丢失的父类,把关联组织在一起。

(2)查找多余的类。类中缺少属性,操作和关联,则可删除这个类。

(3)查找丢失的关联。丢失了操作的访问路径,则加入新的关联以回答查询。

(4)网络系统的具体情况作如下的修改:①现金卡有多个独立的特性。把它分解为两个对象:卡片权限和现金卡。a.卡片权限:它是银行用来鉴别用户访问权限的卡片,表示一个或多个用户帐户的访问权限;各个卡片权限对象中可能具有好几个现金卡,每张都带有安全码,卡片码,它们附在现金卡上,表现银行的卡片权限。b.现金卡:它是自动出纳机得到表示码的数据卡片,它也是银行代码和现金卡代码的数据载体。②"事务"不能体现对帐户之间的传输描述的一般性,因它只涉及一个帐户,一般来说,在每个帐户中,一个"事务"包括一个或多个"更新",一个"更新"是对帐户的一个动作,它们是取款,存款,查询之一。一个"更新"中所有"更新"应该是一个原子操作。③"分理处"和"分离处理机"之间,"分行"和"分行处理机"之间的区别似乎并不影响分析,计算机的通信处理实际上是实现的概念,将"分理处计算机"并入到"分理处",将"分行计算机"并入到"分行"。

(三)建立动态模型

1.准备脚本动态分析从寻找事件开始,然后确定各对象的可能事件顺序。在分析阶段不考虑算法的执行,算法是实现模型的一部分。

2.确定事件确定所有外部事件。事件包括所有来自或发往用户的信息、外部设备的信号、输入、转换和动作,可以发现正常事件,但不能遗漏条件和异常事件。

3.准备事件跟踪表把脚本表示成一个事件跟踪表,即不同对象之间的事件排序表,对象为表中的列,给每个对象分配一个独立的列。

4.构造状态图对各对象类建立状态图,反映对象接收和发送的事件,每个事件跟踪都对应于状态图中一条路径。

(四)建立功能建模

1.确定输入值、输出值先列出输入、输出值,输入、输出值是系统与外界之间的事件的参数。

2.建立数据流图数据流图说明输出值是怎样从输入值得来的,数据流图通常按层次组织。

(五)确定操作

在建立对象模型时,确定了类、关联、结构和属性,还没有确定操作。只有建立了动态模型和功能模型之后,才可能最后确定类的操作。

七、面向对象的设计

面向对象设计是把分析阶段得到的需求转变成符合成本和质量要求的、抽象的系统实现方案的过程。从面向对象分析到面向对象设计,是一个逐渐扩充模型的过程。

瀑布模型把设计进一步划分成概要设计和详细设计两个阶段,类似地,也可以把面向对象设计再细分为系统设计和对象设计。系统设计确定实现系统的策略和目标系统的高层结构。对象设计确定解空间中的类、关联、接口形式及实现操作的算法。

(一)面向对象设计的准则

1.模块化面向对象开发方法很自然地支持了把系统分解成模块的设计原则:对象就是模块。它是把数据结构和操作这些数据的方法紧密地结合在一起所构成的模块。2.抽象面向对象方法不仅支持过程抽象,而且支持数据抽象。3.信息隐藏在面向对象方法中,信息隐藏通过对象的封装性来实现。4.低耦合在面向对象方法中,对象是最基本的模块,因此,耦合主要指不同对象之间相互关联的紧密程度。低耦合是设计的一个重要标准,因为这有助于使得系统中某一部分的变化对其它部分的影响降到最低程度。5.高内聚(1)操作内聚。(2)类内聚。(3)一般——具体内聚。

(二)面向对象设计的启发规则

1.设计结果应该清晰易懂使设计结果清晰、易懂、易读是提高软件可维护性和可重用性的重要措施。显然,人们不会重用那些他们不理解的设计。要做到:(1)用词一致。(2)使用已有的协议。(3)减少消息模式的数量。(4)避免模糊的定义。

2.一般——具体结构的深度应适当

3.设计简单类应该尽量设计小而简单的类,这样便以开发和管理。为了保持简单,应注意以下几点:(1)避免包含过多的属性。(2)有明确的定义。(3)尽量简化对象之间的合作关系。(4)不要提供太多的操作。

4.使用简单的协议一般来说,消息中参数不要超过3个。

5.使用简单的操作面向对象设计出来的类中的操作通常都很小,一般只有3至5行源程序语句,可以用仅含一个动词和一个宾语的简单句子描述它的功能

系统设计是问题求解及建立解答的高级策略。必须制定解决问题的基本方法,系统的高层结构形式包括子系统的分解、它的固有并发性、子系统分配给硬软件、数据存储管理、资源协调、软件控制实现、人机交互接口。

1.系统设计概述

设计阶段先从高层入手,然后细化。系统设计要决定整个结构及风格,这种结构为后面设计阶段的更详细策略的设计提供了基础。

2.系统结构的一般框架

3.系统分解——建立系统的体系结构可用的软件库以及程序员的编程经验。通过面向对象分析得到的问题域精确模型,为设计体系结构奠定了良好的基础,建立了完整的框架。

4.选择软件控制机制软件系统中存在两种控制流,外部控制流和内部控制流。

5.数据存储管理数据存储管理是系统存储或检索对象的基本设施,它建立在某种数据存储管理系统之上,并且隔离了数据存储管理模式的影响。

6.设计人机交互接口在面向对象分析过程中,已经对用户界面需求作了初步分析,在面向对象设计过程中,则应该对系统的人机交互接口进行详细设计,以确定人机交互的细节,其中包括指定窗口和报表的形式、设计命令层次等项内容。

(四)对象设计1.对象设计概述2.三种模型的结合(1)获得操作。(2)确定操作的目标对象。3.算法设计4.优化设计5.控制的实现6.调整继承7.关联的设计八、面向对象的实现

(一)程序设计语言

1.选择面向对象语言采用面向对象方法开发软件的基本目的和主要优点是通过重用提高软件的生产率。因此,应该优先选用能够最完善、最准确地表达问题域语义的面向对象语言。

在选择编程语言时,应该考虑的其他因素还有:对用户学习面向对象分析、设计和编码技术所能提供的培训操作;在使用这个面向对象语言期间能提供的技术支持;能提供给开发人员使用的开发工具、开发平台,对机器性能和内存的需求,集成已有软件的容易程度。

2.程序设计风格(1)提高重用性。(2)提高可扩充性。(3)提高健壮性。

(二)类的实现

一种方案是先开发一个比较小、比较简单的来,作为开发比较大、比较复杂的类的基础。

(1)“原封不动”重用。(2)进化性重用。一个能够完全符合要求特性的类可能并不存在。(3)“废弃性”开发。不用任何重用来开发一个新类。(4)错误处理。一个类应是自主的,有责任定位和报告错误。

(三)应用系统的实现

应用系统的实现是在所有的类都被实现之后的事。实现一个系统是一个比用过程性方法更简单、更简短的过程。有些实例将在其他类的初始化过程中使用。而其余的则必须用某种主过程显式地加以说明,或者当作系统最高层的类的表示的一部分。

在C++和C中有一个main()函数,可以使用这个过程来说明构成系统主要对象的那些类的实例。

(四)面向对象测试

(1)算法层。(2)类层。测试封装在同一个类中的所有方法和属性之间的相互作用。(3)模板层。测试一组协同工作的类之间的相互作用。(4)系统层。把各个子系统组装成完整的面向对象软件系统,在组装过程中同时进行测试。

九、面向对象和基于对象的区别

很多人没有区分“面向对象”和“基于对象”两个不同的概念。面向对象的三大特点(封装,继承,多态)却一不可。通常“基于对象”是使用对象,但是无法利用现有的对象模板产生新的对象类型,继而产生新的对象,也就是说“基于对象”没有继承的特点。而“多态”表示为父类类型的子类对象实例,没有了继承的概念也就无从谈论“多态”。现在的很多流行技术都是基于对象的,它们使用一些封装好的对象,调用对象的方法,设置对象的属性。但是它们无法让程序员派生新对象类型。他们只能使用现有对象的方法和属性。所以当你判断一个新的技术是否是面向对象的时候,通常可以使用后两个特性来加以判断。“面向对象”和“基于对象”都实现了“封装”的概念,但是面向对象实现了“继承和多态”,而“基于对象”没有实现这些,的确很饶口。

从事面向对象编程的人按照分工来说,可以分为“类库的创建者”和“类库的使用者”。使用类库的人并不都是具备了面向对象思想的人,通常知道如何继承和派生新对象就可以使用类库了,然而我们的思维并没有真正的转过来,使用类库只是在形式上是面向对象,而实质上只是库函数的一种扩展。

面向对象是一种思想,是我们考虑事情的方法,通常表现为我们是将问题的解决按照过程方式来解决呢,还是将问题抽象为一个对象来解决它。很多情况下,我们会不知不觉的按照过程方式来解决它,而不是考虑将要解决问题抽象为对象去解决它。有些人打着面向对象的幌子,干着过程编程的勾当。

耦合与变化:耦合是软件不能抵御变化灾难的根本性原因。不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系。动机(Motivate):在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。意图(Intent):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。-------《设计模式》GOF结构图(Struct):适用性:

1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。

3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。

4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。生活中的例子:Command模式将一个请求封装为一个对象,从而使你可以使用不同的请求对客户进行参数化。用餐时的账单是Command模式的一个例子。服务员接受顾客的点单,把它记在账单上封装。这个点单被排队等待烹饪。注意这里的"账单"是不依赖于菜单的,它可以被不同的顾客使用,因此它可以添入不同的点单项目。代码实现:在众多的设计模式中,Command模式是很简单也很优雅的一种设计模式。Command模式它封装的是命令,把命令发出者的责任和命令执行者的责任分开。我们知道,一个类是一组操作和相应的一些变量的集合,现在有这样一个类Document,如下:

1///

23///文档类45///67publicclassDocument89{10/**////1112///显示操作1314///1516publicvoidDisplay()1718{19Console.WriteLine("Display");20}2122/**////2324///撤销操作2526///2728publicvoidUndo()2930{31Console.WriteLine("Undo");32}3334/**////3536///恢复操作3738///3940publicvoidRedo()4142{43Console.WriteLine("Redo");44}45}

通常客户端实现代码如下:

1classProgram23{4staticvoidMain(string[]args)56{7Documentdoc=newDocument();89doc.Display();1011doc.Undo();1213doc.Redo();14}15}

这样的使用本来是没有任何问题的,但是我们看到在这个特定的应用中,出现了Undo/Redo的操作,这时如果行为的请求者和行为的实现者之间还是呈现这样一种紧耦合,就不太合适了。可以看到,客户程序是依赖于具体Document的命令(方法)的,引入Command模式,需要对Document中的三个命令进行抽象,这是Command模式最有意思的地方,因为在我们看来Display(),Undo(),Redo()这三个方法都应该是Document所具有的,如果单独抽象出来成一个命令对象,那就是把函数层面的功能提到了类的层面,有点功能分解的味道,我觉得这正是Command模式解决这类问题的优雅之处,先对命令对象进行抽象:

1///

23///抽象命令45///67publicabstractclassDocumentCommand89{10Document_document;1112publicDocumentCommand(Documentdoc)1314{15this._document=doc;16}1718/**////1920///执行2122///2324publicabstractvoidExecute();2526}

其他的具体命令类都继承于该抽象类,如下:示意性代码如下:

1///

23///显示命令45///67publicclassDisplayCommand:DocumentCommand89{10publicDisplayCommand(Documentdoc)1112:base(doc)13{1415}1617publicoverridevoidExecute()1819{20_document.Display();21}22}232425/**////2627///撤销命令2829///3031publicclassUndoCommand:DocumentCommand3233{34publicUndoCommand(Documentdoc)3536:base(doc)37{3839}4041publicoverridevoidExecute()4243{44_document.Undo();45}46}474849/**////5051///重做命令5253///5455publicclassRedoCommand:DocumentCommand5657{58publicRedoCommand(Documentdoc)5960:base(doc)61{6263}6465publicoverridevoidExecute()6667{68_document.Redo();69}70}

现在还需要一个Invoker角色的类,这其实相当于一个中间角色,前面我曾经说过,使用这样的一个中间层也是我们经常使用的手法,即把A对B的依赖转换为A对C的依赖。如下:

1///

23///Invoker角色45///67publicclassDocumentInvoker89{10DocumentCommand_discmd;1112DocumentCommand_undcmd;1314DocumentCommand_redcmd;1516publicDocumentInvoker(DocumentCommanddiscmd,DocumentCommandundcmd,DocumentCommandredcmd)17{1819this._discmd=discmd;2021this._undcmd=undcmd;2223this._redcmd=redcmd;2425}2627publicvoidDisplay()2829{30_discmd.Execute();31}3233publicvoidUndo()3435{36_undcmd.Execute();37}3839publicvoidRedo()4041{42_redcmd.Execute();43}44}4546现在再来看客户程序的调用代码:47classProgram4849{50staticvoidMain(string[]args)5152{5354Documentdoc=newDocument();555657DocumentCommanddiscmd=newDisplayCommand(doc);5859DocumentCommandundcmd=newUndoCommand(doc);6061DocumentCommandredcmd=newRedoCommand(doc);626364DocumentInvokerinvoker=newDocumentInvoker(discmd,undcmd,redcmd);6566invoker.Display();6768invoker.Undo();6970invoker.Redo();7172}73}

可以看到在客户程序中,不再依赖于Document的Display(),Undo(),Redo()命令,通过Command对这些命令进行了封装,使用它的一个关键就是抽象的Command类,它定义了一个操作的接口。同时我们也可以看到,本来这三个命令仅仅是三个方法而已,但是通过Command模式却把它们提到了类的层面,这其实是违背了面向对象的原则,但它却优雅的解决了分离命令的请求者和命令的执行者的问题,在使用Command模式的时候,一定要判断好使用它的时机。

Command实现要点:

1.Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。

2.实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。

3.通过使用Compmosite模式,可以将多个命令封装为一个“复合命令”MacroCommand。

4.Command模式与C#中的Delegate有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象能力比较弱。

5.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。Command的优缺点:

命令允许请求的一方和接收请求的一方能够独立演化,从而且有以下的优点:

1.命令模式使新的命令很容易地被加入到系统里。

2.允许接收请求的一方决定是否要否决(Veto)请求。

3.能较容易地设计-个命令队列。

4.可以容易地实现对请求的Undo和Redo。

5.在需要的情况下,可以较容易地将命令记入日志。

6.命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

7.命令类与其他任何别的类一样,可以修改和推广。

8.你可以把命令对象聚合在一起,合成为合成命令。比如宏命令便是合成命令的例子。合成命令是合成模式的应用。

9.由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。

命令模式的缺点如下:

1.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

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

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

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

1///

23///抽象聚集45///67publicinterfaceIList89{10IIteratorGetIterator();11}

抽象的迭代器,它是用来访问聚集的类,封装了一些方法,用来把聚集中的数据按顺序读取出来。通常会有MoveNext()、CurrentItem()、Fisrt()、Next()等几个方法让子类去实现。

1///

23///抽象迭代器45///67publicinterfaceIIterator8{9boolMoveNext();1011ObjectCurrentItem();1213voidFirst();1415voidNext();16}

具体的聚集,它实现了抽象聚集中的唯一的方法,同时在里面保存了一组数据,这里我们加上Length属性和GetElement()方法是为了便于访问聚集中的数据。

1///

23///具体聚集45///67publicclassConcreteList:IList8{9int[]list;1011publicConcreteList()1213{14list=newint[]{1,2,3,4,5};15}1617publicIIteratorGetIterator()1819{20returnnewConcreteIterator(this);21}2223publicintLength2425{26get{returnlist.Length;}27}2829publicintGetElement(intindex)3031{32returnlist[index];33}34}

具体迭代器,实现了抽象迭代器中的四个方法,在它的构造函数中需要接受一个具体聚集类型的参数,在这里面我们可以根据实际的情况去编写不同的迭代方式。

1/**////

23///具体迭代器45///67publicclassConcreteIterator:IIterator89{10privateConcreteListlist;1112privateintindex;1314publicConcreteIterator(ConcreteListlist)1516{17this.list=list;1819index=0;20}2122publicboolMoveNext()2324{25if(index

简单的客户端程序调用:

1/**////

23///客户端程序45///67classProgram89{10staticvoidMain(string[]args)1112{13IIteratoriterator;1415IListlist=newConcreteList();1617iterator=list.GetIterator();1819while(iterator.MoveNext())2021{22inti=(int)iterator.CurrentItem();23Console.WriteLine(i.ToString());2425iterator.Next();26}2728Console.Read();2930}3132}

.NET中Iterator中的应用:在.NET下实现Iterator模式,对于聚集接口和迭代器接口已经存在了,其中IEnumerator扮演的就是迭代器的角色,它的实现如下:

1publicinterfaceIEumerator23{4objectCurrent5{6get;7}89boolMoveNext();1011voidReset();1213}

属性Current返回当前集合中的元素,Reset()方法恢复初始化指向的位置,MoveNext()方法返回值true表示迭代器成功前进到集合中的下一个元素,返回值false表示已经位于集合的末尾。能够提供元素遍历的集合对象,在.Net中都实现了IEnumerator接口。

IEnumerable则扮演的就是抽象聚集的角色,只有一个GetEnumerator()方法,如果集合对象需要具备跌代遍历的功能,就必须实现该接口。

1publicinterfaceIEnumerable23{4IEumeratorGetEnumerator();5}

Iterator实现要点:

1.迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。

2.迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。

3.迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。

动机(Motivate):在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”--------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。意图(Intent):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。-------《设计模式》GOF结构图(Struct):适用性:

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

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

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

观察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。在ATM取款,当取款成功后,以手机、邮件等方式进行通知。

代码实现:

1publicclassBankAccount2{3Emaileremailer;//强信赖关系4MobilephoneNumber;//强信赖关系56privatedouble_money;78publicEmailerEmailer9{10get{returnemailer;}11set{this.emailer=value;}12}13publicMobilePhoneNumber14{15get{returnphoneNumber;}16set{this.phoneNumber=value;}17}18publicdoubleMoney19{20get{return_money;}21set{this._money=value;}22}2324publicvoidWithDraw()25{26emailer.SendEmail(this);27phoneNumber.SendNotification(this);28}2930}

1publicclassEmailer2{3privatestring_emailer;4publicEmailer(stringemailer)5{6this._emailer=emailer;7}8publicvoidSendEmail(BankAccountba)9{10//..11Console.WriteLine("Notified:Emaileris{0},Youwithdraw{1:C}",_emailer,ba.Money);12}13}

1publicclassMobile2{3privatelong_phoneNumber;4publicMobile(longphoneNumber)5{6this._phoneNumber=phoneNumber;7}8publicvoidSendNotification(BankAccountba)9{10Console.WriteLine("Notified:Phonenumberis{0}Youwithdraw{1:C}",_phoneNumber,ba.Money);11}12}

此时简单的客户端调用如下:

1classTest2{3staticvoidMain(string[]args)4{5BankAccountba=newBankAccount();6Emaileremailer=newEmailer("abcdwxc@163.com");7Mobilemobile=newMobile(13901234567);8ba.Emailer=emailer;9ba.PhoneNumber=mobile;10ba.Money=2000;11ba.WithDraw();12}13}

运行结果如下:由此可见程序可以正常运行,但请注意BandAccount和Emailer及Mobile之间形成了一种双向的依赖关系,即BankAccount调用了Emailer及Mobile的方法,而Emailer及Mobile调用了BnadAccount类的属性。如果有其中一个类变化,有可能会引起另一个的变化。如果又需添加一种新的通知方式,就得在BankAccount的WithDraw()方法中增加对该中通知方式的调用。显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。由此我们由左图转换到右图。实例代码如下:

1publicinterfaceIObserverAccount2{3voidUpdate(BankAccountba);4}

1publicclassBankAccount2{3IObserverAccountemailer;//依赖于接口4IObserverAccountphoneNumber;//依赖于接口56privatedouble_money;78publicIObserverAccountEmailer9{10get{returnemailer;}11set{this.emailer=value;}12}13publicIObserverAccountPhoneNumber14{15get{returnphoneNumber;}16set{this.phoneNumber=value;}17}18publicdoubleMoney19{20get{return_money;}21set{this._money=value;}22}2324publicvoidWithDraw()25{26emailer.Update(this);27phoneNumber.Update(this);28}2930}

1publicclassEmailer:IObserverAccount2{3privatestring_emailer;4publicEmailer(stringemailer)5{6this._emailer=emailer;7}8publicvoidUpdate(BankAccountba)9{10//..11Console.WriteLine("Notified:Emaileris{0},Youwithdraw{1:C}",_emailer,ba.Money);12}13}

1publicclassMobile:IObserverAccount2{3privatelong_phoneNumber;4publicMobile(longphoneNumber)5{6this._phoneNumber=phoneNumber;7}8publicvoidUpdate(BankAccountba)9{10Console.WriteLine("Notified:Phonenumberis{0}Youwithdraw{1:C}",_phoneNumber,ba.Money);11}12}

客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。

1publicclassBankAccount2{3privateListObservers=newList();456privatedouble_money;78publicdoubleMoney9{10get{return_money;}11set{this._money=value;}12}1314publicvoidWithDraw()15{16foreach(IObserverAccountobinObservers)17{18ob.Update(this);1920}21}22publicvoidAddObserver(IObserverAccountobserver)23{24Observers.Add(observer);25}26publicvoidRemoverObserver(IObserverAccountobserver)27{28Observers.Remove(observer);29}3031}

此时客户端代码如下:

1classTest2{3staticvoidMain(string[]args)4{5BankAccountba=newBankAccount();6IObserverAccountemailer=newEmailer("abcdwxc@163.com");7IObserverAccountmobile=newMobile(13901234567);89ba.Money=2000;10ba.AddObserver(emailer);11ba.AddObserver(mobile);1213ba.WithDraw();14}15}

走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:

1publicabstractclassSubject2{3privateListObservers=newList();45privatedouble_money;6publicSubject(doublemoney)7{8this._money=money;9}1011publicdoubleMoney12{13get{return_money;}14}1516publicvoidWithDraw()17{18foreach(IObserverAccountobinObservers)19{20ob.Update(this);2122}23}24publicvoidAddObserver(IObserverAccountobserver)25{26Observers.Add(observer);27}28publicvoidRemoverObserver(IObserverAccountobserver)29{30Observers.Remove(observer);31}3233}

1publicinterfaceIObserverAccount2{3voidUpdate(Subjectsubject);4}

1publicclassBankAccount:Subject2{3publicBankAccount(doublemoney)4:base(money)5{}67}

1publicclassEmailer:IObserverAccount2{3privatestring_emalier;4publicEmailer(stringemailer)5{6this._emalier=emailer;7}8publicvoidUpdate(Subjectsubject)9{10Console.WriteLine("Notified:Emaileris{0},Youwithdraw{1:C}",_emalier,subject.Money);11}12}

1publicclassMobile:IObserverAccount2{3privatelong_phoneNumber;4publicMobile(longphoneNumber)5{6this._phoneNumber=phoneNumber;7}8publicvoidUpdate(Subjectsubject)9{10Console.WriteLine("Notified:Phonenumberis{0}Youwithdraw{1:C}",_phoneNumber,subject.Money);11}12}

此时客户端实现如下:

1classTest2{3staticvoidMain(string[]args)4{5Subjectsubject=newBankAccount(2000);6subject.AddObserver(newEmailer("abcdwxc@163.com"));7subject.AddObserver(newMobile(13901234567));89subject.WithDraw();10}11}

推模式与拉模式对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQLServer做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。拉模式实现如下:

1publicabstractclassSubject2{3privateListObservers=newList();456privatedouble_money;78publicdoubleMoney9{10get{return_money;}11}12publicSubject(doublemoney)13{14this._money=money;15}16publicvoidWithDraw()17{18foreach(IObserverAccountobinObservers)19{20ob.Update();2122}23}24publicvoidAddObserver(IObserverAccountobserver)25{26Observers.Add(observer);27}28publicvoidRemoverObserver(IObserverAccountobserver)29{30Observers.Remove(observer);31}3233}

1publicinterfaceIObserverAccount2{3voidUpdate();4}

1publicclassEmailer:IObserverAccount2{3privatestring_emalier;4privateSubject_subject;5publicEmailer(stringemailer,Subjectsubject)6{7this._emalier=emailer;8this._subject=subject;9}10publicvoidUpdate()11{12//..13Console.WriteLine("Notified:Emaileris{0},Youwithdraw{1:C}",_emalier,_subject.Money);14}15}

1publicclassMobile:IObserverAccount2{3privatelong_phoneNumber;4privateSubject_subject;5publicMobile(longphoneNumber,Subjectsubject)6{7this._phoneNumber=phoneNumber;8this._subject=subject;9}10publicvoidUpdate()11{12Console.WriteLine("Notified:Phonenumberis{0}Youwithdraw{1:C}",_phoneNumber,_subject.Money);13}14}

此时客户端调用如下:

1classTest2{3staticvoidMain(string[]args)4{5Subjectsubject=newBankAccount(2000);6subject.AddObserver(newEmailer("abcdwxc@163.com",subject));7subject.AddObserver(newMobile(13901234567,subject));89subject.WithDraw();10}11}

.NET中Observer实现:用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。

1publicclassSubject2{3publiceventNotifyEventHandlerNotifyEvent;45privatedouble_money;6publicSubject(doublemoney)7{8this._money=money;9}1011publicdoubleMoney12{13get{return_money;}14}1516publicvoidWithDraw()17{18OnNotifyChange();19}20publicvoidOnNotifyChange()21{22if(NotifyEvent!=null)23{24NotifyEvent(this);25}2627}2829}

1publicclassEmailer2{3privatestring_emalier;4publicEmailer(stringemailer)5{6this._emalier=emailer;7}8publicvoidUpdate(objectobj)9{10if(objisSubject)11{12Subjectsubject=(Subject)obj;1314Console.WriteLine("Notified:Emaileris{0},Youwithdraw{1:C}",_emalier,subject.Money);15}16}17}

publicdelegatevoidNotifyEventHandler(objectsender);

客户端调用如下:

1classTest2{3staticvoidMain(string[]args)4{5Subjectsubject=newSubject(2000);6Emaileremailer=newEmailer("abcdwxc@163.com");7subject.NotifyEvent+=newNotifyEventHandler(emailer.Update);8910subject.WithDraw();11}12}

Observer实现要点:

1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。

2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。

3.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。

1classProgram2{3staticvoidMain(string[]args)4{5stringroman="五千四百三十二";//54326Contextcontext=newContext(roman);78//Buildthe'parsetree'9ArrayListtree=newArrayList();10tree.Add(newOneExpression());11tree.Add(newTenExpression());12tree.Add(newHundredExpression());13tree.Add(newThousandExpression());1415//Interpret16foreach(Expressionexpintree)17{18exp.Interpret(context);19}20Console.WriteLine("{0}={1}",roman,context.Data);21//Waitforuser22Console.Read();23}24}

创建一个抽象类Expression,来描述共同的操作。

1publicabstractclassExpression2{3protectedDictionarytable=newDictionary(9);4publicExpression()5{6table.Add("一",1);7table.Add("二",2);8table.Add("三",3);9table.Add("四",4);10table.Add("五",5);11table.Add("六",6);12table.Add("七",7);13table.Add("八",8);14table.Add("九",9);15}16publicvirtualvoidInterpret(Contextcontext)17{18if(context.Statement.Length==0)19{20return;21}22foreach(stringkeyintable.Keys)23{24intvalue=table[key];25if(context.Statement.EndsWith(key+GetPostifix()))26{27context.Data+=value*Multiplier();28context.Statement=context.Statement.Substring(0,context.Statement.Length-this.GetLength());29}3031if(context.Statement.EndsWith("零"))32{33context.Statement=context.Statement.Substring(0,context.Statement.Length-1);34}35if(context.Statement.Length==0)36{37return;38}39}40}4142publicabstractstringGetPostifix();43publicabstractintMultiplier();44publicvirtualintGetLength()45{46returnthis.GetPostifix().Length+1;47}48}

然后创建一个公共类Context,定义一些全局信息。

1publicclassContext2{3privatestringstatement;4privateintdata;56//Constructor7publicContext(stringstatement)8{9this.statement=statement;10}11//Properties12publicstringStatement13{14get{returnstatement;}15set{statement=value;}16}17publicintData18{19get{returndata;}20set{data=value;}21}22}

1publicclassOneExpression:Expression2{3publicoverridestringGetPostifix()4{5return"";6}7publicoverrideintMultiplier(){return1;}8publicoverrideintGetLength()9{10return1;11}12}13publicclassTenExpression:Expression14{15publicoverridestringGetPostifix()16{17return"十";18}19publicoverrideintMultiplier(){return10;}20publicoverrideintGetLength()21{22return2;23}24}25publicclassHundredExpression:Expression26{27publicoverridestringGetPostifix()28{29return"百";30}31publicoverrideintMultiplier(){return100;}32publicoverrideintGetLength()33{34return2;35}36}37publicclassThousandExpression:Expression38{39publicoverridestringGetPostifix()40{41return"千";42}43publicoverrideintMultiplier(){return1000;}44publicoverrideintGetLength()45{46return2;47}48}

Interpreter实现要点:Interpreter模式的应用场合是interpreter模式应用中的难点,只有满足"业务规则频繁变化,且类似的模式不断重复出现,并且容易抽象为语法规则的问题"才适合使用Interpreter模式。使用Interpreter模式来表示文法规则,从而可以使用面向对象技巧来方便地“扩展”文法。Interpreter模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具。

1//Mediator2abstractclassAbstractChatroom3{4publicabstractvoidRegister(Participantparticipant);5publicabstractvoidSend(stringfrom,stringto,stringmessage);6}

1//ConcreteMediator2classChatroom:AbstractChatroom3{4privateHashtableparticipants=newHashtable();5publicoverridevoidRegister(Participantparticipant)6{7if(participants[participant.Name]==null)8{9participants[participant.Name]=participant;10}11participant.Chatroom=this;12}13publicoverridevoidSend(stringfrom,stringto,stringmessage)14{15Participantpto=(Participant)participants[to];16if(pto!=null)17{18pto.Receive(from,message);19}20}21}

1//AbstractColleague2classParticipant3{4privateChatroomchatroom;5privatestringname;67//Constructor8publicParticipant(stringname)9{10this.name=name;11}12//Properties13publicstringName14{15get{returnname;}16}17publicChatroomChatroom18{19set{chatroom=value;}20get{returnchatroom;}2122}23publicvoidSend(stringto,stringmessage)24{25chatroom.Send(name,to,message);26}27publicvirtualvoidReceive(stringfrom,stringmessage)28{29Console.WriteLine("{0}to{1}:'{2}'",from,name,message);30}31}

1//ConcreteColleaguel2classBeatle:Participant3{4//Constructor5publicBeatle(stringname)6:base(name)7{}8publicoverridevoidReceive(stringfrom,stringmessage)9{10Console.Write("ToaBeatle:");11base.Receive(from,message);12}13}

1//ConcreteColleague22classNonBeatle:Participant3{4//Constructor5publicNonBeatle(stringname)6:base(name)7{}8publicoverridevoidReceive(stringfrom,stringmessage)9{10Console.Write("Toanon-Beatle:");11base.Receive(from,message);12}13}

1staticvoidMain(string[]args)2{3//createchatroom4Chatroomchatroom=newChatroom();5//Createparticipantsandregisterthem6ParticipantGeorge=newBeatle("George");7ParticipantPaul=newBeatle("Paul");8ParticipantRingo=newBeatle("Ringo");9ParticipantJohn=newBeatle("John");10ParticipantYoko=newBeatle("Yoko");11chatroom.Register(George);12chatroom.Register(Paul);13chatroom.Register(Ringo);14chatroom.Register(John);15chatroom.Register(Yoko);1617//chattingparticipants18Yoko.Send("John","HiJohn");19Paul.Send("Ringo","Allyouneedislove");20Ringo.Send("George","MysweetLord");21Paul.Send("John","Can'tbuymelove");22John.Send("Yoko","Mysweetlove");23}

动机(Motivate):在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少地带来请求发送者与接受者的紧耦合。如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。意图(Intent):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。结构图(Struct):适用性:1.有多个对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。2.你想在不明确接收者的情况下,向多个对象中的一个提交一个请求。3.可处理一个请求的对象集合应被动态指定。生活中的例子:代码实现:

1//Handler2abstractclassApprover3{4protectedApproversuccessor;5publicvoidSetSuccessor(Approversuccessor)6{7this.successor=successor;8}9publicabstractvoidProcessRequest(Purchasepurchase);1011}121314

1//ConcreteHandler2classDirector:Approver3{4publicoverridevoidProcessRequest(Purchasepurchase)5{6if(purchase.Amount<10000.0)7{8Console.WriteLine("{0}approvedrequest#{1}",this.GetType().Name,purchase.Number);910}11elseif(successor!=null)12{13successor.ProcessRequest(purchase);14}15}16}

1234classVicePresident:Approver5{6publicoverridevoidProcessRequest(Purchasepurchase)7{8if(purchase.Amount<25000.0)9{10Console.WriteLine("{0}approvedrequest#{1}",this.GetType().Name,purchase.Number);1112}13elseif(successor!=null)14{15successor.ProcessRequest(purchase);16}17}18}

12classPresident:Approver3{4publicoverridevoidProcessRequest(Purchasepurchase)5{6if(purchase.Amount<100000.0)7{8Console.WriteLine("{0}approvedrequest#{1}",this.GetType().Name,purchase.Number);910}11else12{13Console.WriteLine("Request!{0}requiresanexecutivemeeting!",purchase.Number);14}15}16}

123//Requestdetails4classPurchase5{6privateintnumber;7privatedoubleamount;8privatestringpurpose;910//Constructor11publicPurchase(intnumber,doubleamount,stringpurpose)12{13this.number=number;14this.amount=amount;15this.purpose=purpose;16}17//Properties18publicdoubleAmount19{20get{returnamount;}21set{amount=value;}22}23publicstringPurpose24{25get{returnpurpose;}26set{purpose=value;}27}28publicintNumber29{30get{returnnumber;}31set{number=value;}32}33}

12classProgram3{4staticvoidMain(string[]args)5{6//SetupChainofResponsibility7DirectorLarry=newDirector();8VicePresidentSam=newVicePresident();9PresidentTammy=newPresident();10Larry.SetSuccessor(Sam);11Sam.SetSuccessor(Tammy);1213//Generateandprocesspurchaserequests14Purchasep=newPurchase(1034,350.00,"Supplies");15Larry.ProcessRequest(p);1617p=newPurchase(2035,32590.10,"ProjectX");18Larry.ProcessRequest(p);1920p=newPurchase(2036,122100.00,"ProjectY");21Larry.ProcessRequest(p);2223//Waitforuser24Console.Read();25}26}

运行结果如下:ChainofResponsibility实现要点:1.ChainofResponsibility模式的应用场合在于“一个请求可能有多个接受者,但是最后真正的接受者只胡一个”,只有这时候请求发送者与接受者的耦合才胡可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦,从而更好地应对变化。2.应用了ChainofResponsibility模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理职责。3.如果请求传递到职责链的未尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。

对象状态的回溯:对象状态的变化无端,如何回溯/恢复对象在某个点的状态?动机:在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后可以将该对象恢复到原先保存的状态。适用性:1.必须保存一个对象某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。2.如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。代码实现:

1classMemento2{3privatestringname;4privatestringphone;5privatedoublebudget;67//Constructor8publicMemento(stringname,stringphone,doublebudget)9{10this.name=name;11this.phone=phone;12this.budget=budget;13}14//Properties15publicstringName16{17get{returnname;}18set{name=value;}19}20publicstringPhone21{22get{returnphone;}23set{phone=value;}24}25publicdoubleBudget26{27get{returnbudget;}28set{budget=value;}29}30}

1classProspectMemory2{3privateMementomemento;45//Property6publicMementoMemento7{8set{memento=value;}9get{returnmemento;}10}11}

1//Originator2classSalesProspect3{4privatestringname;5privatestringphone;6privatedoublebudget;78//Properties9publicstringName10{11get{returnname;}12set{name=value;Console.WriteLine("Name:"+name);}13}14publicstringPhone15{16get{returnphone;}17set{phone=value;Console.WriteLine("Phone:"+phone);}18}19publicdoubleBudget20{21get{returnBudget;}22set{budget=value;Console.WriteLine("Budget:"+budget);}23}24publicMementoSaveMemento()25{26Console.WriteLine("\nSavingstate--\n");27returnnewMemento(name,phone,budget);28}29publicvoidRestoreMemento(Mementomemento)30{31Console.WriteLine("\nRestoringstate--\n");32this.Name=memento.Name;33this.Phone=memento.Phone;34this.Budget=memento.Budget;35}36}

1classProgram2{3staticvoidMain(string[]args)4{5SalesProspects=newSalesProspect();6s.Name="xiaoming";7s.Phone="(010)65236523";8s.Budget=28000.0;910//Storeinternalstate11ProspectMemorym=newProspectMemory();12m.Memento=s.SaveMemento();1314//Continuechangingoriginator15s.Name="deke";16s.Phone="(029)85423657";17s.Budget=80000.0;1819//Restoresavedstate20s.RestoreMemento(m.Memento);2122//Waitforuser23Console.Read();24}25}

Memento需要注意的几个要点:1.备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”。2.在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器的宽接口;一个为其他对象使用的窄接口。3.在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。

1enumSortType2{3QuickSort,4ShellSort,5MergeSort,6}

1classSort2{3publicvoidSortList(SortTypes)4{5if(s==SortType.QuickSort)6{7ProcessA();8}9elseif(s==SortType.ShellSort)10{11ProcessB();12}13elseif(s==SortType.MergeSort)14{15ProcessC();16}17Console.WriteLine();18}1920protectedvoidProcessA()21{22Console.WriteLine("QuickSortList");23}24protectedvoidProcessB()25{26Console.WriteLine("ShellSortList");27}28protectedvoidProcessC()29{30Console.WriteLine("MergeSortList");31}32}

客户端调用:

1classTest2{3publicstaticvoidMain()4{5Sortsort=newSort();6sort.SortList(SortType.QuickSort);7sort.SortList(SortType.ShellSort);8sort.SortList(SortType.MergeSort);9}10}

由此可见,由于客户端新增调用方式的选择,就会修改SortType及Sort里的判断语句。在类Sort中会增加if语句的判断,用敏捷软件开发的语言说,你应该闻到了代码的臭味道了,也就是设计模式中说的存在了变化的地方。重构以上代码,增加一层中间层来处理变化。类结构如下:

1//Stategy表达抽象算法2abstractclassSortStrategy3{4publicabstractvoidSort(ArrayListlist);5}

1//ConcreteStrategy2classShellSort:SortStrategy3{4publicoverridevoidSort(System.Collections.ArrayListlist)5{6list.Sort();//no-implement7Console.WriteLine("ShellSortedList");89}10}

1//ConcreteStrategy2classMergeSort:SortStrategy3{4publicoverridevoidSort(System.Collections.ArrayListlist)5{6list.Sort();//no-implement7Console.WriteLine("MergeSortList");8}9}

1//ConcreteStrategy2classQuickSort:SortStrategy3{4publicoverridevoidSort(System.Collections.ArrayListlist)5{6list.Sort();//DefaultisQuicksort7Console.WriteLine("QuickSortedList");8}9}

1//Context2classSortdList3{4privateArrayListlist=newArrayList();5privateSortStrategysortstrategy;//对象组合6publicvoidSetSortStrategy(SortStrategysortstrategy)7{8this.sortstrategy=sortstrategy;9}10publicvoidAdd(stringname)11{12list.Add(name);13}14publicvoidSort()15{16sortstrategy.Sort(list);17//Displayresults18foreach(stringnameinlist)19{20Console.WriteLine(""+name);21}22Console.WriteLine();23}24}

客户端代码如下:

1classProgram2{3staticvoidMain(string[]args)4{5//Twocontextsfollowingdifferentstrategies6SortdListstudentRecords=newSortdList();78studentRecords.Add("Satu");9studentRecords.Add("Jim");10studentRecords.Add("Palo");11studentRecords.Add("Terry");12studentRecords.Add("Annaro");1314studentRecords.SetSortStrategy(newQuickSort());15studentRecords.Sort();1617studentRecords.SetSortStrategy(newShellSort());18studentRecords.Sort();1920studentRecords.SetSortStrategy(newMergeSort());21studentRecords.Sort();2223Console.Read();24}25}

由此可见,更好地满足开放封闭原则。Strategy模式的几个要点:1.Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。所谓封装算法,支持算法的变化。2.Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。3.与State类似,如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。

运行结果:Visoitr模式的几个要点:1.Visitor模式通过所谓双重分发(doubledispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作。2.所谓双重分发却Visotor模式中间包括了两个多态分发(注意其中的多态机制);第一个为accept方法的多态辨析;第二个为visitor方法的多态辨析。3.Visotor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visiotr模式适用"Element"类层次结构稳定,而其中的操作却经常面临频繁改动".

对象状态影响对象行为:对象拥有不同的状态,往往会行使不同的行为...动机:在软件构建过程中,某些对象的状态如果改变以及其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。如何在运行时根据对象的状态来透明更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?意图:允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。------《设计模式》GOF结构图:适用性:1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。2.一个操作中含有庞大的多分支的等条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个分支放入一个独立的类中。这使得你可根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。代码实现:

THE END
1.房屋平面设计图制作软件都有哪些软件,挑战版解释落实房屋平面设计图制作软件包括AutoCAD、SketchUp、Revit、Sweet Home 3D等。本文将深入解析这些软件的挑战版和复刻版功能,以帮助读者了解它们在不同设计需求中的应用。 探索房屋平面设计图制作软件:挑战版解释落实,复刻版657687的多面解析 在建筑设计与室内装饰行业中,房屋平面设计图是至关重要的基础文件,它不仅能够帮助设计http://www.juliangyuanshu.com/post/38312.html
2.房屋设计图制作软件APP推荐房屋设计图制作软件下载豌豆荚房屋设计图制作软件榜单为您提供最新房屋设计图制作软件大全,这里不仅有房屋设计图制作软件安卓版本APP、历史版本应用下载资源,还有类似房屋设计图制作软件的应用推荐,欢迎大家前来豌豆荚下载。https://www.wandoujia.com/bangdan/555510/
3.房屋设计图制作app软件大全房屋设计图制作app推荐下载PP助手为您提供房屋设计图制作软件app大全推荐,在这里我们为您提供房屋设计图制作软件app软件下载资源,房屋设计图制作软件app安卓版本、官方版本&老版本下载地址合集,还可查阅相关房屋设计图制作软件app攻略大全,欢迎到PP助手下载。https://wap.pp.cn/topic/745174/
4.房屋3d设计图用什么软件房屋3d设计图用什么软件2023-06-27 精选回答 3d设计图可以用SketchUp制作,方便快速建模,呈现设计创意和构思,主要用于方案推敲。后续方案确定后就可以做效果图了,效果图的制作是需要多个软件配合使用的,先用AutoCAD建立平面图,导入3ds Max创建室内模型,赋予材质,设置调整灯光,然后Vray渲染输出,Photoshop后期修图,最终呈现https://wap.hxsd.com/wenda/62391/
5.房屋设计软件房屋设计图制作软件下载房屋设计软件大全为用户提供了大量电脑房屋设计软件和好用的房屋设计app。如酷家乐、 91家居装修设计、CAD、乐易集成吊顶设计、CAD迷你家装、家炫DIY房屋设计、houzz室内设计、建e网、土巴兔装修设计软件等数十款好用的房屋设计软件。此类应用可以免费制作房屋设计图,具有设天花吊顶、橱衣柜定制、拼砖工具等强大定制功能,https://www.32r.com/zt/330.html
6.房屋平面图设计app手机设计软件室内装修房屋平面图设计app 多特软件园为您整理了多款适合房屋平面图设计app,在多特您能下载多款正规的、无毒软件,在这里安卓版苹果版等应用应有尽有,找适合房屋平面图设计app就来多特软件站吧。 展开>安卓应用 PC软件 房屋设计模拟 版本:v1.0.5类别:模拟经营 大小:78.3MB语言:简体中文 立即下载 房屋装修设计大全 https://m.duote.com/zt/fangwupingmiantushej/
7.自己画房子用什么软件?自己画房子平面图app自己画房子用什么软件呢?大家日常需要绘画房屋平面图需要频繁的在电脑上进行绘制,这样的方式非常拖延时间,今天小编就给大家专门整理一期自己画房子平面图app,能提高大家的绘制效率,并且还能简单标注,轻松制作完整且准确的房屋平面图,并且不同的绘画工具还可以满足用户的绘画需求,有需要的小伙伴可以来小编这里一起下载体验http://www.downcc.com/k/zjhfzpmtapp/
8.实用家居平面图制作软件推荐门的房屋设计app排行榜我们在进行室内装修设计时,巧妙地利用其中所展示的各种装修效果图,以激发我们的创新思维;同时,我们还能直观地看到实际案例中的精美图片和免费设计功能。而对于那些缺乏相关装修设计经验的新手朋友们,我们的产品也将是您的得力助手,无须过度担忧,因为我们具备适宜的模https://app.ali213.net/top/snpmtysmrj.html
9.房屋平面图平面图制作软件,教程和例子这个房屋平面图示例供您免费下载和使用。 3个卧室房屋平面图示例 这个3个卧室的房屋平面图供您免费下载和使用。 2个卧室房屋平面图模板 这个2个卧室的房屋平面图矢量示例供您免费下载和使用。 制作衣柜设计图 厨房设计软件 厨房立面图解决方案 制作图表https://www.edrawsoft.com/cn/home-plan-solutions.php
10.什么软件可以设计房子3d效果图,房子3d效果图app排行可以在线制作房屋3d效果图的app有哪些?今天小编将针对什么软件可以设计房子3d效果图2022的内容,详细的为各位用户们介绍十款同类型的应用。在这些房屋3d效果图软件中,用户们可以更直观的了解到不同的装修风格。 可以在线制作房屋3d效果图的app有哪些?今天小编将针对什么软件可以设计房子3d效果图2022的内容,详细的为各位https://www.18183.com/soft/4360540.html
11.简单的3D家装设计软件,从房屋平面图,室内设计到软装美化和墙纸通过逼真的3D图像,任何人都可以轻松地设计平面图,花园和室内设计,更不用说重新装修房屋,重新装修房间,重新粉刷墙壁和屋顶,设计花园了。 这真的是一款给力的房屋家装设计软件。https://www.nchsoftware.com/design/cn/index.html
12.2020级五年制人才培养方案培养目标:本专业针对电子信息类企业,培养德智体美全面发展,具有良好的职业道德和坚实的职业生涯发展基础;掌握电子信息技术专业必备知识和职业岗位技能,具有电子信息技术基础知识与能力,具有电子产品的装配、调试、设计及质量管理的基本能力,具有一般电子设备的安装、调试、维护与应用能力,具有对通信设备安装、调试、维护能力;http://www.jxxdjsxy.com/jiaowuzaixian/zhuanyeshezhi/2022-06-19/8135.html
13.如何自学平面设计平面图制作软件有哪些平面设计教程→MAIGOO知识如何自学平面设计 平面图制作软件有哪些 平面设计教程 摘要:平面设计是一门技术活,并且涉及面也是非常广泛,室内装潢、商标和品牌、海报、网站图形元素等。有兴趣的小伙伴可以试着自学平面设计,下面为大家介绍自学平面设计的一些思路,还有一些比较热门的常用软件。https://m.maigoo.com/goomai/156858.html
14.公司装修设计公司图用什么软件?装修设计公司预算陷阱?[摘要]做出图纸的话才可以往里面摆放家具什么的才可以做效果,可能有的人不知道公司装修设计公司图用什么软件做比较好,因此,还没有选择出好的软件,同样的,他们也不知道装修设计公司的预算陷阱都有哪些。 如果你们想要自己学习室内装修方面的事情的话,就必须要把一个图纸做出来,做出图纸的话才可以往里面摆放家具什么的https://zhishi.fang.com/jiaju/qg_570918.html
15.房产图片怎么制作?房产图片制作教程步骤自己怎么制作房产图片?对于不会做设计的同学来说,制作图片可谓是非常困难,不仅要学会使用设计软件,还要懂美感,了解设计知识等等,而现在其实不用掌握专业的设计软件也可以轻松制作房产图片。 只需选择创客贴在线设计平台,寻找好用的模板,在原来的模板上进行文案信息、设计元素、图片素材等内容进行修改即可。 https://m.chuangkit.com/searcheo/course/3214.html
16.户型图设计app哪个好?户型图设计软件app免费户型图设计软件户型图设计软件大全有小编为大家准备了可以进行户型图设计的辅助类软件,超多的工具可以帮助设计人员去更好根据房屋来进行设计自己的家里面的装修,满足设计师的工作需求。超级多的设计方案,轻松去进行标记,还有很多模板可以免费进行参考,有需要的用户,快来当易网下载吧http://www.downyi.com/key/huxingtushejiruanjian/
17.简单易上手的户型制作软件,菜鸟也能轻松进行设计户型图制作软件 户型图,是住房平面空间布局情况的一种表达,能够直观展现住房的主要功能结构、位置与尺寸。户型图常见于地产商的户型推荐,是购房者了解房间结构的首要渠道。户型图需要有清晰的表达,方便读图者能够充分了解房屋结构信息,也是房屋后续装修改造的重要参考依据。因此,户型图的绘画是一件专业的事情。https://www.edrawsoft.cn/huxingtu/
18.RoomArranger破解版房屋布局设计工具RoomArrangerRoom Arranger破解版是房屋布局设计工具,可以实现3D模拟,让你轻易移动移动笨重的家具而不用后悔是否因放错位置而重搬!软件省去了在纸上画草图的麻烦,最大程度地节省你宝贵的时间。软件中允许你使用工具来测量你的物品的长度,不仅如此,Room Arranger拥有物品库,中间包括了很多你画草图需要的东西,例如沙发,柜子,桌子等http://www.sd173.com/soft/1332.html
19.亿图建筑平面图设计软件V8下载亿图建筑平面图设计软件是一款专业用于商业建筑设计、空间计划、建筑布局、施工文件、结构图和设施规划的软件。不仅适用于室内家居设计,而且可用于建筑平面设计,轻松绘制房屋建筑平面图,室内布置图,更能设计消防疏散图,管道管理图,花园设计图,电路电信布置图等等。满足您对于办公室,餐厅,酒店,礼堂,别墅,住宅,公寓等的多https://www.xiazaiba.com/html/82559.html
20.邦宝益智:首次公开发行股票招股说明书股票频道目的实施也迫切需要更多包括创意设计、制作以及动漫衍生品开发、模具研发和 制造、营销宣传推广等方面的专业人才,公司虽然具有良好的工作环境、人才引 进及激励机制,在人才引进和使用上有较大的灵活性,但公司在引进上述高素质 人才方面可能存在不确定性。若公司未能继续完善和有效执行人员管理制度以适 应预期的扩张规模,https://stock.stockstar.com/notice/JC2015113000000943_26.shtml
21.在线PS软件在线PS图片(照片)处理工具PS在线图片编辑器是一个专业精简的在线ps图片照片制作处理软件工具,绿色免安装,免下载,直接在浏览器打开就可用它修正,调整和美化图像。https://www.uupoop.com/
22.11个平面图设计软件重磅推荐,设计师请收好!今天我们要给大家带来的是 11 个平面图设计软件重磅推荐,设计师朋友们可一定要收好了!这 11 个平面图设软件分别是:即时设计、Roomle、Foyr Neo、RoomSketcher、CorelDRAW、SmartDraw、AutoCAD Architecture、Sweet Home3D、HomeByMe、SketchUp、Planner 5D,他们的实力都不容小觑,相信他们一定能在画设计图、制作平面设https://js.design/special/article/a-plane-figure.html