C#常见的设计模式
一、概要:
模式分为三种,设计模式、体系结构模式与惯用法。其中惯用法是一种语言紧密相关的模式,例如,定界加锁模式其实是一种惯用法。
在C#项目开发过程中,很多情况下您已经使用了某些模式,但或许您并不知道自己所使用的这种解决方案是一种已经被总结归纳的模式。
工厂、策略、桥接、模板方法、代理等等23种Gof经典模式是属于设计模式,设计模式的粒度相对较小,基本上用于提高模块内部的可扩展性和可维护性需求
三层、MVC、IoC/DI等属于体系结构模式,粒度比设计模式大,它是从项目的整体角度去看项目的架构。设计需要合理性,架构同样追求合理性,这就是架构模式的用途。
C#的经典样例petshop中,采用分层思想构架了整个网站,这里的分层就是体系结构模式;而在数据库访问层,则采用工厂模式来泛化数据库操作,使得业务层不需要关心您现在的数据库是SQL server的,还是oracle的。这就是设计模式的使用。
模式应用不是一两句话能够说清楚的,也不是一天两天能够体会的,需要楼主慢慢体会与学习。
二、分类目录:
创建型:
1. 单件模式(Singleton Pattern)
2. 抽象工厂(Abstract Factory)
3. 建造者模式(Builder)
4. 工厂方法模式(Factory Method)
5. 原型模式(Prototype)
结构型:
6. 适配器模式(Adapter Pattern)
7. 桥接模式(Bridge Pattern)
8. 装饰模式(Decorator Pattern)
9. 组合模式(Composite Pattern)
10. 外观模式(Facade Pattern)
11. 享元模式(Flyweight Pattern)
12. 代理模式(Proxy Pattern)
13. 模板方法(Template Method)
14. 命令模式(Command Pattern)
15. 迭代器模式(Iterator Pattern)
行为型:
16. 观察者模式(Observer Pattern)
17. 解释器模式(Interpreter Pattern)
18. 中介者模式(Mediator Pattern)
19. 职责链模式(Chain of Responsibility Pattern)
20. 备忘录模式(Memento Pattern)
21. 策略模式(Strategy Pattern)
22. 访问者模式(Visitor Pattern)
23. 状态模式(State Pattern)
C# 23种设计模式
China Document 4 Colors
1.1节 工期
How are you
1.1.1 完成日期
1.2节 创建型模式
1.2.1 单件模式(Singleton Pattern)
动机(Motivation):
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只创建一个实例?
这应该是类设计者的责任,而不是类使用者的责任。
结构图:
意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
------<<设计模式>>GOF
生活的例子:
适用性:
(1)当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
(2)当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
代码实现:
(1)单线程Singleton实现
class SingleThread_Singleton
{
private static SingleThread_Singleton instance = null;
private SingleThread_Singleton(){}
public static SingleThread_Singleton Instance
{
get
{
if (instance == null)
{
instance = new SingleThread_Singleton();
}
return instance;
}
}
}
以上代码在单线程情况下不会出现任何问题。但是在多线程的情况下却不是安全的。
如两个线程同时运行到 if (instance == null)判断是否被实例化,一个线程判断为True后,在进行创建
instance = new SingleThread_Singleton();之前,另一个线程也判断(instance == null),结果也为True.
这样就就违背了Singleton模式的原则(保证一个类仅有一个实例)。
怎样在多线程情况下实现Singleton?
(2)多线程Singleton实现:
1 class MultiThread_Singleton
2 {
3 private static volatile MultiThread_Singleton instance = null;
4 private static object lockHelper = new object();
5 private MultiThread_Singleton() { }
6 public static MultiThread_Singleton Instance
7 {
8 get
9 {
10 if (instance == null)
11 {
12 lock (lockHelper)
13 {
14 if (instance == null)
15 {
16 instance = new MultiThread_Singleton();
17 }
18 }
19 }
20 return instance;
21 }
22 }
23
此程序对多线程是安全的,使用了一个辅助对象lockHelper,保证只有一个线程创建实例(如果instance为空,保证只有一个线程instance = new MultiThread_Singleton();创建唯一的一个实例)。(Double Check)
请注意一个关键字volatile,如果去掉这个关键字,还是有可能发生线程不是安全的。
volatile 保证严格意义的多线程编译器在代码编译时对指令不进行微调。
(3)静态Singleton实现
3 class Static_Singleton
4 {
5 public static readonly Static_Singleton instance = new Static_Singleton();
6 private Static_Singleton() { }
7 }
以上代码展开等同于
1 class Static_Singleton
2 {
3 public static readonly Static_Singleton instance;
4 static Static_Singleton()
5 {
6 instance = new Static_Singleton();
7 }
8 private Static_Singleton() { }
9 }
由此可以看出,完全符合Singleton的原则。
优点: 简洁,易懂
缺点: 不可以实现带参数实例的创建
1.2.2 抽象工程模式(Abstract Factory)
常规的对象创建方法:
//创建一个Road对象
Road road =new Road();
new 的问题:
实现依赖,不能应对“具体实例化类型”的变化。
解决思路:
封装变化点-----哪里变化,封装哪里
潜台词: 如果没有变化,当然不需要额外的封装!
工厂模式的缘起
变化点在“对象创建”,因此就封装“对象创建”
面向接口编程----依赖接口,而非依赖实现
最简单的解决方法:
1 class RoadFactory{
2 public static Road CreateRoad()
3 {
4 return new Road();
5 }
6 }
7 //创建一个Road对象
8 Road road=roadFactory.CreateRoad();
创建一系列相互依赖对象的创建工作:
假设一个游戏开场景:
我们需要构造"道路"、"房屋"、"地道","从林"...等等对象
工厂方法如下:
1 class RoadFactory
2 {
3 public static Road CreateRoad()
4 {
5 return new Road();
6 }
7 public static Building CreateBuilding()
8 {
9 return new Building();
10 }
11 public static Tunnel CreateTunnel()
12 {
13 return new Tunnel();
14 }
15 public static Jungle CreateJungle()
16 {
17 return new Jungle();
18 }
19 }
调用方式如下:
1 Road road = RoadFactory.CreateRoad();
3 Building building = RoadFactory.CreateBuilding();
4 Tunnel tunnel = RoadFactory.CreateTunnel();
5 Jungle jungle = RoadFactory.CreateJungle();
如上可见简单工厂的问题:
不能应对"不同系列对象"的变化。比如有不同风格的场景---对应不同风格的道路,房屋、地道....
如何解决:
使用面向对象的技术来"封装"变化点。
动机(Motivate):
在软件系统中,经常面临着"一系统相互依赖的对象"的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种"封装机制"来避免客户程序和这种"多系列具体对象创建工作"的紧耦合?
意图(Intent):
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
----《设计模式》GOF
结构图(Struct):
适用性:
1.一个系统要独立于它的产品的创建、组合和表示时。
2.一个系统要由多个产品系统中的一个来配置时。
3.当你要强调一系列相关的产品对象的设计以便进行联合使用时。
4.当你提供一个产品类库,而只想显示它们的接口不是实现时。
生活例子:
结构图代码实现:
1 abstract class AbstractFactory
2 {
3 public abstract AbstractProductA CreateProductA();
4 public abstract AbstractProductB CreateProductB();
5 }
1 abstract class AbstractProductA
2 {
3 public abstract void Interact(AbstractProductB b);
4 }
1 abstract class AbstractProductB
2 {
3 public abstract void Interact(AbstractProductA a);
4 }
1 class Client
2 {
3 private AbstractProductA AbstractProductA;
4 private AbstractProductB AbstractProductB;
5 public Client(AbstractFactory factory)
6 {
7 AbstractProductA = factory.CreateProductA();
8 AbstractProductB = factory.CreateProductB();
9 }
10 public void Run()
11 {
12 AbstractProductB.Interact(AbstractProductA);
13 AbstractProductA.Interact(AbstractProductB);
14 }
15 }
1 class ConcreteFactory1:AbstractFactory
2 {
3 public override AbstractProductA CreateProductA()
4 {
5 return new ProductA1();
6 }
7 public override AbstractProductB CreateProductB()
8 {
9 return new ProductB1();
10 }
11 }
1 class ConcreteFactory2:AbstractFactory
2 {
3 public override AbstractProductA CreateProductA()
4 {
5 return new ProdcutA2();
6 }
7 public override AbstractProductB CreateProductB()
8 {
9 return new ProductB2();
10 }
11 }
1 class ProductA1:AbstractProductA
2 {
3 public override void Interact(AbstractProductB b)
4 {
5 Console.WriteLine(this.GetType().Name + "interact with" + b.GetType().Name);
6 }
7 }
1 class ProductB1:AbstractProductB
2 {
3 public override void Interact(AbstractProductA a)
4 {
5 Console.WriteLine(this.GetType().Name + "interact with" + a.GetType().Name);
6 }
7 }
1 class ProdcutA2:AbstractProductA
2 {
3 public override void Interact(AbstractProductB b)
4 {
5 Console.WriteLine(this.GetType().Name + "interact with" + b.GetType().Name);
6 }
7 }
1 class ProductB2:AbstractProductB
2 {
3 public override void Interact(AbstractProductA a)
4 {
5 Console.WriteLine(this.GetType().Name + "interact with" + a.GetType().Name);
6 }
7 }
1 public static void Main()
2 {
3 // Abstractfactory1
4 AbstractFactory factory1 = new ConcreteFactory1();
5 Client c1 = new Client(factory1);
6 c1.Run();
7 // Abstractfactory2
8 AbstractFactory factory2 = new ConcreteFactory2();
9 Client c2 = new Client(factory2);
10 c2.Run();
11 }
Abstract Factory注意的几点:
如果不存在”多系列对象创建“的需求变化,则没必要应用Abstract Factory模式,静态工厂方法足矣。
"系列对象"指的是这些对象之间有相互依赖、或作用的关系。例如游戏开发场景中的"道路"与"房屋"依赖,“道路”与“地道”的依赖。
Abstract Factory模式主要在于应对"新系列"的需求变动。其缺点在于难以应对”新对象“的需求变动。
Abstract Factory模式经常和Factory Method模式共同组合来应对“对象创建”的需求变化
1.2.3 建造者模式(Builder)
Builder模式的缘起:
假设创建游戏中的一个房屋House设施,该房屋的构建由几部分组成,且各个部分富于变化。如果使用最直观的设计方法,每一个房屋部分的变化,都将导致房屋构建的重新修正.....
动机(Motivation):
在软件系统中,有时候面临一个"复杂对象"的创建工作,其通常由各个部分的子对象用一定算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合到一起的算法却相对稳定。
如何应对种变化呢?如何提供一种"封装机制"来隔离出"复杂对象的各个部分"的变化,从而保持系统中的"稳定构建算法"不随需求的改变而改变?
意图(Intent):
将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
-------《设计模式》GOF
结构图(Struct):
协作(Collaborations):
生活中的例子:
适用性:
1.当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
2.当构造过程必须允许被构造的对象有不同的表示时。
实例代码:
Builder类:
1 public abstract class Builder
2 {
3 public abstract void BuildDoor();
4 public abstract void BuildWall();
5 public abstract void BuildWindows();
6 public abstract void BuildFloor();
7 public abstract void BuildHouseCeiling();
8
9 public abstract House GetHouse();
10 }
Director类:这一部分是 组合到一起的算法(相对稳定)。
1 public class Director
2 {
3 public void Construct(Builder builder)
4 {
5 builder.BuildWall();
6 builder.BuildHouseCeiling();
7 builder.BuildDoor();
8 builder.BuildWindows();
9 builder.BuildFloor();
10 }
11 }
ChineseBuilder类
1 public class ChineseBuilder:Builder
2 {
3 private House ChineseHouse = new House();
4 public override void BuildDoor()
5 {
6 Console.WriteLine("this Door 's style of Chinese");
7 }
8 public override void BuildWall()
9 {
10 Console.WriteLine("this Wall 's style of Chinese");
11 }
12 public override void BuildWindows()
13 {
14 Console.WriteLine("this Windows 's style of Chinese");
15 }
16 public override void BuildFloor()
17 {
18 Console.WriteLine("this Floor 's style of Chinese");
19 }
20 public override void BuildHouseCeiling()
21 {
22 Console.WriteLine("this Ceiling 's style of Chinese");
23 }
24 public override House GetHouse()
25 {
26 return ChineseHouse;
27 }
28 }
RomanBuilder类:
1 class RomanBuilder:Builder
2 {
3 private House RomanHouse = new House();
4 public override void BuildDoor()
5 {
6 Console.WriteLine("this Door 's style of Roman");
7 }
8 public override void BuildWall()
9 {
10 Console.WriteLine("this Wall 's style of Roman");
11 }
12 public override void BuildWindows()
13 {
14 Console.WriteLine("this Windows 's style of Roman");
15 }
16 public override void BuildFloor()
17 {
18 Console.WriteLine("this Floor 's style of Roman");
19 }
20 public override void BuildHouseCeiling()
21 {
22 Console.WriteLine("this Ceiling 's style of Roman");
23 }
24 public override House GetHouse()
25 {
26 return RomanHouse;
27 }
28 }
ChineseBuilder和RomanBuilder这两个是:这个复杂对象的两个部分经常面临着剧烈的变化。
1 public class Client
2 {
3 public static void Main(string[] args)
4 {
5 Director director = new Director();
6
7 Builder instance;
8
9 Console.WriteLine("Please Enter House No:");
10
11 string No = Console.ReadLine();
12
13 string houseType = ConfigurationSettings.AppSettings["No" + No];
14
15 instance = (Builder)Assembly.Load("House").CreateInstance("House." + houseType);
16
17 director.Construct(instance);
18
19 House house= instance.GetHouse();
20 house.Show();
21
22 Console.ReadLine();
23 }
24 }
1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <appSettings>
4 <add key="No1" value="RomanBuilder"></add>
5 <add key="No2" value="ChineseBuilder"></add>
6 </appSettings>
7 </configuration>
Builder模式的几个要点:
Builder模式 主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的乘法,而复杂对象的各个部分则经常变化。
Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
Abstract Factory模式解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化。Builder械通常和Composite模式组合使用
1.2.4 工厂方法模式(Factory Method)
耦合关系:
动机(Motivation):
在软件系统中,由于需求的变化,"这个对象的具体实现"经常面临着剧烈的变化,但它却有比较稳定的接口。
如何应对这种变化呢?提供一种封装机制来隔离出"这个易变对象"的变化,从而保持系统中"其它依赖的对象"不随需求的变化而变化。
意图(Intent):
定义一个用户创建对象的接口,让子类决定实例哪一个类。Factory Method使一个类的实例化延迟到子类。
----------《设计模式》GOF
结构图(Struct):
生活实例:
适用性:
1.当一个类不知道它所必须创建的对象类的时候。
2.当一个类希望由它子类来指定它所创建对象的时候。
3.当类将创建对象的职责委托给多个帮助子类中的某个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
实例代码:
CarFactory类:
1 public abstract class CarFactory
2 {
3 public abstract Car CarCreate();
4 }
Car类:
1 public abstract class Car
2 {
3 public abstract void StartUp();
4 public abstract void Run();
5 public abstract void Stop();
6
7 }
HongQiCarFactory类:
1 public class HongQiCarFactory:CarFactory
2 {
3 public override Car CarCreate()
4 {
5 return new HongQiCar();
6 }
7 }
BMWCarFactory类:
1 public class BMWCarFactory:CarFactory
2 {
3 public override Car CarCreate()
4 {
5 return new BMWCar();
6 }
7 }
HongQiCar类:
1 public class HongQiCar:Car
2 {
3 public override void StartUp()
4 {
5 Console.WriteLine("Test HongQiCar start-up speed!");
6 }
7 public override void Run()
8 {
9 Console.WriteLine("The HongQiCar run is very quickly!");
10 }
11 public override void Stop()
12 {
13 Console.WriteLine("The slow stop time is 3 second ");
14 }
15 }
BMWCar类:
1 public class BMWCar:Car
2 {
3 public override void StartUp()
4 {
5 Console.WriteLine("The BMWCar start-up speed is very quickly");
6 }
7 public override void Run()
8 {
9 Console.WriteLine("The BMWCar run is quitely fast and safe!!!");
10 }
11 public override void Stop()
12 {
13 Console.WriteLine("The slow stop time is 2 second");
14 }
15 }
app.config
1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <appSettings>
4 <add key="No1" value="HongQiCarFactory"/>
5 <add key="No2" value="BMWCarFactory"/>
6 </appSettings>
7 </configuration>
Program类:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Console.WriteLine("Please Enter Factory Method No:");
6 Console.WriteLine("******************************");
7 Console.WriteLine("no Factory Method");
8 Console.WriteLine("1 HongQiCarFactory");
9 Console.WriteLine("2 BMWCarFactory");
10 Console.WriteLine("******************************");
11 int no=Int32.Parse(Console.ReadLine().ToString());
12 string factoryType=ConfigurationManager.AppSettings["No"+no];
13 //CarFactory factory = new HongQiCarFactory();
14 CarFactory factory = (CarFactory)Assembly.Load("FactoryMehtod").CreateInstance("FactoryMehtod." + factoryType); ;
15 Car car=factory.CarCreate();
16 car.StartUp();
17 car.Run();
18 car.Stop();
19
20 }
21 }
Factory Method 模式的几个要点:
Factory Method模式主要用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。
Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
Factory Mehtod模式解决"单个对象"的需求变化,AbstractFactory模式解决"系列对象"的需求变化,Builder模式解决"对象部分"的需求变化
1.2.5 原型模式(Prototype)
依赖关系倒置:
动机(Motivate):
在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着
剧烈的变化,但是它们却拥有比较稳定一致的接口。
如何应对这种变化?如何向“客户程序(使用这些对象的程序)"隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?
意图(Intent):
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
------《设计模式》GOF
结构图(Struct):
生活例子:
适用性:
1.当一个系统应该独立于它的产品创建,构成和表示时;
2.当要实例化的类是在运行时刻指定时,例如,通过动态装载;
3.为了避免创建一个与产品类层次平行的工厂类层次时;
4.当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
示意性代码例子:
1 public abstract class NormalActor
2 {
3 public abstract NormalActor clone();
4 }
1 public class NormalActorA:NormalActor
2 {
3 public override NormalActor clone()
4 {
5 Console.WriteLine("NormalActorA is call");
6 return (NormalActor)this.MemberwiseClone();
7
8 }
9 }
1 public class NormalActorB :NormalActor
2 {
3 public override NormalActor clone()
4 {
5 Console.WriteLine("NormalActorB was called");
6 return (NormalActor)this.MemberwiseClone();
7
8 }
9 }
public class GameSystem
{
public void Run(NormalActor normalActor)
{
NormalActor normalActor1 = normalActor.clone();
NormalActor normalActor2 = normalActor.clone();
NormalActor normalActor3 = normalActor.clone();
NormalActor normalActor4 = normalActor.clone();
NormalActor normalActor5 = normalActor.clone();
}
}
class Program
{
static void Main(string[] args)
{
GameSystem gameSystem = new GameSystem();
gameSystem.Run(new NormalActorA());
}
}
如果又需要创建新的对象(flyActor),只需创建此抽象类,然后具体类进行克隆。
public abstract class FlyActor
{
public abstract FlyActor clone();
}
public class FlyActorB:FlyActor
{
/// <summary>
/// 浅拷贝,如果用深拷贝,可使用序列化
/// </summary>
/// <returns></returns>
public override FlyActor clone()
{
return (FlyActor)this.MemberwiseClone();
}
}
此时,调用的Main()函数只需如下:
class Program
{
static void Main(string[] args)
{
GameSystem gameSystem = new GameSystem();
gameSystem.Run(new NormalActorA(), new FlyActorB());
}
}
Prototype的几个要点:
Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这
些“易变类”拥有“稳定的接口”。
Prototype模式对于“如何创建易变类的实体对象“采用“原型克隆”的方法来做,它使得我们可以
非常灵活地动态创建“拥有某些稳定接口中”的新对象----所需工作仅仅是注册的地方不断地Clone.
Prototype模式中的Clone方法可以利用.net中的object类的memberwiseClone()方法或者序列化来实现深拷贝。
有关创建型模式的讨论:
Singleton模式解决的是实体对象个数的问题。除了Singleton之外,其他创建型模式解决的是都是new 所带来的耦合关系。
Factory Method ,Abstract Factory,Builder都需要一个额外的工厂类来负责实例化“易变对象”,而Prototype则是通过原型(一个特殊的工厂类)来克隆“易变对象”。
如果遇到“易变类”,起初的设计通常从Factory Mehtod开始,当遇到更多的复杂变化时,再考虑重重构为其他三种工厂模式(Abstract Factory,Builder,Prototype)
适配(转换)的概念无处不在......
适配,即在不改变原有实现的基础上,将原先不兼容的接口转换为兼容的接口。
例如:二转换为三箱插头,将高电压转换为低电压等。
动机(Motivate):
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
那么如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?这就是本文要说的Adapter 模式。
意图(Intent):
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
-------《设计模式》GOF
结构(Struct):
图1:对象适配器
图2:类适配器
生活中的例子:
适用性:
1.系统需要使用现有的类,而此类的接口不符合系统的需要。
2.想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
3.(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
示意性代码实例:
1 interface IStack
2 {
3 void Push(object item);
4 void Pop();
5 object Peek();
6 }
1 //对象适配器(Adapter与Adaptee组合的关系)
2 public class Adapter : IStack //适配对象
3 {
4 ArrayList adaptee;//被适配的对象
5 public Adapter()
6 {
7 adaptee = new ArrayList();
8 }
9 public void Push(object item)
10 {
11 adaptee.Add(item);
12 }
13 public void Pop()
14 {
15 adaptee.RemoveAt(adaptee.Count - 1);
16 }
17 public object Peek()
18 {
19 return adaptee[adaptee.Count - 1];
20 }
21 }
类适配器
1 public class Adapter :ArrayList, IStack
2 {
3 public void Push(object item)
4 {
5 this.Add(item);
6 }
7 public void Pop()
8 {
9 this.RemoveAt(this.Count - 1);
10 }
11 public object Peek()
12 {
13 return this[this.Count - 1];
14 }
15 }
Adapter模式的几个要点:
Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
GOF23定义了两种Adapter模式的实现结构:对象适配器和类适配器。但类适配器采用“多继承”的实现方式,带来不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。
Adapter模式可以实现的非常灵活,不必拘泥于GOF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象“作为新的接口方法参数,来达到适配的目的。
Adapter模式本身要求我们尽可能地使用”面向接口的编程"风格,这样才能在后期很方便的适配。
.NET框架中的Adapter应用:
(1)在.Net中复用com对象:
Com 对象不符合.net对象的接口
使用tlbimp.exe来创建一个Runtime Callable Wrapper(RCW)以使其符合.net对象的接口。
(2).NET数据访问类(Adapter变体):
各种数据库并没有提供DataSet接口
使用DBDataAdapter可以将任何各数据库访问/存取适配到一个DataSet对象上。
(3)集合类中对现有对象的排序(Adapter变体);
现有对象未实现IComparable接口
实现一个排序适配器(继承IComparer接口),然后在其Compare方法中对两个对象进行比较。
动机(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模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。
代码实现:
1 abstract class Brush
2 {
3 protected Color c;
4 public abstract void Paint();
5
6 public void SetColor(Color c)
7 { this.c = c; }
8 }
1 class BigBrush : Brush
2 {
3 public override void Paint()
4 { Console.WriteLine("Using big brush and color {0} painting", c.color); }
5 }
1 class SmallBrush : Brush
2 {
3 public override void Paint()
4 { Console.WriteLine("Using small brush and color {0} painting", c.color); }
5 }
1 class Color
2 {
3 public string color;
4 }
1 class Red : Color
2 {
3 public Red()
4 { this.color = "red"; }
5 }
1 class Green : Color
2 {
3 public Green()
4 { this.color = "green"; }
5 }
1 class Blue : Color
2 {
3 public Blue()
4 { this.color = "blue"; }
5 }
1 class Program
2 {
3 public static void Main()
4 {
5 Brush b = new BigBrush();
6 b.SetColor(new Red());
7 b.Paint();
8 b.SetColor(new Blue());
9 b.Paint();
10 b.SetColor(new Green());
11 b.Paint();
12
13 b = new SmallBrush();
14 b.SetColor(new Red());
15 b.Paint();
16 b.SetColor(new Blue());
17 b.Paint();
18 b.SetColor(new Green());
19 b.Paint();
20 }
适用性:
1.如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
2.设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
3 .一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
4 .虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
Bridge要点:
1.Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
2.所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同平台上的不同型号。
3.Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
4.Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。
子类复子类,子类何其多
假如我们需要为游戏中开发一种坦克,除了各种不同型号的坦克外,我们还希望在不同场合中为其增加以下一种或多种功能;比如红外线夜视功能,比如水陆两栖功能,比如卫星定位功能等等。
按类继承的作法如下:
1 //抽象坦克
2 public abstract class Tank
3 {
4 public abstract void Shot();
5 public abstract void Run();
6 }
各种型号:
1 //T50型号
2 public class T50:Tank
3 {
4 public override void Shot()
5 {
6 Console.WriteLine("T50坦克平均每秒射击5发子弹");
7 }
8 public override void Run()
9 {
10 Console.WriteLine("T50坦克平均每时运行30公里");
11 }
12 }
1 //T75型号
2 public class T75 : Tank
3 {
4 public override void Shot()
5 {
6 Console.WriteLine("T75坦克平均每秒射击10发子弹");
7 }
8 public override void Run()
9 {
10 Console.WriteLine("T75坦克平均每时运行35公里");
11 }
12 }
1 //T90型号
2 public class T90 :Tank
3 {
4 public override void Shot()
5 {
6 Console.WriteLine("T90坦克平均每秒射击10发子弹");
7 }
8 public override void Run()
9 {
10 Console.WriteLine("T90坦克平均每时运行40公里");
11 }
12 }
各种不同功能的组合:比如IA具有红外功能接口、IB具有水陆两栖功能接口、IC具有卫星定位功能接口。
1 //T50坦克各种功能的组合
2 public class T50A:T50,IA
3 {
4 //具有红外功能
5 }
6 public class T50B:T50,IB
7 {
8 //具有水陆两栖功能
9 }
10 public class T50C:T50,IC
11 {
12
13 }
14 public class T50AB:T50,IA,IB
15 {}
18 public class T50AC:T50,IA,IC
19 {}
20 public class T50BC:T50,IB,IC
21 {}
22 public class T50ABC:T50,IA,IB,IC
23 {}
1
2 //T75各种不同型号坦克各种功能的组合
3 public class T75A:T75,IA
4 {
5 //具有红外功能
6 }
7 public class T75B:T75,IB
8 {
9 //具有水陆两栖功能
10 }
11 public class T75C:T75,IC
12 {
13 //具有卫星定位功能
14 }
15 public class T75AB:T75,IA,IB
16 {
17 //具有红外、水陆两栖功能
18 }
19 public class T75AC:T75,IA,IC
20 {
21 //具有红外、卫星定位功能
22 }
23 public class T75BC:T75,IB,IC
24 {
25 //具有水陆两栖、卫星定位功能
26 }
27 public class T75ABC:T75,IA,IB,IC
28 {
29 //具有红外、水陆两栖、卫星定位功能
30 }
1
2 //T90各种不同型号坦克各种功能的组合
3 public class T90A:T90,IA
4 {
5 //具有红外功能
6 }
7 public class T90B:T90,IB
8 {
9 //具有水陆两栖功能
10 }
11 public class T90C:T90,IC
12 {
13 //具有卫星定位功能
14 }
15 public class T90AB:T90,IA,IB
16 {
17 //具有红外、水陆两栖功能
18 }
19 public class T90AC:T90,IA,IC
20 {
21 //具有红外、卫星定位功能
22 }
23 public class T90BC:T90,IB,IC
24 {
25 //具有水陆两栖、卫星定位功能
26 }
27 public class T90ABC:T90,IA,IB,IC
28 {
29 //具有红外、水陆两栖、卫星定位功能
30 }
由此可见,如果用类继承实现,子类会爆炸式地增长。
动机(Motivate):
上述描述的问题根源在于我们“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态物质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能组合)会导致更多子类的膨胀(多继承)。
如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
意图(Intent):
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
------《设计模式》GOF
结构图(Struct):
生活中的例子:
适用性:
需要扩展一个类的功能,或给一个类增加附加责任。
需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
实现代码:
1 namespace Decorator
2 {
3 public abstract class Tank
4 {
5 public abstract void Shot();
6 public abstract void Run();
7 }
8 }
1 namespace Decorator
2 {
3 public class T50:Tank
4 {
5 public override void Shot()
6 {
7 Console.WriteLine("T50坦克平均每秒射击5发子弹");
8 }
9 public override void Run()
10 {
11 Console.WriteLine("T50坦克平均每时运行30公里");
12 }
13 }
14 }
1 namespace Decorator
2 {
3 public class T75 : Tank
4 {
5 public override void Shot()
6 {
7 Console.WriteLine("T75坦克平均每秒射击10发子弹");
8 }
9 public override void Run()
10 {
11 Console.WriteLine("T75坦克平均每时运行35公里");
12 }
13 }
14 }
1 namespace Decorator
2 {
3 public class T90 :Tank
4 {
5 public override void Shot()
6 {
7 Console.WriteLine("T90坦克平均每秒射击10发子弹");
8 }
9 public override void Run()
10 {
11 Console.WriteLine("T90坦克平均每时运行40公里");
12 }
13 }
14 }
1 namespace Decorator
2 {
3 public abstract class Decorator :Tank //Do As 接口继承 非实现继承
4 {
5 private Tank tank; //Has a 对象组合
6 public Decorator(Tank tank)
7 {
8 this.tank = tank;
9 }
10 public override void Shot()
11 {
12 tank.Shot();
13 }
14 public override void Run()
15 {
16 tank.Run();
17 }
18 }
19 }
20
1
2 namespace Decorator
3 {
4 public class DecoratorA :Decorator
5 {
6 public DecoratorA(Tank tank) : base(tank)
7 {
8 }
9 public override void Shot()
10 {
11 //Do some extension //功能扩展 且有红外功能
12 base.Shot();
13 }
14 public override void Run()
15 {
16
17 base.Run();
18 }
19 }
20 }
1 namespace Decorator
2 {
3 public class DecoratorB :Decorator
4 {
5 public DecoratorB(Tank tank) : base(tank)
6 {
7 }
8 public override void Shot()
9 {
10 //Do some extension //功能扩展 且有水陆两栖功能
11 base.Shot();
12 }
13 public override void Run()
14 {
15
16 base.Run();
17 }
18 }
19 }
20
1 namespace Decorator
2 {
3 public class DecoratorC :Decorator
4 {
5 public DecoratorC(Tank tank) : base(tank)
6 {
7 }
8 public override void Shot()
9 {
10 //Do some extension //功能扩展 且有卫星定位功能
11 base.Shot();
12 }
13 public override void Run()
14 {
15
16 base.Run();
17 }
18
19 }
20 }
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Tank tank = new T50();
6 DecoratorA da = new DecoratorA(tank); //且有红外功能
7 DecoratorB db = new DecoratorB(da); //且有红外和水陆两栖功能
8 DecoratorC dc = new DecoratorC(db); //且有红外、水陆两栖、卫星定们三种功能
9 dc.Shot();
10 dc.Run();
11 }
12 }
Decorator模式的几个要点:
通过采用组合、而非继承的手法,Decorator模式实现了在运行时动态地扩展对象功能的能力,而且可以
根据需要扩展多个功能。避免了单独使用继承带来的“灵活性差"和"多子类衍生问题"。
Component类在Decorator模式中充当抽象接口的角色,不应该去实现具体的行为。而且Decorator类对于Component类应该透明---换言之Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能。
Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所且有的接口。但在实现上又表现has a Component的组合关系,即Decorator类又使用了另外一个Component类。我们可以使用一个或者多个Decorator对象来“装饰”一个Component对象,且装饰后的对象仍然是一个Component对象。
Decorator模式并非解决”多子类衍生的多继承“问题,Decorator模式应用的要点在于解决“主体
类在多个方向上的扩展功能”------是为“装饰”的含义。
Decorator在.NET(Stream)中的应用:
可以看到, BufferedStream和CryptoStream其实就是两个包装类,这里的Decorator模式省略了抽象装饰角色(Decorator),示例代码如下:
1 class Program
2
3 {
4
5 public static void Main(string[] args)
6
7 {
8
9 MemoryStream ms =
10
11 new MemoryStream(new byte[] { 100,456,864,222,567});
12
13
14
15 //扩展了缓冲的功能
16
17 BufferedStream buff = new BufferedStream(ms);
18
19
20
21 //扩展了缓冲,加密的功能
22
23 CryptoStream crypto = new CryptoStream(buff);
24
25 }
26
27 }
通过反编译,可以看到BufferedStream类的代码(只列出部分),它是继承于Stream类:
1 public sealed class BufferedStream : Stream
2
3 {
4
5 // Methods
6
7 private BufferedStream();
8
9 public BufferedStream(Stream stream);
10
11 public BufferedStream(Stream stream, int bufferSize);
12
13 // Fields
14
15 private int _bufferSize;
16
17 private Stream _s;
18
19 }
动机(Motivate):
组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
意图(Intent):
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。
-----------《设计模式》GOF
结构图(Struct):
生活中的例子:
适用性:
1.你想表示对象的部分-整体层次结构
2.你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
代码实现:
这里我们用绘图这个例子来说明Composite模式,通过一些基本图像元素(直线、圆等)以及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树。在设计中我们对每一个对象都配备一个Draw()方法,在调用时,会显示相关的图形。可以看到,这里复合图像元素它在充当对象的同时,又是那些基本图像元素的一个容器。先看一下基本的类结构图:
图中橙色的区域表示的是复合图像元素。
示意性代码:
1 public abstract class Graphics
2 {
3 protected string _name;
4
5 public Graphics(string name)
6 {
7 this._name = name;
8 }
9 public abstract void Draw();
10 }
11
12 public class Picture : Graphics
13 {
14 public Picture(string name)
15 : base(name)
16 { }
17 public override void Draw()
18 {
19 //
20 }
21
22 public ArrayList GetChilds()
23 {
24 //返回所有的子对象
25 }
26 }
而其他作为树枝构件,实现代码如下:
1 public class Line:Graphics
2 {
3 public Line(string name)
4 : base(name)
5 { }
6
7 public override void Draw()
8 {
9 Console.WriteLine("Draw a" + _name.ToString());
10 }
11 }
12
13 public class Circle : Graphics
14 {
15 public Circle(string name)
16 : base(name)
17 { }
18
19 public override void Draw()
20 {
21 Console.WriteLine("Draw a" + _name.ToString());
22 }
23 }
24
25 public class Rectangle : Graphics
26 {
27 public Rectangle(string name)
28 : base(name)
29 { }
30
31 public override void Draw()
32 {
33 Console.WriteLine("Draw a" + _name.ToString());
34 }
35 }
现在我们要对该图像元素进行处理:在客户端程序中,需要判断返回对象的具体类型到底是基本图像元素,还是复合图像元素。如果是复合图像元素,我们将要用递归去处理,然而这种处理的结果却增加了客户端程序与复杂图像元素内部结构之间的依赖,那么我们如何去解耦这种关系呢?我们希望的是客户程序可以像处理基本图像元素一样来处理复合图像元素,这就要引入Composite模式了,需要把对于子对象的管理工作交给复合图像元素,为了进行子对象的管理,它必须提供必要的Add(),Remove()等方法,类结构图如下:
示意代码:
1 public abstract class Graphics
2 {
3 protected string _name;
4
5 public Graphics(string name)
6 {
7 this._name = name;
8 }
9 public abstract void Draw();
10 public abstract void Add();
11 public abstract void Remove();
12 }
13
14 public class Picture : Graphics
15 {
16 protected ArrayList picList = new ArrayList();
17
18 public Picture(string name)
19 : base(name)
20 { }
21 public override void Draw()
22 {
23 Console.WriteLine("Draw a" + _name.ToString());
24
25 foreach (Graphics g in picList)
26 {
27 g.Draw();
28 }
29 }
30
31 public override void Add(Graphics g)
32 {
33 picList.Add(g);
34 }
35 public override void Remove(Graphics g)
36 {
37 picList.Remove(g);
38 }
39 }
40
41 public class Line : Graphics
42 {
43 public Line(string name)
44 : base(name)
45 { }
46
47 public override void Draw()
48 {
49 Console.WriteLine("Draw a" + _name.ToString());
50 }
51 public override void Add(Graphics g)
52 { }
53 public override void Remove(Graphics g)
54 { }
55 }
56
57 public class Circle : Graphics
58 {
59 public Circle(string name)
60 : base(name)
61 { }
62
63 public override void Draw()
64 {
65 Console.WriteLine("Draw a" + _name.ToString());
66 }
67 public override void Add(Graphics g)
68 { }
69 public override void Remove(Graphics g)
70 { }
71 }
72
73 public class Rectangle : Graphics
74 {
75 public Rectangle(string name)
76 : base(name)
77 { }
78
79 public override void Draw()
80 {
81 Console.WriteLine("Draw a" + _name.ToString());
82 }
83 public override void Add(Graphics g)
84 { }
85 public override void Remove(Graphics g)
86 { }
87 }
这样引入Composite模式后,客户端程序不再依赖于复合图像元素的内部实现了。然而,我们程序中仍然存在着问题,因为Line,Rectangle,Circle已经没有了子对象,它是一个基本图像元素,因此Add(),Remove()的方法对于它来说没有任何意义,而且把这种错误不会在编译的时候报错,把错误放在了运行期,我们希望能够捕获到这类错误,并加以处理,稍微改进一下我们的程序:
1 public class Line : Graphics
2 {
3 public Line(string name)
4 : base(name)
5 { }
6
7 public override void Draw()
8 {
9 Console.WriteLine("Draw a" + _name.ToString());
10 }
11 public override void Add(Graphics g)
12 {
13 //抛出一个我们自定义的异常
14 }
15 public override void Remove(Graphics g)
16 {
17 //抛出一个我们自定义的异常
18 }
19 }
这样改进以后,我们可以捕获可能出现的错误,做进一步的处理。上面的这种实现方法属于透明式的Composite模式,如果我们想要更安全的一种做法,就需要把管理子对象的方法声明在树枝构件Picture类里面,这样如果叶子节点Line,Rectangle,Circle使用这些方法时,在编译期就会出错,看一下类结构图:
示意代码:
1 public abstract class Graphics
2 {
3 protected string _name;
4
5 public Graphics(string name)
6 {
7 this._name = name;
8 }
9 public abstract void Draw();
10 }
11
12 public class Picture : Graphics
13 {
14 protected ArrayList picList = new ArrayList();
15
16 public Picture(string name)
17 : base(name)
18 { }
19 public override void Draw()
20 {
21 Console.WriteLine("Draw a" + _name.ToString());
22
23 foreach (Graphics g in picList)
24 {
25 g.Draw();
26 }
27 }
28
29 public void Add(Graphics g)
30 {
31 picList.Add(g);
32 }
33 public void Remove(Graphics g)
34 {
35 picList.Remove(g);
36 }
37 }
38
39 public class Line : Graphics
40 {
41 public Line(string name)
42 : base(name)
43 { }
44
45 public override void Draw()
46 {
47 Console.WriteLine("Draw a" + _name.ToString());
48 }
49 }
50
51 public class Circle : Graphics
52 {
53 public Circle(string name)
54 : base(name)
55 { }
56
57 public override void Draw()
58 {
59 Console.WriteLine("Draw a" + _name.ToString());
60 }
61 }
62
63 public class Rectangle : Graphics
64 {
65 public Rectangle(string name)
66 : base(name)
67 { }
68
69 public override void Draw()
70 {
71 Console.WriteLine("Draw a" + _name.ToString());
72 }
73 }
这种方式属于安全式的Composite模式,在这种方式下,虽然避免了前面所讨论的错误,但是它也使得叶子节点和树枝构件具有不一样的接口。这种方式和透明式的Composite各有优劣,具体使用哪一个,需要根据问题的实际情况而定。通过Composite模式,客户程序在调用Draw()的时候不用再去判断复杂图像元素中的子对象到底是基本图像元素,还是复杂图像元素,看一下简单的客户端调用:
1 public class App
2 {
3 public static void Main()
4 {
5 Picture root = new Picture("Root");
6
7 root.Add(new Line("Line"));
8 root.Add(new Circle("Circle"));
9
10 Rectangle r = new Rectangle("Rectangle");
11 root.Add(r);
12
13 root.Draw();
Composite模式实现要点:
1.Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
2.将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复内部实现结构——发生依赖关系,从而更能“应对变化”。
3.Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.NET控件的实现在这方面为我们提供了一个很好的示范。
4.Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
动机(Motivate):
在软件开发系统中,客户程序经常会与复杂系统的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化。那么如何简化客户程序与子系统之间的交互接口?如何将复杂系统的内部子系统与客户程序之间的依赖解耦?
意图(Intent):
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
--------《设计模式》GOF
结构图(Struct):
适用性:
1.为一个复杂子系统提供一个简单接口。
2.提高子系统的独立性。
3.在层次化结构中,可以使用Facade模式定义系统中每一层的入口。
生活中的例子:
代码实现:
我们平时的开发中其实已经不知不觉的在用Façade模式,现在来考虑这样一个抵押系统,当有一个客户来时,有如下几件事情需要确认:到银行子系统查询他是否有足够多的存款,到信用子系统查询他是否有良好的信用,到贷款子系统查询他有无贷款劣迹。只有这三个子系统都通过时才可进行抵押。我们先不考虑Façade模式,那么客户程序就要直接访问这些子系统,分别进行判断。类结构图下:
在这个程序中,我们首先要有一个顾客类,它是一个纯数据类,并无任何操作,示意代码:
1 //顾客类
2 public class Customer
3 {
4 private string _name;
5
6 public Customer(string name)
7 {
8 this._name = name;
9 }
10
11 public string Name
12 {
13 get { return _name; }
14 }
15 }
下面这三个类均是子系统类,示意代码:
1 //银行子系统
2 public class Bank
3 {
4 public bool HasSufficientSavings(Customer c, int amount)
5 {
6 Console.WriteLine("Check bank for " + c.Name);
7 return true;
8 }
9 }
10
11 //信用子系统
12 public class Credit
13 {
14 public bool HasGoodCredit(Customer c)
15 {
16 Console.WriteLine("Check credit for " + c.Name);
17 return true;
18 }
19 }
20
21 //贷款子系统
22 public class Loan
23 {
24 public bool HasNoBadLoans(Customer c)
25 {
26 Console.WriteLine("Check loans for " + c.Name);
27 return true;
28 }
29 }
看客户程序的调用:
1 //客户程序
2 public class MainApp
3 {
4 private const int _amount = 12000;
5
6 public static void Main()
7 {
8 Bank bank = new Bank();
9 Loan loan = new Loan();
10 Credit credit = new Credit();
11
12 Customer customer = new Customer("Ann McKinsey");
13
14 bool eligible = true;
15
16 if (!bank.HasSufficientSavings(customer, _amount))
17 {
18 eligible = false;
19 }
20 else if (!loan.HasNoBadLoans(customer))
21 {
22 eligible = false;
23 }
24 else if (!credit.HasGoodCredit(customer))
25 {
26 eligible = false;
27 }
28
29 Console.WriteLine("\n" + customer.Name + " has been " + (eligible ? "Approved" : "Rejected"));
30 Console.ReadLine();
31 }
32 }
可以看到,在不用Façade模式的情况下,客户程序与三个子系统都发生了耦合,这种耦合使得客户程序依赖于子系统,当子系统化时,客户程序也将面临很多变化的挑战。一个合情合理的设计就是为这些子系统创建一个统一的接口,这个接口简化了客户程序的判断操作。看一下引入Façade模式后的类结构图:
变
外观类Mortage的实现如下:
1 /外观类
2 public class Mortgage
3 {
4 private Bank bank = new Bank();
5 private Loan loan = new Loan();
6 private Credit credit = new Credit();
7
8 public bool IsEligible(Customer cust, int amount)
9 {
10 Console.WriteLine("{0} applies for {1:C} loan\n",
11 cust.Name, amount);
12
13 bool eligible = true;
14
15 if (!bank.HasSufficientSavings(cust, amount))
16 {
17 eligible = false;
18 }
19 else if (!loan.HasNoBadLoans(cust))
20 {
21 eligible = false;
22 }
23 else if (!credit.HasGoodCredit(cust))
24 {
25 eligible = false;
26 }
27
28 return eligible;
29 }
30 }
顾客类和子系统类的实现仍然如下:
1 //银行子系统
2 public class Bank
3 {
4 public bool HasSufficientSavings(Customer c, int amount)
5 {
6 Console.WriteLine("Check bank for " + c.Name);
7 return true;
8 }
9 }
10
11 //信用证子系统
12 public class Credit
13 {
14 public bool HasGoodCredit(Customer c)
15 {
16 Console.WriteLine("Check credit for " + c.Name);
17 return true;
18 }
19 }
20
21 //贷款子系统
22 public class Loan
23 {
24 public bool HasNoBadLoans(Customer c)
25 {
26 Console.WriteLine("Check loans for " + c.Name);
27 return true;
28 }
29 }
30
31 //顾客类
32 public class Customer
33 {
34 private string name;
35
36 public Customer(string name)
37 {
38 this.name = name;
39 }
40
41 public string Name
42 {
43 get { return name; }
44 }
45 }
而此时客户程序的实现:
1 //客户程序类
2 public class MainApp
3 {
4 public static void Main()
5 {
6 //外观
7 Mortgage mortgage = new Mortgage();
8
9 Customer customer = new Customer("Ann McKinsey");
10 bool eligable = mortgage.IsEligible(customer, 125000);
11
12 Console.WriteLine("\n" + customer.Name +
13 " has been " + (eligable ? "Approved" : "Rejected"));
14 Console.ReadLine();
15 }
16 }
可以看到引入Façade模式后,客户程序只与Mortgage发生依赖,也就是Mortgage屏蔽了子系统之间的复杂的操作,达到了解耦内部子系统与客户程序之间的依赖。
.NET架构中的Façade模式
Façade模式在实际开发中最多的运用当属开发N层架构的应用程序了,一个典型的N层结构如下:
在这个架构中,总共分为四个逻辑层,分别为:用户层UI,业务外观层Business Façade,业务规则层Business Rule,数据访问层Data Access。其中Business Façade层的职责如下:
l 从“用户”层接收用户输入
l 如果请求需要对数据进行只读访问,则可能使用“数据访问”层
l 将请求传递到“业务规则”层
l 将响应从“业务规则”层返回到“用户”层
l 在对“业务规则”层的调用之间维护临时状态
对这一架构最好的体现就是Duwamish示例了。在该应用程序中,有部分操作只是简单的从数据库根据条件提取数据,不需要经过任何处理,而直接将数据显示到网页上,比如查询某类别的图书列表。而另外一些操作,比如计算定单中图书的总价并根据顾客的级别计算回扣等等,这部分往往有许多不同的功能的类,操作起来也比较复杂。如果采用传统的三层结构,这些商业逻辑一般是会放在中间层,那么对内部的这些大量种类繁多,使用方法也各异的不同的类的调用任务,就完全落到了表示层。这样势必会增加表示层的代码量,将表示层的任务复杂化,和表示层只负责接受用户的输入并返回结果的任务不太相称,并增加了层与层之间的耦合程度。于是就引入了一个Façade层,让这个Facade来负责管理系统内部类的调用,并为表示层提供了一个单一而简单的接口。看一下Duwamish结构图:
从图中可以看到,UI层将请求发送给业务外观层,业务外观层对请求进行初步的处理,判断是否需要调用业务规则层,还是直接调用数据访问层获取数据。最后由数据访问层访问数据库并按照来时的步骤返回结果到UI层,来看具体的代码实现。
在获取商品目录的时候,Web UI调用业务外观层:
1 productSystem = new ProductSystem();
2 categorySet = productSystem.GetCategories(categoryID);
业务外观层直接调用了数据访问层:
1 public CategoryData GetCategories(int categoryId)
2 {
3 //
4 // Check preconditions
5 //
6 ApplicationAssert.CheckCondition(categoryId >= 0,"Invalid Category Id",ApplicationAssert.LineNumber);
7 //
8 // Retrieve the data
9 //
10 using (Categories accessCategories = new Categories())
11 {
12 return accessCategories.GetCategories(categoryId);
13 }
14
15 }
在添加订单时,UI调用业务外观层:
1 public void AddOrder()
2 {
3 ApplicationAssert.CheckCondition(cartOrderData != null, "Order requires data", ApplicationAssert.LineNumber);
4
5 //Write trace log.
6 ApplicationLog.WriteTrace("Duwamish7.Web.Cart.AddOrder:\r\nCustomerId: " +
7 cartOrderData.Tables[OrderData.CUSTOMER_TABLE].Rows[0][OrderData.PKID_FIELD].ToString());
8 cartOrderData = (new OrderSystem()).AddOrder(cartOrderData);
9 }
业务外观层调用业务规则层:
1 public OrderData AddOrder(OrderData order)
2 {
3 //
4 // Check preconditions
5 //
6 ApplicationAssert.CheckCondition(order != null, "Order is required", ApplicationAssert.LineNumber);
7
8 (new BusinessRules.Order()).InsertOrder(order);
9 return order;
10 }
业务规则层进行复杂的逻辑处理后,再调用数据访问层:
1 public OrderData AddOrder(OrderData order)
2 {
3 //
4 // Check preconditions
5 //
6 ApplicationAssert.CheckCondition(order != null, "Order is required", ApplicationAssert.LineNumber);
7
8 (new BusinessRules.Order()).InsertOrder(order);
9 return order;
10 }
11
12
13 业务规则层进行复杂的逻辑处理后,再调用数据访问层:
14 public bool InsertOrder(OrderData order)
15 {
16 //
17 // Assume it's good
18 //
19 bool isValid = true;
20 //
21 // Validate order summary
22 //
23 DataRow summaryRow = order.Tables[OrderData.ORDER_SUMMARY_TABLE].Rows[0];
24
25 summaryRow.ClearErrors();
26
27 if (CalculateShipping(order) != (Decimal)(summaryRow[OrderData.SHIPPING_HANDLING_FIELD]))
28 {
29 summaryRow.SetColumnError(OrderData.SHIPPING_HANDLING_FIELD, OrderData.INVALID_FIELD);
30 isValid = false;
31 }
32
33 if (CalculateTax(order) != (Decimal)(summaryRow[OrderData.TAX_FIELD]))
34 {
35 summaryRow.SetColumnError(OrderData.TAX_FIELD, OrderData.INVALID_FIELD);
36 isValid = false;
37 }
38 //
39 // Validate shipping info
40 //
41 isValid &= IsValidField(order, OrderData.SHIPPING_ADDRESS_TABLE, OrderData.SHIP_TO_NAME_FIELD, 40);
42 //
43 // Validate payment info
44 //
45 DataRow paymentRow = order.Tables[OrderData.PAYMENT_TABLE].Rows[0];
46
47 paymentRow.ClearErrors();
48
49 isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_TYPE_FIELD, 40);
50 isValid &= IsValidField(paymentRow, OrderData.CREDIT_CARD_NUMBER_FIELD, 32);
51 isValid &= IsValidField(paymentRow, OrderData.EXPIRATION_DATE_FIELD, 30);
52 isValid &= IsValidField(paymentRow, OrderData.NAME_ON_CARD_FIELD, 40);
53 isValid &= IsValidField(paymentRow, OrderData.BILLING_ADDRESS_FIELD, 255);
54 //
55 // Validate the order items and recalculate the subtotal
56 //
57 DataRowCollection itemRows = order.Tables[OrderData.ORDER_ITEMS_TABLE].Rows;
58
59 Decimal subTotal = 0;
60
61 foreach (DataRow itemRow in itemRows)
62 {
63 itemRow.ClearErrors();
64
65 subTotal += (Decimal)(itemRow[OrderData.EXTENDED_FIELD]);
66
67 if ((Decimal)(itemRow[OrderData.PRICE_FIELD]) <= 0)
68 {
69 itemRow.SetColumnError(OrderData.PRICE_FIELD, OrderData.INVALID_FIELD);
70 isValid = false;
71 }
72
73 if ((short)(itemRow[OrderData.QUANTITY_FIELD]) <= 0)
74 {
75 itemRow.SetColumnError(OrderData.QUANTITY_FIELD, OrderData.INVALID_FIELD);
76 isValid = false;
77 }
78 }
79 //
80 // Verify the subtotal
81 //
82 if (subTotal != (Decimal)(summaryRow[OrderData.SUB_TOTAL_FIELD]))
83 {
84 summaryRow.SetColumnError(OrderData.SUB_TOTAL_FIELD, OrderData.INVALID_FIELD);
85 isValid = false;
86 }
87
88 if ( isValid )
89 {
90 using (DataAccess.Orders ordersDataAccess = new DataAccess.Orders())
91 {
92 return (ordersDataAccess.InsertOrderDetail(order)) > 0;
93 }
94 }
95 else
96 return false;
97 }
Facade模式的个要点:
从客户程序的角度来看,Facade模式不仅简化了整个组件系统的接口,同时对于组件内部与外部客户程序来说,从某种程度上也达到了一种“解耦”的效果----内部子系统的任何变化不会影响到Facade接口的变化。
Facade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Facdae很多时候更是一种架构
设计模式。
注意区分Facade模式、Adapter模式、Bridge模式与Decorator模式。Facade模式注重简化接口,Adapter模式注重转换接口,Bridge模式注重分离接口(抽象)与其实现,Decorator模式注重稳定接口的前提下为对象扩展功能。
LINQ是什么?
它是Language Integrated Query。
当我们要对数据库表进行查询的时候,我们一定会编写 "select * from sometable where ID = .."的语句。好,那我们现在根据LINQ的语法,完全可以将我们熟悉的SQL中像"select","from","where"等语句在.NET Framework环境中顺利使用并且大大提高开发的效率。
下面我就牛刀小试,做个demo看看。
1. 先下载LinQ框架
现在最新版本是2006年5月发布"Orcas CTP", 下载地址(这里 )
2. 下载安装待完毕。
3. 新建一个"LINQ Console Application"项目。
4. 输入代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Query;
5 using System.Xml.XLinq;
6 using System.Data.DLinq;
7
8 namespace LINQConsoleApplication1
9 {
10 class Program
11 {
12 static void Main(string[] args)
13 {
14 string[] aBunchOfWords = {"One","Two", "Hello", "World",
15
16 "Four", "Five"};
17 var result =
18 from s in aBunchOfWords // query the string array
19 where s.Length == 5 // for all words with length = 5
20 select s; // and return the string
21 foreach (var s in result) {
22 Console.WriteLine(s); //print
23 }
24 }
25 }
26 }
运行结果如下:
Hello
World
print any key to continue ...
面向对象的代价
面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。但是,在
某些特殊的应用中下,由于对象的数量太大,采用面向对象会给系统带来难以承受的内存开销。比如:
图形应用中的图元等对象、字处理应用中的字符对象等。
动机(Motivate):
采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价--------主要指内存需求方面的代价。
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?
意图(Intent):
运用共享技术有效地支持大量细粒度的对象。 -------《设计模式》GOF
结构(Struct):
适用性:
当以下所有的条件都满足时,可以考虑使用享元模式:
1、 一个系统有大量的对象。
2、 这些对象耗费大量的内存。
3、 这些对象的状态中的大部分都可以外部化。
4、 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5、 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
满足以上的这些条件的系统可以使用享元对象。最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。
生活中的例子:
享元模式使用共享技术有效地支持大量细粒度的对象。公共交换电话网(PSTN)是享元的一个例子。有一些资源例如拨号音发生器、振铃发生器和拨号接收器是必须由所有用户共享的。当一个用户拿起听筒打电话时,他不需要知道使用了多少资源。对于用户而言所有的事情就是有拨号音,拨打号码,拨通电话。
代码实现:
Flyweight在拳击比赛中指最轻量级,即“蝇量级”,这里翻译为“享元”,可以理解为共享元对象(细粒度对象)的意思。提到Flyweight模式都会一般都会用编辑器例子来说明,这里也不例外,但我会尝试着通过重构来看待Flyweight模式。考虑这样一个字处理软件,它需要处理的对象可能有单个的字符,由字符组成的段落以及整篇文档,根据面向对象的设计思想和Composite模式,不管是字符还是段落,文档都应该作为单个的对象去看待,这里只考虑单个的字符,不考虑段落及文档等对象,于是可以很容易的得到下面的结构图:
1 // "Charactor"
2 public abstract class Charactor
3 {
4 //Fields
5 protected char _symbol;
6
7 protected int _width;
8
9 protected int _height;
10
11 protected int _ascent;
12
13 protected int _descent;
14
15 protected int _pointSize;
16
17 //Method
18 public abstract void Display();
19 }
20
21 // "CharactorA"
22 public class CharactorA : Charactor
23 {
24 // Constructor
25 public CharactorA()
26 {
27 this._symbol = 'A';
28 this._height = 100;
29 this._width = 120;
30 this._ascent = 70;
31 this._descent = 0;
32 this._pointSize = 12;
33 }
34
35 //Method
36 public override void Display()
37 {
38 Console.WriteLine(this._symbol);
39 }
40 }
41
42 // "CharactorB"
43 public class CharactorB : Charactor
44 {
45 // Constructor
46 public CharactorB()
47 {
48 this._symbol = 'B';
49 this._height = 100;
50 this._width = 140;
51 this._ascent = 72;
52 this._descent = 0;
53 this._pointSize = 10;
54 }
55
56 //Method
57 public override void Display()
58 {
59 Console.WriteLine(this._symbol);
60 }
61 }
62
63 // "CharactorC"
64 public class CharactorC : Charactor
65 {
66 // Constructor
67 public CharactorC()
68 {
69 this._symbol = 'C';
70 this._height = 100;
71 this._width = 160;
72 this._ascent = 74;
73 this._descent = 0;
74 this._pointSize = 14;
75 }
76
77 //Method
78 public override void Display()
79 {
80 Console.WriteLine(this._symbol);
81 }
82 }
好了,现在看到的这段代码可以说是很好地符合了面向对象的思想,但是同时我们也为此付出了沉重的代价,那就是性能上的开销,可以想象,在一篇文档中,字符的数量远不止几百个这么简单,可能上千上万,内存中就同时存在了上千上万个Charactor对象,这样的内存开销是可想而知的。进一步分析可以发现,虽然我们需要的Charactor实例非常多,这些实例之间只不过是状态不同而已,也就是说这些实例的状态数量是很少的。所以我们并不需要这么多的独立的Charactor实例,而只需要为每一种Charactor状态创建一个实例,让整个字符处理软件共享这些实例就可以了。看这样一幅示意图:
现在我们看到的A,B,C三个字符是共享的,也就是说如果文档中任何地方需要这三个字符,只需要使用共享的这三个实例就可以了。然而我们发现单纯的这样共享也是有问题的。虽然文档中的用到了很多的A字符,虽然字符的symbol等是相同的,它可以共享;但是它们的pointSize却是不相同的,即字符在文档中中的大小是不相同的,这个状态不可以共享。为解决这个问题,首先我们将不可共享的状态从类里面剔除出去,即去掉pointSize这个状态(只是暂时的J),类结构图如下所示:
1 // "Charactor"
2 public abstract class Charactor
3 {
4 //Fields
5 protected char _symbol;
6
7 protected int _width;
8
9 protected int _height;
10
11 protected int _ascent;
12
13 protected int _descent;
14
15 //Method
16 public abstract void Display();
17 }
18
19 // "CharactorA"
20 public class CharactorA : Charactor
21 {
22 // Constructor
23 public CharactorA()
24 {
25 this._symbol = 'A';
26 this._height = 100;
27 this._width = 120;
28 this._ascent = 70;
29 this._descent = 0;
30 }
31
32 //Method
33 public override void Display()
34 {
35 Console.WriteLine(this._symbol);
36 }
37 }
38
39 // "CharactorB"
40 public class CharactorB : Charactor
41 {
42 // Constructor
43 public CharactorB()
44 {
45 this._symbol = 'B';
46 this._height = 100;
47 this._width = 140;
48 this._ascent = 72;
49 this._descent = 0;
50 }
51
52 //Method
53 public override void Display()
54 {
55 Console.WriteLine(this._symbol);
56 }
57 }
58
59 // "CharactorC"
60 public class CharactorC : Charactor
61 {
62 // Constructor
63 public CharactorC()
64 {
65 this._symbol = 'C';
66 this._height = 100;
67 this._width = 160;
68 this._ascent = 74;
69 this._descent = 0;
70 }
71
72 //Method
73 public override void Display()
74 {
75 Console.WriteLine(this._symbol);
76 }
77 }
好,现在类里面剩下的状态都可以共享了,下面我们要做的工作就是控制Charactor类的创建过程,即如果已经存在了“A”字符这样的实例,就不需要再创建,直接返回实例;如果没有,则创建一个新的实例。如果把这项工作交给Charactor类,即Charactor类在负责它自身职责的同时也要负责管理Charactor实例的管理工作,这在一定程度上有可能违背类的单一职责原则,因此,需要一个单独的类来做这项工作,引入CharactorFactory类,结构图如下:
1 // "CharactorFactory"
2 public class CharactorFactory
3 {
4 // Fields
5 private Hashtable charactors = new Hashtable();
6
7 // Constructor
8 public CharactorFactory()
9 {
10 charactors.Add("A", new CharactorA());
11 charactors.Add("B", new CharactorB());
12 charactors.Add("C", new CharactorC());
13 }
14
15 // Method
16 public Charactor GetCharactor(string key)
17 {
18 Charactor charactor = charactors[key] as Charactor;
19
20 if (charactor == null)
21 {
22 switch (key)
23 {
24 case "A": charactor = new CharactorA(); break;
25 case "B": charactor = new CharactorB(); break;
26 case "C": charactor = new CharactorC(); break;
27 //
28 }
29 charactors.Add(key, charactor);
30 }
31 return charactor;
32 }
33 }
到这里已经完全解决了可以共享的状态(这里很丑陋的一个地方是出现了switch语句,但这可以通过别的办法消除,为了简单期间我们先保持这种写法)。下面的工作就是处理刚才被我们剔除出去的那些不可共享的状态,因为虽然将那些状态移除了,但是Charactor对象仍然需要这些状态,被我们剥离后这些对象根本就无法工作,所以需要将这些状态外部化。首先会想到一种比较简单的解决方案就是对于不能共享的那些状态,不需要去在Charactor类中设置,而直接在客户程序代码中进行设置,类结构图如下:
1 public class Program
2 {
3 public static void Main()
4 {
5 Charactor ca = new CharactorA();
6 Charactor cb = new CharactorB();
7 Charactor cc = new CharactorC();
8
9 //显示字符
10
11 //设置字符的大小ChangeSize();
12 }
13
14 public void ChangeSize()
15 {
16 //在这里设置字符的大小
17 }
18 }
按照这样的实现思路,可以发现如果有多个客户端程序使用的话,会出现大量的重复性的逻辑,用重构的术语来说是出现了代码的坏味道,不利于代码的复用和维护;另外把这些状态和行为移到客户程序里面破坏了封装性的原则。再次转变我们的实现思路,可以确定的是这些状态仍然属于Charactor对象,所以它还是应该出现在Charactor类中,对于不同的状态可以采取在客户程序中通过参数化的方式传入。类结构图如下:
1 // "Charactor"
2 public abstract class Charactor
3 {
4 //Fields
5 protected char _symbol;
6
7 protected int _width;
8
9 protected int _height;
10
11 protected int _ascent;
12
13 protected int _descent;
14
15 protected int _pointSize;
16
17 //Method
18 public abstract void SetPointSize(int size);
19 public abstract void Display();
20 }
21
22 // "CharactorA"
23 public class CharactorA : Charactor
24 {
25 // Constructor
26 public CharactorA()
27 {
28 this._symbol = 'A';
29 this._height = 100;
30 this._width = 120;
31 this._ascent = 70;
32 this._descent = 0;
33 }
34
35 //Method
36 public override void SetPointSize(int size)
37 {
38 this._pointSize = size;
39 }
40
41 public override void Display()
42 {
43 Console.WriteLine(this._symbol +
44 "pointsize:" + this._pointSize);
45 }
46 }
47
48 // "CharactorB"
49 public class CharactorB : Charactor
50 {
51 // Constructor
52 public CharactorB()
53 {
54 this._symbol = 'B';
55 this._height = 100;
56 this._width = 140;
57 this._ascent = 72;
58 this._descent = 0;
59 }
60
61 //Method
62 public override void SetPointSize(int size)
63 {
64 this._pointSize = size;
65 }
66
67 public override void Display()
68 {
69 Console.WriteLine(this._symbol +
70 "pointsize:" + this._pointSize);
71 }
72 }
73
74 // "CharactorC"
75 public class CharactorC : Charactor
76 {
77 // Constructor
78 public CharactorC()
79 {
80 this._symbol = 'C';
81 this._height = 100;
82 this._width = 160;
83 this._ascent = 74;
84 this._descent = 0;
85 }
86
87 //Method
88 public override void SetPointSize(int size)
89 {
90 this._pointSize = size;
91 }
92
93 public override void Display()
94 {
95 Console.WriteLine(this._symbol +
96 "pointsize:" + this._pointSize);
97 }
98 }
99
100 // "CharactorFactory"
101 public class CharactorFactory
102 {
103 // Fields
104 private Hashtable charactors = new Hashtable();
105
106 // Constructor
107 public CharactorFactory()
108 {
109 charactors.Add("A", new CharactorA());
110 charactors.Add("B", new CharactorB());
111 charactors.Add("C", new CharactorC());
112 }
113
114 // Method
115 public Charactor GetCharactor(string key)
116 {
117 Charactor charactor = charactors[key] as Charactor;
118
119 if (charactor == null)
120 {
121 switch (key)
122 {
123 case "A": charactor = new CharactorA(); break;
124 case "B": charactor = new CharactorB(); break;
125 case "C": charactor = new CharactorC(); break;
126 //
127 }
128 charactors.Add(key, charactor);
129 }
130 return charactor;
131 }
132 }
133
134 public class Program
135 {
136 public static void Main()
137 {
138 CharactorFactory factory = new CharactorFactory();
139
140 // Charactor "A"
141 CharactorA ca = (CharactorA)factory.GetCharactor("A");
142 ca.SetPointSize(12);
143 ca.Display();
144
145 // Charactor "B"
146 CharactorB cb = (CharactorB)factory.GetCharactor("B");
147 ca.SetPointSize(10);
148 ca.Display();
149
150 // Charactor "C"
151 CharactorC cc = (CharactorC)factory.GetCharactor("C");
152 ca.SetPointSize(14);
153 ca.Display();
154 }
155 }
可以看到这样的实现明显优于第一种实现思路。好了,到这里我们就到到了通过Flyweight模式实现了优化资源的这样一个目的。在这个过程中,还有如下几点需要说明:
1.引入CharactorFactory是个关键,在这里创建对象已经不是new一个Charactor对象那么简单,而必须用工厂方法封装起来。
2.在这个例子中把Charactor对象作为Flyweight对象是否准确值的考虑,这里只是为了说明Flyweight模式,至于在实际应用中,哪些对象需要作为Flyweight对象是要经过很好的计算得知,而绝不是凭空臆想。
3.区分内外部状态很重要,这是享元对象能做到享元的关键所在。
到这里,其实我们的讨论还没有结束。有人可能会提出如下问题,享元对象(Charactor)在这个系统中相对于每一个内部状态而言它是唯一的,这跟单件模式有什么区别呢?这个问题已经很好回答了,那就是单件类是不能直接被实例化的,而享元类是可以被实例化的。事实上在这里面真正被设计为单件的应该是享元工厂(不是享元)类,因为如果创建很多个享元工厂的实例,那我们所做的一切努力都是白费的,并没有减少对象的个数。修改后的类结构图如下:
1 // "CharactorFactory"
2 public class CharactorFactory
3 {
4 // Fields
5 private Hashtable charactors = new Hashtable();
6
7 private CharactorFactory instance;
8 // Constructor
9 private CharactorFactory()
10 {
11 charactors.Add("A", new CharactorA());
12 charactors.Add("B", new CharactorB());
13 charactors.Add("C", new CharactorC());
14 }
15
16 // Property
17 public CharactorFactory Instance
18 {
19 get
20 {
21 if (instance != null)
22 {
23 instance = new CharactorFactory();
24 }
25 return instance;
26 }
27 }
28
29 // Method
30 public Charactor GetCharactor(string key)
31 {
32 Charactor charactor = charactors[key] as Charactor;
33
34 if (charactor == null)
35 {
36 switch (key)
37 {
38 case "A": charactor = new CharactorA(); break;
39 case "B": charactor = new CharactorB(); break;
40 case "C": charactor = new CharactorC(); break;
41 //
42 }
43 charactors.Add(key, charactor);
44 }
45 return charactor;
46 }
47 }
.NET框架中的应用:
Flyweight更多时候的时候一种底层的设计模式,在我们的实际应用程序中使用的并不是很多。在.NET中的String类型其实就是运用了Flyweight模式。可以想象,如果每次执行string s1 = “abcd”操作,都创建一个新的字符串对象的话,内存的开销会很大。所以.NET中如果第一次创建了这样的一个字符串对象s1,下次再创建相同的字符串s2时只是把它的引用指向“abcd”,这样就实现了“abcd”在内存中的共享。可以通过下面一个简单的程序来演示s1和s2的引用是否一致:
1 public class Program
2 {
3 public static void Main(string[] args)
4 {
5 string s1 = "abcd";
6 string s2 = "abcd";
7
8 Console.WriteLine(Object.ReferenceEquals(s1,s2));
9
10 Console.ReadLine();
11 }
12 }
Flyweight实现要点:
1.面向对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。
2.Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。
3.享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。另外它将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
直接与间接:
人们对复杂的软件系统常有一种处理手法,即增加一层间接层,从而对系统获得一种更为灵活、
满足特定需求的解决方案。
动机(Motivate):
在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦。
如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。
意图(Intent):
为其他对象提供一种代理以控制对这个对象的访问。 -------《设计模式》GOF
结构图(Struct):
生活中的例子:
代理模式提供一个中介以控制对这个对象的访问。一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
代码实例:
在软件系统中,我们无时不在跨越障碍,当我们访问网络上一台计算机的资源时,我们正在跨越网络障碍,当我们去访问服务器上数据库时,我们又在跨越数据库访问障碍,同时还有网络障碍。跨越这些障碍有时候是非常复杂的,如果我们更多的去关注处理这些障碍问题,可能就会忽视了本来应该关注的业务逻辑问题,Proxy模式有助于我们去解决这些问题。我们以一个简单的数学计算程序为例,这个程序只负责进行简单的加减乘除运算:
1 public class Math
2 {
3 public double Add(double x,double y)
4 {
5 return x + y;
6 }
7
8 public double Sub(double x,double y)
9 {
10 return x - y;
11 }
12
13 public double Mul(double x,double y)
14 {
15 return x * y;
16 }
17
18 public double Dev(double x,double y)
19 {
20 return x / y;
21 }
22 }
如果说这个计算程序部署在我们本地计算机上,使用就非常之简单了,我们也就不用去考虑Proxy模式了。但现在问题是这个Math类并没有部署在我们本地,而是部署在一台服务器上,也就是说Math类根本和我们的客户程序不在同一个地址空间之内,我们现在要面对的是跨越Internet这样一个网络障碍:
这时候调用Math类的方法就没有下面那么简单了,因为我们更多的还要去考虑网络的问题,对接收到的结果解包等一系列操作。
1 public class App
2 {
3 public static void Main()
4 {
5 Math math = new Math();
6
7 // 对接收到的结果数据进行解包
8
9 double addresult = math.Add(2,3);
10
11 double subresult = math.Sub(6,4);
12
13 double mulresult = math.Mul(2,3);
14
15 double devresult = math.Dev(2,3);
16 }
17 }
为了解决由于网络等障碍引起复杂性,就引出了Proxy模式,我们使用一个本地的代理来替Math类打点一切,即为我们的系统引入了一层间接层,示意图如下:
我们在MathProxy中对实现Math数据类的访问,让MathProxy来代替网络上的Math类,这样我们看到MathProxy就好像是本地Math类,它与客户程序处在了同一地址空间内:
1 public class MathProxy
2 {
3 private Math math = new Math();
4
5 // 以下的方法中,可能不仅仅是简单的调用Math类的方法
6
7 public double Add(double x,double y)
8 {
9 return math.Add(x,y);
10 }
11
12 public double Sub(double x,double y)
13 {
14 return math.Sub(x,y);
15 }
16
17 public double Mul(double x,double y)
18 {
19 return math.Mul(x,y);
20 }
21
22 public double Dev(double x,double y)
23 {
24 return math.Dev(x,y);
25 }
26 }
现在可以说我们已经实现了对Math类的代理,存在的一个问题是我们在MathProxy类中调用了原实现类Math的方法,但是Math并不一定实现了所有的方法,为了强迫Math类实现所有的方法,另一方面,为了我们更加透明的去操作对象,我们在Math类和MathProxy类的基础上加上一层抽象,即它们都实现与IMath接口,示意图如下:
1 public interface IMath
2 {
3 double Add(double x,double y);
4
5 double Sub(double x,double y);
6
7 double Mul(double x,double y);
8
9 double Dev(double x,double y);
10 }
11
12 Math类和MathProxy类分别实现IMath接口:
13
14 public class MathProxy : IMath
15 {
16 //
17 }
18
19 public class Math : IMath
20 {
21 //
22 }
此时我们在客户程序中就可以像使用Math类一样来使用MathProxy类了:
1 public class App
2 {
3 public static void Main()
4 {
5 MathProxy proxy = new MathProxy();
6
7 double addresult = proxy.Add(2,3);
8
9 double subresult = proxy.Sub(6,4);
10
11 double mulresult = proxy.Mul(2,3);
12
13 double devresult = proxy.Dev(2,3);
14 }
15 }
到这儿整个使用Proxy模式的过程就完成了,回顾前面我们的解决方案,无非是在客户程序和Math类之间加了一个间接层,这也是我们比较常见的解决问题的手段之一。另外,对于程序中的接口Imath,并不是必须的,大多数情况下,我们为了保持对对象操作的透明性,并强制实现类实现代理类所要调用的所有的方法,我们会让它们实现与同一个接口。但是我们说代理类它其实只是在一定程度上代表了原来的实现类,所以它们有时候也可以不实现于同一个接口。
代理模式实现要点:
1.远程(Remote)代理:为一个位于不同的 地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部份的网络通讯工作。由于客户可能没有意识到会启动一个耗费时间的远程调用,因此客户没有必要的思想准备。
2.虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载;代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。
3.Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
4.保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。保护代理的好处是它可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用传递给被代理的对象。
5.Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
6.防火墙(Firewall)代理:保护目标,不让恶意用户接近。
7.同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
8.智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
虚拟方法,由virtual声明,它允许在派生类中重写,也可以不重写。如果在派生类中重写时要声明override.
1 public class myclass
2 {
3 public virtual int myint()
4 {
5 ///函数体;
6 }
7 }
8 class myclass1:myclass
9 {
10 public override int myint()
11 {
12 // 函数休;
13 }
14 }
抽象方法:要求其类必须是抽象类,抽象类及抽象方法由abstract声明,抽象方法中没有函数体,必须在派生类中重写此方法,重写时也须声明override.
1 public abstract class myclass
2 {
3 public abstract int myint();
4 }
5 public class myclass1:myclass
6 {
7 public override int myint()
8 {
9 //函数体;
10 }
11 }
[.NET(C#)]
把attribute翻译成特性,用来标识类,方法
把property翻译为属性,性质,用于存取类的字段
把markup翻译成标记,tag还是翻译成标签比较好
[.NET(C#)]
.NET Framework的核心是其运行库的执行环境。
称为公共语言运行库(CLR)或.NET运行库.
通常将在CLR的控制下运行的代码称为托管代码(managed code).
在CLR执行开发的源代码之前,需要编译它们为中间语言(IL),CLR再把IL编译为平台专用的代码。
程序集(assembly)是包含编译好的,面向.NET Framework的代码的逻辑单元.
可执行代码和库代码使用相同的程序集结构.
程序集的一个重要特性是它们包含的元数据描述了对应代码中定义的类型和方法.
[.NET(C#)]
ASP页面有时显示比较慢,因为服务器端代码是解释性的不是编译的.
由于ASP代码不是结构化的所以难于维护,加上ASP不支持错误处理和语法检查。
而ASP.NET页面是结构化的。每个页面都是一个继承了.NET类System.Web.UI.Page的类。
另外ASP.NET的后台编码功能允许进一步采用结构化的方式.
页面请求是和WEB服务器在编译后高速缓存ASP.NET页面。
[.NET(C#)]
覆盖(override)和重载(overload):
覆盖是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
其实,重载的概念并不属于“面向对象编程”,
重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰
然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。
如,有两个同名函数:function func(p:integer):integer; 和function func(p:string):integer;。
那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。
对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。
也就是说,它们的地址在编译期就绑定了(早绑定),
因此,重载和多态无关!真正和多态相关的是“覆盖”。
当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,
这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。
因此,这样的函数地址是在运行期绑定的(晚邦定)。
结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!
[.NET(C#)]
C#中ref和out的区别:
方法参数上的 out 方法参数关键字使方法引用传递到方法的同一个变量。
当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
当希望方法返回多个值时,声明 out 方法非常有用。
使用 out 参数的方法仍然可以返回一个值。一个方法可以有一个以上的 out 参数。
若要使用 out 参数,必须将参数作为 out 参数显式传递到方法。out 参数的值不会传递到 out 参数。
不必初始化作为 out 参数传递的变量。然而,必须在方法返回之前为 out 参数赋值。
属性不是变量,不能作为 out 参数传递。
方法参数上的 ref 方法参数关键字使方法引用传递到方法的同一个变量。
当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
若要使用 ref 参数,必须将参数作为 ref 参数显式传递到方法。
ref 参数的值被传递到 ref 参数。 传递到 ref 参数的参数必须最先初始化。
将此方法与 out 参数相比,后者的参数在传递到 out 参数之前不必显式初始化。
属性不是变量,不能作为 ref 参数传递。
两者都是按地址传递的,使用后都将改变原来的数值。
ref可以把参数的数值传递进函数,但是out是要把参数清空
就是说你无法把一个数值从out传递进去的,out进去后,参数的数值为空,所以你必须初始化一次。
两个的区别:ref是有进有出,out是只出不进。
[.NET(C#)]
ADO和ADO.NET的区别:
ADO使用OLE DB接口并基于微软的COM技术
而ADO.NET拥有自己的ADO.NET接口并且基于微软的.NET体系架构。
ADO以Recordset存储,而ADO.NET则以DataSet表示。
Recordset看起来更像单表,如果让Recordset以多表的方式表示就必须在SQL中进行多表连接。
反之,DataSet可以是多个表的集合。ADO 的运作是一种在线方式,这意味着不论是浏览或更新数据都必须是实时的。
ADO.NET则使用离线方式,在访问数据的时候ADO.NET会利用XML制作数据的一份幅本
ADO.NET的数据库连接也只有在这段时间需要在线。
由于ADO使用COM技术,这就要求所使用的数据类型必须符合COM规范
而ADO.NET基于XML格式,数据类型更为丰富并且不需要再做COM编排导致的数据类型转换,从而提高了整体性能。
ADO.NET为.NET构架提供了优化的数据访问模型,和基于COM的ADO是完全两样的数据访问方式。
ado.net与ado存在着比较大的差异:
1.ado.net遵循更通用的原则,不那么专门面向数据库。
ado.net集合了所有允许数据处理的类。这些类表示具有典型数据库功能(如索引,排序和视图)的数据容器对象。
尽管ado.net是.net数据库应用程序的权威解决方案
但从总体设计上看,它不像ado数据模型那样以数据库为中心,这是ado.net的一大特点。
2.目前,ado.net提供了两种数据库访问类库:一种用于sql server 7.0 或更高版本
另一种用于其他所有您可能已经安装的ole db提供程序。
在这两种情况下,您分别使用不同的类,但遵循相似的命名规则。
除前缀,名称都是相同的。前一种情况前缀为sql,后一种情况则是oledb。
同时,.net框架还提供了odbc .net的数据访问模式。
odbc .net data provider是 .net 框架的增强组件,它可以访问原始的 odbc 驱动程序
就像 ole db .net data provider 可以访问原始的 ole db providers 一样。
目前它仅在下列驱动程序中测试过:
microsoft sql odbc driver,microsoft odbc driver for oracle,microsoft jet odbc driver。
3.ado.net提供了两个队形来处理从数据源中抽取数据,它们是dataset和datareader对象。
前者是记录在内存中的缓存,您可以从任何方向随意访问和修改。
后者是高度优化的对象,专为以仅向前方式滚动只读记录而设计。
4.ado.net统一了数据容器类编程接口,无论您打算编写何种应用程序,windows窗体,web窗体还是web服务
都可以通过同一组类来处理数据。
不管在后端的数据源数sql server数据库,ole db,xml文件还是一个数组
您都可以通过相同的方法和属性来滚动和处理它们的内容。
5.在ado中,xml只不过是输入和输出格式。
然而在ado.net中,xml是一种数据格式,提供了操作,组织,共享和传递数据的手段。
ADO。NET相对于ADO等主要有什么改进?
1:ado.net不依赖于ole db提供程序,而是使用.net托管提供的程序,
2:不使用com
3:不在支持动态游标和服务器端游
4:,可以断开connection而保留当前数据集可用
5:强类型转换
6:xml支持
[.NET(C#)]
new 关键字用法
(1)new 运算符 用于创建对象和调用构造函数。
(2)new 修饰符 用于向基类成员隐藏继承成员。
(3)new 约束 用于在泛型声明中约束可能用作类型参数的参数的类型。
指定泛型类声明中的任何类型参数都必须有公共的无参数构造函数。
[.NET(C#)]
C#中,string str = null 与 string str ="",说明区别。
string str =""初始化对象分配空间
而string str=null初始化对象
[.NET(C#)]
DataGrid的Datasouse可以连接什么数据源
DataTable DataView DataSet DataViewManager 任何实现IListSource接口的组件 任何实现IList接口的组件
[.NET(C#)]
概述反射和序列化
反射:公共语言运行库加载器管理应用程序域。
这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。
程序集包含模块,而模块包含类型,类型又包含成员。
反射则提供了封装程序集、模块和类型的对象。
您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。
然后,可以调用类型的方法或访问其字段和属性。
序列化:序列化是将对象状态转换为可保持或传输的格式的过程。
与序列化相对的是反序列化,它将流转换为对象。
这两个过程结合起来,可以轻松地存储和传输数据。
[.NET(C#)]
可访问性级别有哪几种
public 访问不受限制。
protected 访问仅限于包含类或从包含类派生的类型。
internal 访问仅限于当前程序集。
protected internal 访问仅限于从包含类派生的当前程序集或类型。
private 访问仅限于包含类型。
[.NET(C#)]
O/R Mapping 的原理:利用反射,配置将对象和数据库表映射。
[.NET(C#)]
sealed 修饰符有什么特点?
sealed 修饰符表示密封,用于类时,表示该类不能再被继承
不能和 abstract 同时使用,因为这两个修饰符在含义上互相排斥
用于方法和属性时,表示该方法或属性不能再被继承,必须和 override 关键字一起使用
因为使用 sealed 修饰符的方法或属性肯定是基类中相应的虚成员
通常用于实现第三方类库时不想被客户端继承,或用于没有必要再继承的类以防止滥用继承造成层次结构体系混乱
恰当的利用 sealed 修饰符也可以提高一定的运行效率,因为不用考虑继承类会重写该成员
[.NET(C#)]
详述.NET里class和struct的异同
结构与类共享几乎所有相同的语法,但结构比类受到的限制更多:
尽管结构的静态字段可以初始化,结构实例字段声明还是不能使用初始值设定项。
结构不能声明默认构造函数(没有参数的构造函数)或析构函数。
结构的副本由编译器自动创建和销毁,因此不需要使用默认构造函数和析构函数。
实际上,编译器通过为所有字段赋予默认值(参见默认值表)来实现默认构造函数。
结构不能从类或其他结构继承。
结构是值类型 -- 如果从结构创建一个对象并将该对象赋给某个变量,变量则包含结构的全部值。
复制包含结构的变量时,将复制所有数据,对新副本所做的任何修改都不会改变旧副本的数据。
由于结构不使用引用,因此结构没有标识 -- 具有相同数据的两个值类型实例是无法区分的。
C# 中的所有值类型本质上都继承自 ValueType,后者继承自 Object。
编译器可以在一个称为装箱的过程中将值类型转换为引用类型。
结构具有以下特点:
结构是值类型,而类是引用类型。
向方法传递结构时,结构是通过传值方式传递的,而不是作为引用传递的。
与类不同,结构的实例化可以不使用 new 运算符。
结构可以声明构造函数,但它们必须带参数。
一个结构不能从另一个结构或类继承,而且不能作为一个类的基。
所有结构都直接继承自 System.ValueType,后者继承自 System.Object。
结构可以实现接口。
在结构中初始化实例字段是错误的。
类与结构的差别
1. 值类型与引用类型
结构是值类型:值类型在堆栈上分配地址,所有的基类型都是结构类型
例如:int 对应System.int32 结构,string 对应 system.string 结构 ,通过使用结构可以创建更多的值类型
类是引用类型:引用类型在堆上分配地址 堆栈的执行效率要比堆的执行效率高
可是堆栈的资源有限,不适合处理大的逻辑复杂的对象。
所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑
因为结构是值类型所以结构之间的赋值可以创建新的结构,而类是引用类型,类之间的赋值只是复制引用 注:
1.虽然结构与类的类型不一样,可是他们的基类型都是对象(object),c#中所有类型的基类型都是object
2.虽然结构的初始化也使用了New 操作符可是结构对象依然分配在堆栈上而不是堆上
如果不使用“新建”(new),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用
2.继承性
结构:不能从另外一个结构或者类继承,本身也不能被继承
虽然结构没有明确的用sealed声明,可是结构是隐式的sealed .
类:完全可扩展的,除非显示的声明sealed 否则类可以继承其他类和接口,自身也能被继承
注:虽然结构不能被继承 可是结构能够继承接口,方法和类继承接口一样
例如:结构实现接口
interface IImage
{
void Paint();
}
struct Picture : IImage
{
public void Paint()
{
// painting code goes here
}
private int x, y, z; // other struct members
}
3.内部结构:
结构:
没有默认的构造函数,但是可以添加构造函数
没有析构函数
没有 abstract 和 sealed(因为不能继承)
不能有protected 修饰符
可以不使用new 初始化
在结构中初始化实例字段是错误的
类:
有默认的构造函数
有析构函数
可以使用 abstract 和 sealed
有protected 修饰符
必须使用new 初始化
[.NET(C#)]
如何选择结构还是类
1. 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些
2. 结构表示如点、矩形和颜色这样的轻量对象
例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。
在此情况下,结构的成本较低。
3. 在表现抽象和多级别的对象层次时,类是最好的选择
4. 大多数情况下该类型只是一些数据时,结构时最佳的选择
[.NET(C#)]
abstract class和interface有什么区别?
答:声明方法的存在而不去实现它的类被叫做抽像类(abstract class)
它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。
不能创建abstract 类的实例。
然而可以创建一个变量,其类型是一个抽像类,并让它指向具体子类的一个实例。
不能有抽像构造函数或抽像静态方法。
Abstract 类的子类为它们父类中的所有抽像方法提供实现,否则它们也是抽像类。
取而代之,在子类中实现该方法。
知道其行为的其它类可以在类中实现这些方法。
接口(interface)是抽像类的变体。
在接口中,所有方法都是抽像的。
多继承性可通过实现这样的接口而获得。
接口中的所有方法都是抽像的,没有一个有程序体。
接口只可以定义static final成员变量。
接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。
当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。
然后,它可以在实现了该接口的类的任何对像上调用接口的方法。
由于有抽像类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。
引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。
接口可以继承接口。
抽像类可以实现(implements)接口
抽像类是否可继承实体类(concrete class),但前提是实体类必须有明确的构造函数。
[.NET(C#)]
什么叫应用程序域?什么是托管代码?什么是强类型系统?
什么是装箱和拆箱?什么是重载?CTS、CLS和CLR分别作何解释?
应用程序域:
应用程序域为安全性、可靠性、版本控制以及卸载程序集提供了隔离边界。
应用程序域通常由运行库宿主创建,运行库宿主负责在运行应用程序之前引导公共语言运行库。
应用程序域提供了一个更安全、用途更广的处理单元,公共语言运行库可使用该单元提供应用程序之间的隔离。
应用程序域可以理解为一种轻量级进程。起到安全的作用。占用资源小。
托管代码:
使用基于公共语言运行库的语言编译器开发的代码称为托管代码;托管代码具有许多优点,
例如:跨语言集成、跨语言异常处理、增强的安全性、版本控制和部署支持、简化的组件交互模型、调试和分析服务等。
装箱和拆箱:
从值类型接口转换到引用类型:装箱。
从引用类型转换到值类型:拆箱。
装箱和拆箱使值类型能够被视为对象。
对值类型装箱将把该值类型打包到 Object 引用类型的一个实例中。
这使得值类型可以存储于垃圾回收堆中。
拆箱将从对象中提取值类型。
重载:
每个类型成员都有一个唯一的签名。
方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成。
只要签名不同,就可以在一种类型内定义具有相同名称的多种方法。
当定义两种或多种具有相同名称的方法时,就称作重载。
CTS通用类型系统 (common type system) :
一种确定公共语言运行库如何定义、使用和管理类型的规范。
CLR公共语言运行库:
.NET Framework 提供了一个称为公共语言运行库的运行时环境.
它运行代码并提供使开发过程更轻松的服务。
CLS公共语言规范:
要和其他对象完全交互,而不管这些对象是以何种语言实现的.
对象必须只向调用方公开那些它们必须与之互用的所有语言的通用功能。
为此定义了公共语言规范 (CLS),它是许多应用程序所需的一套基本语言功能。
强类型:
C# 是强类型语言;因此每个变量和对象都必须具有声明类型。
[.NET(C#)]
值类型和引用类型的区别?
基于值类型的变量直接包含值。
将一个值类型变量赋给另一个值类型变量时,将复制包含的值。
这与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身。
所有的值类型均隐式派生自 System.ValueType。
与引用类型不同,从值类型不可能派生出新的类型。但与引用类型相同的是,结构也可以实现接口。
与引用类型不同,值类型不可能包含 null 值。然而,可空类型功能允许将 null 赋给值类型。
每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。
值类型主要由两类组成:结构、枚举
结构分为以下几类:
Numeric(数值)类型、整型、浮点型、decimal、bool、用户定义的结构。
引用类型的变量又称为对象,可存储对实际数据的引用。
声明引用类型的关键字:class、interface、delegate、内置引用类型: object、string
值类型 引用类型
内存分配地点 分配在栈中 分配在堆中
效率 效率高,不需要地址转换 效率低,需要进行地址转换
内存回收 使用完后,立即回收 使用完后,不是立即回收,等待GC回收
赋值操作 进行复制,创建一个同值新对象 只是对原有对象的引用
函数参数与返回值 是对象的复制 是原有对象的引用,并不产生新的对象
类型扩展 不易扩展 容易扩展,方便与类型扩展
[.NET(C#)]
如何理解委托
委托类似于 C++ 函数指针,但它是类型安全的。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
方法不需要与委托签名精确匹配。有关更多信息,请参见协变和逆变。
C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。
[.NET(C#)]
C#中的接口和类有什么异同。
异:
不能直接实例化接口。
接口不包含方法的实现。
接口、类和结构可从多个接口继承。
但是C# 只支持单继承:类只能从一个基类继承实现。
类定义可在不同的源文件之间进行拆分。
同:
接口、类和结构可从多个接口继承。
接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。
接口可以包含事件、索引器、方法和属性。
一个类可以实现多个接口。
[.NET(C#)]
ASP.net的身份验证方式有哪些
Windows 身份验证提供程序
提供有关如何将 Windows 身份验证与 Microsoft Internet 信息服务 (IIS) 身份验证
结合使用来确保 ASP.NET 应用程序安全的信息。
Forms 身份验证提供程序
提供有关如何使用您自己的代码创建应用程序特定的登录窗体并执行身份验证的信息。
使用 Forms 身份验证的一种简便方法是使用 ASP.NET 成员资格和 ASP.NET 登录控件
它们一起提供了一种只需少量或无需代码就可以收集、验证和管理用户凭据的方法。
Passport 身份验证提供程序
提供有关由 Microsoft 提供的集中身份验证服务的信息,该服务为成员站点提供单一登录和核心配置
[.NET(C#)]
活动目录的作用
Active Directory存储了有关网络对象的信息,并且让管理员和用户能够轻松地查找和使用这些信息。
Active Directory使用了一种结构化的数据存储方式,并以此作为基础对目录信息进行合乎逻辑的分层组织。
[.NET(C#)]
解释一下UDDI、WSDL的意义及其作用
UDDI:统一描述、发现和集成协议(UDDI, Universal Description, Discovery and Integration)
是一套基于Web的、分布式的、为Web服务提供的信息注册中心的实现标准规范,
同时也包含一组使企业能将自身提供的Web服务注册以使得别的企业能够发现的访问协议的实现标准。
UDDI 提供了一组基于标准的规范用于描述和发现服务,还提供了一组基于因特网的实现。
WSDL:WSDL描述Web服务的公共接口。
这是一个基于XML的关于如何与Web服务通讯和使用的服务描述;
服务 URL 和命名空间
网络服务的类型
(可能还包括 SOAP 的函数调用,正像我所说过的,WSDL 足够自如地去描述网络服务的广泛内容)
有效函数列表
每个函数的参数
每个参数的类型
每个函数的返回值及其数据类型
[.NET(C#)]
什么是SOAP,有哪些应用。
答:SOAP(Simple Object Access Protocol )简单对象访问协议
是在分散或分布式的环境中交换信息并执行远程过程调用的协议,是一个基于XML的协议。
使用SOAP,不用考虑任何特定的传输协议(最常用的还是HTTP协议)
可以允许任何类型的对象或代码,在任何平台上,以任何一直语言相互通信。
这种相互通信采用的是XML格式的消息。
SOAP也被称作XMLP,为两个程序交换信息提供了一种标准的工作机制。
在各类机构之间通过电子方式相互协作的情况下完全有必要为此制定相应的标准。
SOAP描述了把消息捆绑为XML的工作方式。
它还说明了发送消息的发送方、消息的内容和地址以及发送消息的时间。
SOAP是Web Service的基本通信协议。
SOAP规范还定义了怎样用XML来描述程序数据(Program Data),怎样执行RPC(Remote Procedure Call)。
大多数SOAP解决方案都支持RPC-style应用程序。
SOAP还支持 Document-style应用程序(SOAP消息只包含XML文本信息)。
最后SOAP规范还定义了HTTP消息是怎样传输SOAP消息的。
MSMQ、SMTP、TCP/IP都可以做SOAP的传输协议。
SOAP 是一种轻量级协议,用于在分散型、分布式环境中交换结构化信息。
SOAP 利用 XML 技术定义一种可扩展的消息处理框架,它提供了一种可通过多种底层协议进行交换的消息结构。
这种框架的设计思想是要独立于任何一种特定的编程模型和其他特定实现的语义。
SOAP 定义了一种方法以便将 XML 消息从 A 点传送到 B 点。
为此,它提供了一种基于 XML 且具有以下特性的消息处理框架:
1) 可扩展
2) 可通过多种底层网络协议使用
3) 独立于编程模型。
[.NET(C#)]
如何部署一个ASP.net页面
VS 2005和VS 2003都有发布机制。
2003可以发布然后再复制部署。
VS2005基本上可以直接部署到对应位置。
[.NET(C#)]
GC是什么? 为什么要有GC?
答:GC是垃圾收集器。
程序员不用担心内存管理,因为垃圾收集器会自动进行管理。
要请求垃圾收集,可以调用下面的方法之一:
System.gc()
Runtime.getRuntime().gc()
不过在C#中不能直接实现Finalize方法,而是在析构函数中调用基类的Finalize()方法
[.NET(C#)]
如何理解.net中的垃圾回收机制
.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。
每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。
只要托管堆中有地址空间可用,运行库就会继续为新对象分配空间。
但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。
垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。
当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。
[.NET(C#)]
列举ASP.NET 页面之间传递值的几种方式。 并说出他们的优缺点。
答. 1).使用QueryString, 如....?id=1; response. Redirect()....
2).使用Session变量
3).使用Server.Transfer
session(viewstate) 简单,但易丢失
application 全局
cookie 简单,但可能不支持,可能被伪造
input ttype="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中的Add Web Reference菜单选项
[.NET(C#)]
微软.NET 构架下remoting和webservice两项技术的理解以及实际中的应用。
.net Remoting 的工作原理是:服务器端向客户端发送一个进程编号,一个程序域编号,以确定对象的位置。
WS主要是可利用HTTP,穿透防火墙。而Remoting可以利用TCP/IP,二进制传送提高效率。
remoting是.net中用来跨越machine,process,appdomain进行方法调用的技术
对于三成结构的程序,就可以使用remoting技术来构建.
它是分布应用的基础技术.相当于以前的DCOM
Web Service是一种构建应用程序的普通模型
并能在所有支持internet网通讯的操作系统上实施。
Web Service令基于组件的开发和web的结合达到最佳
[.NET(C#)]
启动一个线程是用run()还是start()?
答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态。
这意味着它可以由JVM调度并执行。
这并不意味着线程就会立即运行。
run()方法可以产生必须退出的标志来停止一个线程。
[.NET(C#)]
构造器Constructor是否可被override?
构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。
[.NET(C#)]
abstract的method不可同时是static,不可同时是native,不可同时是synchronized
[.NET(C#)]
final, finally, finalize的区别。
final: 修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。
因此 一个类不能既被声明为 abstract的,又被声明为final的。
将变量或方法声明为final,可以保证它们在使用中不被改变。
被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
被声明为 final的方法也同样只能使用,不能重载
finally: 在异常处理时提供 finally 块来执行任何清除操作。
如果抛出一个异常,那么相匹配的 catch 子句就会执行.
然后控制就会进入 finally 块(如果有的话)。
finalize: 方法名。
Java 技术允许使用 finalize() 方法在垃圾收集器将对像从内存中清除出去之前做必要的清理工作。
这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。
它是在 Object 类中定义的 ,因此所有的类都继承了它。
子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。
finalize() 方法是在垃圾收集器删除对像之前对这个对象调用的。
[.NET(C#)]
进程和线程的区别:
进程是系统进行资源分配和调度的单位;
线程是CPU调度和分派的单位.
一个进程可以有多个线程,这些线程共享这个进程的资源。
[.NET(C#)]
堆和栈的区别:
栈:由编译器自动分配、释放。在函数体中定义的变量通常在栈上。
堆:一般由程序员分配释放。用new、malloc等分配内存函数分配得到的就是在堆上。
[.NET(C#)]
成员变量和成员函数前加static的作用:
它们被称为常成员变量和常成员函数,又称为类成员变量和类成员函数。
分别用来反映类的状态。
比如类成员变量可以用来统计类实例的数量,类成员函数负责这种统计的动作。
[.NET(C#)]
在c#中using和new这两个关键字有什么意义:
using 引入名称空间或者使用非托管资源
new 新建实例或者隐藏父类方法
[.NET(C#)]
XML即可扩展标记语言。
eXtensible Markup Language.标记是指计算机所能理解的信息符号
通过此种标记,计算机之间可以处理包含各种信息的文章等。
如何定义这些标记,即可以选择国际通用的标记语言
比如HTML,也可以使用象XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。
XML是从SGML中简化修改出来的。它主要用到的有XML、XSL和XPath等。
[.NET(C#)]
什么是code-Behind技术。
答:ASPX,RESX和CS三个后缀的文件,这个就是代码分离.
实现了HTML代码和服务器代码分离.方便代码编写和整理.
[.NET(C#)]
XML 与 HTML 的主要区别
1. XML是区分大小写字母的,HTML不区分。
2. 在HTML中,如果上下文清楚地显示出段落或者列表键在何处结尾,
那么你可以省略</p>或者</li>之类的结束 标记。
在XML中,绝对不能省略掉结束标记。
3. 在XML中,拥有单个标记而没有匹配的结束标记的元素必须用一个 / 字符作为结尾。
这样分析器就知道不用 查找结束标记了。
4. 在XML中,属性值必须分装在引号中。在HTML中,引号是可用可不用的。
5. 在HTML中,可以拥有不带值的属性名。在XML中,所有的属性都必须带有相应的值。
[.NET(C#)]
.net的错误处理机制是什么?
答:.net错误处理机制采用try->catch->finally结构.
发生错误时,层层上抛,直到找到匹配的Catch为止。
[.NET(C#)]
Static Nested Class 和 Inner Class的不同:
Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。
而通常的内部类需要在外部类实例化后才能实例化。
[.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字段,显然不能是局部的。
无处不在的Template Method
如果你只想掌握一种设计模式,那么它就是Template Method!
动机(Motivate):
变化 -----是软件设计的永恒主题,如何管理变化带来的复杂性?设计模式的艺术性和复杂度就在于如何
分析,并发现系统中的变化和稳定点,并使用特定的设计方法来应对这种变化。
意图(Intent):
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 -------《设计模式》GOF
结构图(Struct):
适用性:
1.一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2.各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke和Johnson所描述过的“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
3.控制子类扩展。模板方法只在特定点调用“Hook”操作,这样就只允许在这些点进行扩展。
生活中的例子:
代码实现:
假如我们需要简单的读取Northwind数据库中的表的记录并显示出来。对于数据库操作,我们知道不管读取的是哪张表,它一般都应该经过如下这样的几步:
1.连接数据库(Connect)
2.执行查询命令(Select)
3.显示数据(Display)
4.断开数据库连接(Disconnect)
这些步骤是固定的,但是对于每一张具体的数据表所执行的查询却是不一样的。显然这需要一个抽象角色,给出顶级行为的实现。如下图:
Template Method模式的实现方法是从上到下,我们首先给出顶级框架DataAccessObject的实现逻辑:
1 public abstract class DataAccessObject
2
3 {
4 protected string connectionString;
5
6 protected DataSet dataSet;
7
8 protected virtual void Connect()
9
10 {
11 connectionString =
12
13 "Server=.;User Id=sa;Password=;Database=Northwind";
14
15 }
16
17 protected abstract void Select();
18
19 protected abstract void Display();
20
21
22 protected virtual void Disconnect()
23
24 {
25 connectionString = "";
26 }
27
28 // The "Template Method"
29
30 public void Run()
31
32 {
33 Connect();
34
35 Select();
36
37 Display();
38
39 Disconnect();
40 }
41 }
显然在这个顶级的框架DataAccessObject中给出了固定的轮廓,方法Run()便是模版方法,Template Method模式也由此而得名。而对于Select()和Display()这两个抽象方法则留给具体的子类去实现,如下图:
1 class Categories : DataAccessObject
2
3 {
4 protected override void Select()
5 {
6 string sql = "select CategoryName from Categories";
7
8 SqlDataAdapter dataAdapter = new SqlDataAdapter(
9
10 sql, connectionString);
11
12 dataSet = new DataSet();
13
14 dataAdapter.Fill(dataSet, "Categories");
15
16 }
17
18 protected override void Display()
19
20 {
21
22 Console.WriteLine("Categories ---- ");
23
24 DataTable dataTable = dataSet.Tables["Categories"];
25
26 foreach (DataRow row in dataTable.Rows)
27
28 {
29
30 Console.WriteLine(row["CategoryName"].ToString());
31
32 }
33
34 Console.WriteLine();
35
36 }
37 }
1 class Products : DataAccessObject
2
3 {
4 protected override void Select()
5
6 {
7 string sql = "select top 10 ProductName from Products";
8
9 SqlDataAdapter dataAdapter = new SqlDataAdapter(
10
11 sql, connectionString);
12
13 dataSet = new DataSet();
14
15 dataAdapter.Fill(dataSet, "Products");
16
17 }
18
19 protected override void Display()
20
21 {
22
23 Console.WriteLine("Products ---- ");
24
25 DataTable dataTable = dataSet.Tables["Products"];
26
27 foreach (DataRow row in dataTable.Rows)
28
29 {
30 Console.WriteLine(row["ProductName"].ToString());
31
32 }
33
34 Console.WriteLine();
35
36 }
37
38 }
再来看看客户端程序的调用,不需要再去调用每一个步骤的方法:
1 public class App
2
3 {
4 static void Main()
5 {
6
7 DataAccessObject dao;
8
9
10 dao = new Categories();
11
12 dao.Run();
13
14
15 dao = new Products();
16
17 dao.Run();
18
19 // Wait for user
20
21 Console.Read();
22
23 }
24
25 }
在上面的例子中,需要注意的是:
1.对于Connect()和Disconnect()方法实现为了virtual,而Select()和Display()方法则为abstract,这是因为如果这个方法有默认的实现,则实现为virtual,否则为abstract。
2.Run()方法作为一个模版方法,它的一个重要特征是:在基类里定义,而且不能够被派生类更改。有时候它是私有方法(private method),但实际上它经常被声明为protected。它通过调用其它的基类方法(覆写过的)来工作,但它经常是作为初始化过程的一部分被调用的,这样就没必要让客户端程序员能够直接调用它了。
3.在一开始我们提到了不管读的是哪张数据表,它们都有共同的操作步骤,即共同点。因此可以说Template Method模式的一个特征就是剥离共同点。
Template Mehtod实现要点:
1.Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
2.除了可以灵活应对子步骤的变化外,“不用调用我,让我来调用你(Don't call me ,let me call you)”的反向控制结构是Template Method的典型应用。“Don’t call me.Let me call you”是指一个父类调用一个子类的操作,而不是相反。
3.在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法,纯虚方法),但一般推荐将它们设置为protected方法。
方法参数上的 out 方法参数关键字使方法引用传递到方法的同一个变量。
当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
当希望方法返回多个值时,声明 out 方法非常有用。
使用 out 参数的方法仍然可以返回一个值。一个方法可以有一个以上的 out 参数。
若要使用 out 参数,必须将参数作为 out 参数显式传递到方法。out 参数的值不会传递到 out 参数。
不必初始化作为 out 参数传递的变量。然而,必须在方法返回之前为 out 参数赋值。
属性不是变量,不能作为 out 参数传递。
方法参数上的 ref 方法参数关键字使方法引用传递到方法的同一个变量。
当控制传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。
若要使用 ref 参数,必须将参数作为 ref 参数显式传递到方法。
ref 参数的值被传递到 ref 参数。 传递到 ref 参数的参数必须最先初始化。
将此方法与 out 参数相比,后者的参数在传递到 out 参数之前不必显式初始化。
属性不是变量,不能作为 ref 参数传递。
两者都是按地址传递的,使用后都将改变原来的数值。
ref可以把参数的数值传递进函数,但是out是要把参数清空
就是说你无法把一个数值从out传递进去的,out进去后,参数的数值为空,所以你必须初始化一次。
两个的区别:ref是有进有出,out是只出不进。
代码实例如下:
1 namespace TestOutAndRef
2 {
3 class TestApp
4 {
5
6 static void outTest(out int x, out int y)
7 {//离开这个函数前,必须对x和y赋值,否则会报错。
8 //y = x;
9 //上面这行会报错,因为使用了out后,x和y都清空了,需要重新赋值,即使调用函数前赋过值也不行
10 x = 1;
11 y = 2;
12 }
13 static void refTest(ref int x, ref int y)
14 {
15 x = 1;
16 y = x;
17 }
18
19
20 static public void OutArray(out int[] myArray)
21 {
22 // Initialize the array:
23 myArray = new int[5] { 1, 2, 3, 4, 5 };
24 }
25 public static void FillArray(ref int[] arr)
26 {
27 // Create the array on demand:
28 if (arr == null)
29 arr = new int[10];
30 // Otherwise fill the array:
31 arr[0] = 123;
32 arr[4] = 1024;
33 }
34
35
36 public static void Main()
37 {
38 //out test
39 int a,b;
40 //out使用前,变量可以不赋值
41 outTest(out a, out b);
42 Console.WriteLine("a={0};b={1}",a,b);
43 int c=11,d=22;
44 outTest(out c, out d);
45 Console.WriteLine("c={0};d={1}",c,d);
46
47 //ref test
48 int m,n;
49 //refTest(ref m, ref n);
50 //上面这行会出错,ref使用前,变量必须赋值
51
52 int o=11,p=22;
53 refTest(ref o, ref p);
54 Console.WriteLine("o={0};p={1}",o,p);
55
56
57
58 int[] myArray1; // Initialization is not required
59
60 // Pass the array to the callee using out:
61 OutArray(out myArray1);
62
63 // Display the array elements:
64 Console.WriteLine("Array1 elements are:");
65 for (int i = 0; i < myArray1.Length; i++)
66 Console.WriteLine(myArray1[i]);
67
68 // Initialize the array:
69 int[] myArray = { 1, 2, 3, 4, 5 };
70
71 // Pass the array using ref:
72 FillArray(ref myArray);
73
74 // Display the updated array:
75 Console.WriteLine("Array elements are:");
76 for (int i = 0; i < myArray.Length; i++)
77 Console.WriteLine(myArray[i]);
78 }
79 }
80
81 }
运行结果 如下:
New 关键词的三种用法 C#
(1)new 运算符 用于创建对象和调用构造函数。
(2)new 修饰符 用于隐藏基类成员的继承成员。
(3)new 约束 用于在泛型声明中约束可能用作类型参数的参数的类型。
new 运算符
1.用于创建对象和调用构造函数
例:Class_Test MyClass = new Class_Test();
2.也用于为值类型调用默认的构造函数
例:int myInt = new int();
myInt 初始化为 0,它是 int 类型的默认值。该语句的效果等同于:int myInt = 0;
3.不能重载 new 运算符。
4.如果 new 运算符分配内存失败,则它将引发 OutOfMemoryException 异常。
new 修饰符
使用 new 修饰符显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
请看下面的类:
1 public class MyClass
2
3 {
4
5 public int x;
6
7 public void Invoke() {}
8
9 }
10
在派生类中用 Invoke 名称声明成员会隐藏基类中的 Invoke 方法,即:
1 public class MyDerivedC : MyClass
2
3 {
4
5 new public void Invoke() {}
6
7 }
8
但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。
通过继承隐藏名称采用下列形式之一:
1.引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。
2.引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。
3.引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。
4.在同一成员上同时使用 new 和 override 是错误的。
注意:在不隐藏继承成员的声明中使用 new 修饰符将生成警告。
示例
在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。同时也说明了如何使用完全限定名访问基类的隐藏成员。
1 using System;
2
3 public class MyBaseC
4
5 {
6
7 public static int x = 55;
8
9 public static int y = 22;
10
11 }
12
13
14
15 public class MyDerivedC : MyBaseC
16
17 {
18
19 new public static int x = 100; // Name hiding
20
21 public static void Main()
22
23 {
24
25 // Display the overlapping value of x:
26
27 Console.WriteLine(x);
28
29
30
31 // Access the hidden value of x:
32
33 Console.WriteLine(MyBaseC.x);
34
35
36
37 // Display the unhidden member y:
38
39 Console.WriteLine(y);
40
41 }
42
43 }
44
输出
100
55
22
如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。
示例
在该例中,嵌套类 MyClass 隐藏了基类中具有相同名称的类。该例不仅说明了如何使用完全限定名访问隐藏类成员,同时也说明了如何使用 new 修饰符消除警告消息。
1 using System;
2
3 public class MyBaseC
4
5 {
6
7 public class MyClass
8
9 {
10
11 public int x = 200;
12
13 public int y;
14
15 }
16
17 }
18
19
20
21 public class MyDerivedC : MyBaseC
22
23 {
24
25 new public class MyClass // nested type hiding the base type members
26
27 {
28
29 public int x = 100;
30
31 public int y;
32
33 public int z;
34
35 }
36
37
38
39 public static void Main()
40
41 {
42
43 // Creating object from the overlapping class:
44
45 MyClass S1 = new MyClass();
46
47
48
49 // Creating object from the hidden class:
50
51 MyBaseC.MyClass S2 = new MyBaseC.MyClass();
52
53
54
55 Console.WriteLine(S1.x);
56
57 Console.WriteLine(S2.x);
58
59 }
60
61 }
62
输出
100
200
C#中,string str = null 与 string str =""的区别。
string str =""初始化对象分配空间
而string str=null初始化对象
更详细的解释
这样定义后,str1是一个空字符串,空字符串是一个特殊的字符串,只不过这个字符串的值为空,在内存中是有准确的指向的。string str2=null,这样定义后,只是定义了一个string 类的引用,str2并没有指向任何地方,在使用前如果不实例化的话,将报错。
面向对象(Object Oriented,OO)是当前计算机界关心的重点,它是90年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。 谈到面向对象,这方面的文章非常多。但是,明确地给出对象的定义或说明对象的定义的非常少——至少我现在还没有发现。其初,“面向对象”是专指在程序设计中采用封装、继承、抽象等设计方法。可是,这个定义显然不能再适合现在情况。面向对象的思想已经涉及到软件开发的各个方面。如,面向对象的分析(OOA,Object Oriented Analysis),面向对象的设计(OOD,Object Oriented Design)、以及我们经常说的面向对象的编程实现(OOP,Object Oriented Programming)。许多有关面向对象的文章都只是讲述在面向对象的开发中所需要注意的问题或所采用的比较好的设计方法。看这些文章只有真正懂得什么是对象,什么是面向对象,才能最大程度地对自己有所裨益。这一点,恐怕对初学者甚至是从事相关工作多年的人员也会对它们的概念模糊不清。
面向对象是当前计算机界关心的重点,它是90年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。
一、传统开发方法存在问题
1.软件重用性差
重用性是指同一事物不经修改或稍加修改就可多次重复使用的性质。软件重用性是软件工程追求的目标之一。
2.软件可维护性差
软件工程强调软件的可维护性,强调文档资料的重要性,规定最终的软件产品应该由完整、一致的配置成分组成。在软件开发过程中,始终强调软件的可读性、可修改性和可测试性是软件的重要的质量指标。实践证明,用传统方法开发出来的软件,维护时其费用和成本仍然很高,其原因是可修改性差,维护困难,导致可维护性差。
3.开发出的软件不能满足用户需要
用传统的结构化开发大型软件系统涉及各种不同领域的知识,在开发需求模糊或需求动态变化的系统时,所开发出的软件系统往往不能真正满足用户的需要。
用结构化方法开发的软件,其稳定性、可修改性和可重用性都比较差,这是因为结构化方法的本质是功能分解,从代表目标系统整体功能的单个处理着手,自顶向下不断把复杂的处理分解为子处理,这样一层一层的分解下去,直到仅剩下若干个容易实现的子处理功能为止,然后用相应的工具来描述各个最低层的处理。因此,结构化方法是围绕实现处理功能的“过程”来构造系统的。然而,用户需求的变化大部分是针对功能的,因此,这种变化对于基于过程的设计来说是灾难性的。用这种方法设计出来的系统结构常常是不稳定的 ,用户需求的变化往往造成系统结构的较大变化,从而需要花费很大代价才能实现这种变化。
二、面向对象的基本概念
(1)对象。
对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。
(2)对象的状态和行为。
对象具有状态,一个对象用数据值来描述它的状态。
对象还有操作,用于改变对象的状态,对象及其操作就是对象的行为。
对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中
(3)类。
具有相同或相似性质的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象。
类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。
类具有操作,它是对象的行为的抽象,用操作名和实现该操作的方法来描述。
(4)类的结构。
在客观世界中有若干类,这些类之间有一定的结构关系。通常有两种主要的结构关系,即一般--具体结构关系,整体--部分结构关系。
①一般——具体结构称为分类结构,也可以说是“或”关系,或者是“is a”关系。
②整体——部分结构称为组装结构,它们之间的关系是一种“与”关系,或者是“has a”关系。
(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年由James Rumbaugh等5人提出来的,其经典著作为“面向对象的建模与设计”。
该方法是一种新兴的面向对象的开发方法,开发工作的基础是对真实世界的对象建模,然后围绕这些对象使用分析模型来进行独立于语言的设计,面向对象的建模和设计促进了对需求的理解,有利于开发得更清晰、更容易维护的软件系统。该方法为大多数应用领域的软件开发提供了一种实际的、高效的保证,努力寻求一种问题求解的实际方法。
4.UML(Unified Modeling Language)语言
软件工程领域在1995年~1997年取得了前所未有的进展,其成果超过软件工程领域过去15年的成就总和,其中最重要的成果之一就是统一建模语言(UML)的出现。UML将是面向对象技术领域内占主导地位的标准建模语言。UML不仅统一了Booch方法、OMT方法、OOSE方法的表示方法,而且对其作了进一步的发展,最终统一为大众接受的标准建模语言。UML是一种定义良好、易于表达、功能强大且普遍适用的建模语言。它融入了软件工程领域的新思想、新方法和新技术。它的作用域不限于支持面向对象的分析与设计,还支持从需求分析开始的软件开发全过程。
五、面向对象的模型
·对象模型
对象模型表示了静态的、结构化的系统数据性质,描述了系统的静态结构,它是从客观世界实体的对象关系角度来描述,表现了对象的相互关系。该模型主要关心系统中对象的结构、属性和操作,它是分析阶段三个模型的核心,是其他两个模型的框架。
1.对象和类
(1) 对象。
对象建模的目的就是描述对象。
(2) 类。
通过将对象抽象成类,我们可以使问题抽象化,抽象增强了模型的归纳能力。
(3) 属性。
属性指的是类中对象所具有的性质(数据值)。
(4) 操作和方法。
操作是类中对象所使用的一种功能或变换。类中的各对象可以共享操作,每个操作都有一个目标对象作为其隐含参数。
方法是类的操作的实现步骤。
2.关联和链
关联是建立类之间关系的一种手段,而链则是建立对象之间关系的一种手段。
(1) 关联和链的含义。
链表示对象间的物理与概念联结,关联表示类之间的一种关系,链是关联的实例,关联是链的抽象。
(2) 角色。
角色说明类在关联中的作用,它位于关联的端点。
(3) 受限关联。
受限关联由两个类及一个限定词组成,限定词是一种特定的属性,用来有效的减少关联的重数,限定词在关联的终端对象集中说明。
限定提高了语义的精确性,增强了查询能力,在现实世界中,常常出现限定词。
(4) 关联的多重性。
关联的多重性是指类中有多少个对象与关联的类的一个对象相关。重数常描述为“一”或“多”。
图10-8表示了各种关联的重数。小实心圆表示“多个”,从零到多。小空心圆表示零或一。没有符号表示的是一对一关联。
3.类的层次结构
(1) 聚集关系。
聚集是一种“整体-部分”关系。在这种关系中,有整体类和部分类之分。聚集最重要的性质是传递性,也具有逆对称性。
聚集可以有不同层次,可以把不同分类聚集起来得到一颗简单的聚集树,聚集树是一种简单表示,比画很多线来将部分类联系起来简单得多,对象模型应该容易地反映各级层次,图10-10表示一个关于微机的多极聚集。
(2)一般化关系。
一般化关系是在保留对象差异的同时共享对象相似性的一种高度抽象方式。它是“一般---具体”的关系。一般化类称为你类,具体类又能称为子类,各子类继承了交类的性质,而各子类的一些共同性质和操作又归纳到你类中。因此,一般化关系和继承是同时存在的。一般化关系的符号表示是在类关联的连线上加一个小三角形,如图10-11
4.对象模型
(1)模板。模板是类、关联、一般化结构的逻辑组成。
(2)对象模型。
对象模型是由一个或若干个模板组成。模板将模型分为若干个便于管理的子块,在整个对象模型和类及关联的构造块之间,模板提供了一种集成的中间单元,模板中的类名及关联名是唯一的。
·动态模型
动态模型是与时间和变化有关的系统性质。该模型描述了系统的控制结构,它表示了瞬间的、行为化的系统控制
性质,它关心的是系统的控制,操作的执行顺序,它表示从对象的事件和状态的角度出发,表现了对象的相互行为。
该模型描述的系统属性是触发事件、事件序列、状态、事件与状态的组织。使用状态图作为描述工具。它涉及到事件、状态、操作等重要概念。
1.事件
事件是指定时刻发生的某件事。
2.状态
状态是对象属性值的抽象。对象的属性值按照影响对象显著行为的性质将其归并到一个状态中去。状态指明了对象
对输入事件的响应。
3.状态图
状态图是一个标准的计算机概念,他是有限自动机的图形表示,这里把状态图作为建立动态模型的图形工具。
状态图反映了状态与事件的关系。当接收一事件时,下一状态就取决于当前状态和所接收的该事件,由该事件引起的状态变化称为转换。
状态图是一种图,用结点表示状态,结点用圆圈表示;圆圈内有状态名,用箭头连线表示状态的转换,上面标记事件名,箭头方向表示转换的方向。
·功能模型
功能模型描述了系统的所有计算。功能模型指出发生了什么,动态模型确定什么时候发生,而对象模型确定发生的客体。功能模型表明一个计算如何从输入值得到输出值,它不考虑计算的次序。功能模型由多张数据流图组成。数据流图用来表示从源对象到目标对象的数据值的流向,它不包含控制信息,控制信息在动态模型中表示,同时数据流图也不表示对象中值的组织,值的组织在对象模型中表示。图10-15给出了一个窗口系统的图标显示的数据流图。
数据流图中包含有处理、数据流、动作对象和数据存储对象。
1.处理
数据流图中的处理用来改变数据值。最低层处理是纯粹的函数,一张完整的数据流图是一个高层处理。
2.数据流
数据流图中的数据流将对象的输出与处理、处理与对象的输入、处理与处理联系起来。在一个计算机中,用数据流来表示一中间数据值,数据流不能改变数据值。
3.动作对象
动作对象是一种主动对象,它通过生成或者使用数据值来驱动数据流图。
4.数据存储对象
数据流图中的数据存储是被动对象,它用来存储数据。它与动作对象不一样,数据存储本身不产生任何操作,它只响应存储和访问的要求。
六、面向对象的分析
面向对象分析的目的是对客观世界的系统进行建模。本节以上面介绍的模型概念为基础,结合“银行网络系统”的具体实例来构造客观世界问题的准确、严密的分析模型。
分析模型有三种用途:用来明确问题需求;为用户和开发人员提供明确需求;为用户和开发人员提供一个协商的基础,作为后继的设计和实现的框架。
(一) 面向对象的分析
系统分析的第一步是:陈述需求。分析者必须同用户一块工作来提炼需求,因为这样才表示了用户的真实意图,其中涉及对需求的分析及查找丢失的信息。下面以“银行网络系统”为例,用面向对象方法进行开发。
银行网络系统问题陈述:设计支持银行网络的软件,银行网络包括人工出纳站和分行共享的自动出纳机。每个分理处用分理处计算机来保存各自的帐户,处理各自的事务;各自分理处的出纳站与分理处计算机通信,出纳站录入帐户和事务数据;自动出纳机与分行计算机通信,分行计算机与拨款分理处结帐,自动出纳机与用户接口接受现金卡,与分行计算机通信完成事务,发放现金,打印收据;系统需要记录保管和安全措施;系统必须正确处理同一帐户的并发访问;每个分处理为自己的计算机准备软件,银行网络费用根据顾客和现金卡的数目分摊给各分理处。
图10-18给出银行网络系统的示意图。
(二)建立对象模型
首先标识和关联,因为它们影响了整体结构和解决问题的方法,其次是增加属性,进一步描述类和关联的基本网络,使用继承合并和组织类,最后操作增加到类中去作为构造动态模型和功能模型的副产品。
1.确定类
构造对象模型的第一步是标出来自问题域的相关的对象类,对象包括物理实体和概念。所有类在应用中都必须有意义,在问题陈述中,并非所有类都是明显给出的。有些是隐含在问题域或一般知识中的。
按图10-19所示的过程确定类
查找问题陈述中的所有名词,产生如下的暂定类。
软件 银行网络 出纳员 自动出纳机 分行
分处理 分处理计算机 帐户 事务 出纳站
事务数据 分行计算机 现金卡 用户 现金
收据 系统 顾客 费用 帐户数据
访问 安全措施 记录保管
根据下列标准,去掉不必要的类和不正确的类。
(1) 冗余类:若两个类表述了同一个信息 ,保留最富有描述能力的类。如"用户"和"顾客"就是重复的描述,因为"顾客"最富有描述性,因此保留它。
(2) 不相干的类:除掉与问题没有关系或根本无关的类。例如,摊派费用超出了银行网络的范围。
(3) 模糊类:类必须是确定的,有些暂定类边界定义模糊或范围太广,如"记录保管"就模糊类,它是"事务"中的一部分。
(4) 属性:某些名词描述的是其他对象的属性,则从暂定类中删除。如果某一性质的独立性很重要,就应该把他归属到类,而不把它作为属性。
(5) 操作:如果问题陈述中的名词有动作含义,则描述的操作就不是类。但是具有自身性质而且需要独立存在的操作应该描述成类。如我们只构造电话模型,"拨号"就是动态模型的一部分而不是类,但在电话拨号系统中,"拨号"是一个重要的类,它日期、时间、受话地点等属性。
在银行网络系统中,模糊类是"系统"、"安全措施"、"记录保管"、"银行网络"等。属于属性的有:"帐户数据"、"收据"、"现金"、"事务数据"。属于实现的如:"访问"、"软件"等。这些均应除去。
2.准备数据字典
为所有建模实体准备一个数据字典。准确描述各个类的精确含义,描述当前问题中的类的范围,包括对类的成员、用法方面的假设或限制。
3.确定关联
两个或多个类之间的相互依赖就是关联。一种依赖表示一种关联,可用各种方式来实现关联,但在分析模型中应删除实现的考虑,以便设计时更为灵活。关联常用描述性动词或动词词组来表示,其中有物理位置的表示、传导的动作、通信、所有者关系、条件的满足等。从问题陈述中抽取所有可能的关联表述,把它们记下来,但不要过早去细化这些表述。
下面是银行网络系统中所有可能的关联,大多数是直接抽取问题中的动词词组而得到的。在陈述中,有些动词词组表述的关联是不明显的。最后,还有一些关联与客观世界或人的假设有关,必须同用户一起核实这种关联,因为这种关联在问题陈述中找不到。
银行网络问题陈述中的关联:
·银行网络包括出纳站和自动出纳机;
·分行共享自动出纳机;
·分理处提供分理处计算机;
·分理处计算机保存帐户;
·分理处计算机处理帐户支付事务;
·分理处拥有出纳站;
·出纳站与分理处计算机通信;
·出纳员为帐户录入事务;
·自动出纳机接受现金卡;
·自动出纳机与用户接口;
·自动出纳机发放现金;
·自动出纳机打印收据;
·系统处理并发访问;
·分理处提供软件;
·费用分摊给分理处。
隐含的动词词组:
·分行由分理处组成;
·分理处拥有帐户;
·分行拥有分行计算机;
·系统提供记录保管;
·系统提供安全;
·顾客有现金卡。
基于问题域知识的关联:
·分理处雇佣出纳员;
·现金卡访问帐户。
使用下列标准去掉不必要和不正确的关联:
(1) 若某个类已被删除,那么与它有关的关联也必须删除或者用其它类来重新表述。在例中,我们删除了"银行网络",相关的关联也要删除。
(2) 不相干的关联或实现阶段的关联:删除所有问题域之外的关联或涉及实现结构中的关联。如"系统处理并发访问"就是一种实现的概念。
(3) 动作:关联应该描述应用域的结构性质而不是瞬时事件,因此应删除"自动出纳机接受现金卡","自动出纳机与用户接口"等。
(4) 派生关联:省略那些可以用其他关联来定义的关联。因为这种关联是冗余的。银行网络系统的初步对象图如图10-20所示。其中含有关联。
4.确定属性
属性是个体对象的性质,属性通常用修饰性的名词词组来表示.形容词常常表示具体的可枚举的属性值,属性不可能在问题陈述中完全表述出来,必须借助于应用域的知识及对客观世界的知识才可以找到它们。只考虑与具体应用直接相关的属性,不要考虑那些超出问题范围的属性。首先找出重要属性,避免那些只用于实现的属性,要为各个属性取有意义的名字。按下列标准删除不必要的和不正确的属性:
(1) 对象:若实体的独立存在比它的值重要,那么这个实体不是属性而是对象。如在邮政目录中,"城市"是一个属性,然而在人口普查中,"城市"则被看作是对象。在具体应用中,具有自身性质的实体一定是对象。
(2) 定词:若属性值取决于某种具体上下文,则可考虑把该属性重新表述为一个限定词。
(3) 名称:名称常常作为限定词而不是对象的属性,当名称不依赖于上下文关系时,名称即为一个对象属性,尤其是它不惟一时。
(4) 标识符:在考虑对象模糊性时,引入对象标识符表示,在对象模型中不列出这些对象标识符,它是隐含在对象模型中,只列出存在于应用域的属性。
(5) 内部值:若属性描述了对外不透明的对象的内部状态,则应从对象模型中删除该属性。
(6) 细化:忽略那些不可能对大多数操作有影响的属性。
5.使用继承来细化类
使用继承来共享公共机构,以次来组织类,可以用两种方式来进行。
(1)自底向上通过把现有类的共同性质一般化为父类,寻找具有相似的属性,关系或操作的类来发现继承。例如"远程事务"和"出纳事务"是类似的,可以一般化为" 事务"。有些一般化结构常常是基于客观世界边界的现有分类,只要可能,尽量使用现有概念。对称性常有助于发现某些丢失的类。
(2)自顶向下将现有的类细化为更具体的子类。具体化常常可以从应用域中明显看出来。应用域中各枚举字情况是最常见的具体化的来源。例如:菜单,可以有固定菜单,顶部菜单,弹出菜单,下拉菜单等,这就可以把菜单类具体细化为各种具体菜单的子类。当同一关联名出现多次且意义也相同时,应尽量具体化为相关联的类,例如"事务"从"出纳站"和"自动出纳机"进入,则"录入站"就是"出纳站"和"自动出纳站"的一般化。在类层次中,可以为具体的类分配属性和关联。各属性和都应分配给最一般的适合的类,有时也加上一些修正。
应用域中各枚举情况是最常见的具体化的来源。
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行源程序语句,可以用仅含一个动词和一个宾语的简单句子描述它的功能
6.把设计变动减至最小
通常,设计的质量越高,设计结果保持不变的时间也越长。即使出现必须修改设计的情况,也应该使修改的范围尽可能小。
(三)系统设计
系统设计是问题求解及建立解答的高级策略。必须制定解决问题的基本方法,系统的高层结构形式包括子系统的分解、它的固有并发性、子系统分配给硬软件、数据存储管理、资源协调、软件控制实现、人机交互接口。
1.系统设计概述
设计阶段先从高层入手,然后细化。系统设计要决定整个结构及风格,这种结构为后面设计阶段的更详细策略的设计提供了基础。
(1)系统分解。
系统中主要的组成部分称为子系统,子系统既不是一个对象也不是一个功能,而是类、关联、操作、事件和约束的集合。
(2)确定并发性。
分析模型、现实世界及硬件中不少对象均是并发的。
(3)处理器及任务分配。
各并发子系统必须分配给单个硬件单元,要么是一个一般的处理器,要么是一个具体的功能单元。
(4)数据存储管理。
系统中的内部数据和外部数据的存储管理是一项重要的任务。通常各数据存储可以将数据结构、文件、数据库组合在一起,不同数据存储要在费用、访问时间、容量及可靠性之间做出折衷考虑。
(5)全局资源的处理。
必须确定全局资源,并且制定访问全局资源的策略。
(6)选择软件控制机制。
分析模型中所有交互行为都表示为对象之间的事件。系统设计必须从多种方法中选择某种方法来实现软件的控制。
(7)人机交互接口设计。
设计中的大部分工作都与稳定的状态行为有关,但必须考虑用户使用系统的交互接口。
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"讲的便是先将一个函数登记上,然后在以后调用此函数。
2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
生活中的例子:
Command模式将一个请求封装为一个对象,从而使你可以使用不同的请求对客户进行参数化。用餐时的账单是Command模式的一个例子。服务员接受顾客的点单,把它记在账单上封装。这个点单被排队等待烹饪。注意这里的"账单"是不依赖于菜单的,它可以被不同的顾客使用,因此它可以添入不同的点单项目。
代码实现:
在众多的设计模式中,Command模式是很简单也很优雅的一种设计模式。Command模式它封装的是命令,把命令发出者的责任和命令执行者的责任分开。我们知道,一个类是一组操作和相应的一些变量的集合,现在有这样一个类Document,如下:
1 /// <summary>
2
3 /// 文档类
4
5 /// </summary>
6
7 public class Document
8
9 {
10 /**//// <summary>
11
12 /// 显示操作
13
14 /// </summary>
15
16 public void Display()
17
18 {
19 Console.WriteLine("Display");
20 }
21
22 /**//// <summary>
23
24 /// 撤销操作
25
26 /// </summary>
27
28 public void Undo()
29
30 {
31 Console.WriteLine("Undo");
32 }
33
34 /**//// <summary>
35
36 /// 恢复操作
37
38 /// </summary>
39
40 public void Redo()
41
42 {
43 Console.WriteLine("Redo");
44 }
45 }
通常客户端实现代码如下:
1 class Program
2
3 {
4 static void Main(string[] args)
5
6 {
7 Document doc = new Document();
8
9 doc.Display();
10
11 doc.Undo();
12
13 doc.Redo();
14 }
15 }
这样的使用本来是没有任何问题的,但是我们看到在这个特定的应用中,出现了Undo/Redo的操作,这时如果行为的请求者和行为的实现者之间还是呈现这样一种紧耦合,就不太合适了。可以看到,客户程序是依赖于具体Document的命令(方法)的,引入Command模式,需要对Document中的三个命令进行抽象,这是Command模式最有意思的地方,因为在我们看来Display(),Undo(),Redo()这三个方法都应该是Document所具有的,如果单独抽象出来成一个命令对象,那就是把函数层面的功能提到了类的层面,有点功能分解的味道,我觉得这正是Command模式解决这类问题的优雅之处,先对命令对象进行抽象:
1 /// <summary>
2
3 /// 抽象命令
4
5 /// </summary>
6
7 public abstract class DocumentCommand
8
9 {
10 Document _document;
11
12 public DocumentCommand(Document doc)
13
14 {
15 this._document = doc;
16 }
17
18 /**//// <summary>
19
20 /// 执行
21
22 /// </summary>
23
24 public abstract void Execute();
25
26 }
其他的具体命令类都继承于该抽象类,如下:
示意性代码如下:
1 /// <summary>
2
3 /// 显示命令
4
5 /// </summary>
6
7 public class DisplayCommand : DocumentCommand
8
9 {
10 public DisplayCommand(Document doc)
11
12 : base(doc)
13 {
14
15 }
16
17 public override void Execute()
18
19 {
20 _document.Display();
21 }
22 }
23
24
25 /**//// <summary>
26
27 /// 撤销命令
28
29 /// </summary>
30
31 public class UndoCommand : DocumentCommand
32
33 {
34 public UndoCommand(Document doc)
35
36 : base(doc)
37 {
38
39 }
40
41 public override void Execute()
42
43 {
44 _document.Undo();
45 }
46 }
47
48
49 /**//// <summary>
50
51 /// 重做命令
52
53 /// </summary>
54
55 public class RedoCommand : DocumentCommand
56
57 {
58 public RedoCommand(Document doc)
59
60 : base(doc)
61 {
62
63 }
64
65 public override void Execute()
66
67 {
68 _document.Redo();
69 }
70 }
现在还需要一个Invoker角色的类,这其实相当于一个中间角色,前面我曾经说过,使用这样的一个中间层也是我们经常使用的手法,即把A对B的依赖转换为A对C的依赖。如下:
1 /// <summary>
2
3 /// Invoker角色
4
5 /// </summary>
6
7 public class DocumentInvoker
8
9 {
10 DocumentCommand _discmd;
11
12 DocumentCommand _undcmd;
13
14 DocumentCommand _redcmd;
15
16 public DocumentInvoker(DocumentCommand discmd,DocumentCommand undcmd,DocumentCommand redcmd)
17 {
18
19 this._discmd = discmd;
20
21 this._undcmd = undcmd;
22
23 this._redcmd = redcmd;
24
25 }
26
27 public void Display()
28
29 {
30 _discmd.Execute();
31 }
32
33 public void Undo()
34
35 {
36 _undcmd.Execute();
37 }
38
39 public void Redo()
40
41 {
42 _redcmd.Execute();
43 }
44 }
45
46 现在再来看客户程序的调用代码:
47 class Program
48
49 {
50 static void Main(string[] args)
51
52 {
53
54 Document doc = new Document();
55
56
57 DocumentCommand discmd = new DisplayCommand(doc);
58
59 DocumentCommand undcmd = new UndoCommand(doc);
60
61 DocumentCommand redcmd = new RedoCommand(doc);
62
63
64 DocumentInvoker invoker = new DocumentInvoker(discmd,undcmd,redcmd);
65
66 invoker.Display();
67
68 invoker.Undo();
69
70 invoker.Redo();
71
72 }
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.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。
1 class Program
2
3 {
4 static void Main(string[] args)
5
6 {
7 Document doc = new Document();
8
9 doc.Display();
10
11 doc.Undo();
12
13 doc.Redo();
14 }
15 }
动机(Motivate):
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“ 同一种算法在多种集合对象上进行操作”提供了可能。
使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方法。
意图(Intent):
提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。-------《设计模式》GOF
结构图(Struct):
适用性:
1.访问一个聚合对象的内容而无需暴露它的内部表示。
2.支持对聚合对象的多种遍历。
3.为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。
生活中的例子:
迭代器提供一种方法顺序访问一个集合对象中各个元素,而又不需要暴露该对象的内部表示。在早期的电视机中,一个拨盘用来改变频道。当改变频道时,需要手工转动拨盘移过每一个频道,而不论这个频道是否有信号。现在的电视机,使用[后一个]和[前一个]按钮。当按下[后一个]按钮时,将切换到下一个预置的频道。想象一下在陌生的城市中的旅店中看电视。当改变频道时,重要的不是几频道,而是节目内容。如果对一个频道的节目不感兴趣,那么可以换下一个频道,而不需要知道它是几频道。
代码实现:
在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据。面向对象设计原则中有一条是类的单一职责原则,所以我们要尽可能的去分解这些职责,用不同的类去承担不同的职责。Iterator模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据。下面看一个简单的示意性例子,类结构图如下:
首先有一个抽象的聚集,所谓的聚集就是就是数据的集合,可以循环去访问它。它只有一个方法GetIterator()让子类去实现,用来获得一个迭代器对象。
1 /// <summary>
2
3 /// 抽象聚集
4
5 /// </summary>
6
7 public interface IList
8
9 {
10 IIterator GetIterator();
11 }
抽象的迭代器,它是用来访问聚集的类,封装了一些方法,用来把聚集中的数据按顺序读取出来。通常会有MoveNext()、CurrentItem()、Fisrt()、Next()等几个方法让子类去实现。
1 /// <summary>
2
3 /// 抽象迭代器
4
5 /// </summary>
6
7 public interface IIterator
8 {
9 bool MoveNext();
10
11 Object CurrentItem();
12
13 void First();
14
15 void Next();
16 }
具体的聚集,它实现了抽象聚集中的唯一的方法,同时在里面保存了一组数据,这里我们加上Length属性和GetElement()方法是为了便于访问聚集中的数据。
1 /// <summary>
2
3 /// 具体聚集
4
5 /// </summary>
6
7 public class ConcreteList : IList
8 {
9 int[] list;
10
11 public ConcreteList()
12
13 {
14 list = new int[] { 1,2,3,4,5};
15 }
16
17 public IIterator GetIterator()
18
19 {
20 return new ConcreteIterator(this);
21 }
22
23 public int Length
24
25 {
26 get { return list.Length; }
27 }
28
29 public int GetElement(int index)
30
31 {
32 return list[index];
33 }
34 }
具体迭代器,实现了抽象迭代器中的四个方法,在它的构造函数中需要接受一个具体聚集类型的参数,在这里面我们可以根据实际的情况去编写不同的迭代方式。
1 /**//// <summary>
2
3 /// 具体迭代器
4
5 /// </summary>
6
7 public class ConcreteIterator : IIterator
8
9 {
10 private ConcreteList list;
11
12 private int index;
13
14 public ConcreteIterator(ConcreteList list)
15
16 {
17 this.list = list;
18
19 index = 0;
20 }
21
22 public bool MoveNext()
23
24 {
25 if (index < list.Length)
26
27 return true;
28
29 else
30
31 return false;
32 }
33
34 public Object CurrentItem()
35
36 {
37 return list.GetElement(index) ;
38 }
39
40 public void First()
41
42 {
43 index = 0;
44 }
45
46 public void Next()
47
48 {
49 if (index < list.Length)
50
51 {
52 index++;
53 }
54 }
55 }
简单的客户端程序调用:
1 /**//// <summary>
2
3 /// 客户端程序
4
5 /// </summary>
6
7 class Program
8
9 {
10 static void Main(string[] args)
11
12 {
13 IIterator iterator;
14
15 IList list = new ConcreteList();
16
17 iterator = list.GetIterator();
18
19 while (iterator.MoveNext())
20
21 {
22 int i = (int)iterator.CurrentItem();
23 Console.WriteLine(i.ToString());
24
25 iterator.Next();
26 }
27
28 Console.Read();
29
30 }
31
32 }
.NET中Iterator中的应用:
在.NET下实现Iterator模式,对于聚集接口和迭代器接口已经存在了,其中IEnumerator扮演的就是迭代器的角色,它的实现如下:
1 public interface IEumerator
2
3 {
4 object Current
5 {
6 get;
7 }
8
9 bool MoveNext();
10
11 void Reset();
12
13 }
属性Current返回当前集合中的元素,Reset()方法恢复初始化指向的位置,MoveNext()方法返回值true表示迭代器成功前进到集合中的下一个元素,返回值false表示已经位于集合的末尾。能够提供元素遍历的集合对象,在.Net中都实现了IEnumerator接口。
IEnumerable则扮演的就是抽象聚集的角色,只有一个GetEnumerator()方法,如果集合对象需要具备跌代遍历的功能,就必须实现该接口。
1 public interface IEnumerable
2
3 {
4 IEumerator GetEnumerator();
5 }
Iterator实现要点:
1.迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
2.迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
3.迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。
Shift+Alt+Enter: 切换全屏编辑
Ctrl+B,T / Ctrl+K,K: 切换书签开关
Ctrl+B,N / Ctrl+K,N: 移动到下一书签
Ctrl+B,P: 移动到上一书签
Ctrl+B,C: 清除全部标签
Ctrl+I: 渐进式搜索
Ctrl+Shift+I: 反向渐进式搜索
Ctrl+F: 查找
Ctrl+Shift+F: 在文件中查找
F3: 查找下一个
Shift+F3: 查找上一个
Ctrl+H: 替换
Ctrl+Shift+H: 在文件中替换
Alt+F12: 查找符号(列出所有查找结果)
Ctrl+Shift+V: 剪贴板循环
Ctrl+左右箭头键: 一次可以移动一个单词
Ctrl+上下箭头键: 滚动代码屏幕,但不移动光标位置。
Ctrl+Shift+L: 删除当前行
Ctrl+M,M: 隐藏或展开当前嵌套的折叠状态
Ctrl+M,L: 将所有过程设置为相同的隐藏或展开状态
Ctrl+M,P: 停止大纲显示
Ctrl+E,S: 查看空白
Ctrl+E,W: 自动换行
Ctrl+G: 转到指定行
Shift+Alt+箭头键: 选择矩形文本
Alt+鼠标左按钮: 选择矩形文本
Ctrl+Shift+U: 全部变为大写
Ctrl+U: 全部变为小写
代码快捷键
Ctrl+J / Ctrl+K,L: 列出成员
Ctrl+Shift+空格键 / Ctrl+K,P: 参数信息
Ctrl+K,I: 快速信息
Ctrl+E,C / Ctrl+K,C: 注释选定内容
Ctrl+E,U / Ctrl+K,U: 取消选定注释内容
Ctrl+K,M: 生成方法存根
Ctrl+K,X: 插入代码段
Ctrl+K,S: 插入外侧代码
F12: 转到所调用过程或变量的定义
窗口快捷键
Ctrl+W,W: 浏览器窗口
Ctrl+W,S: 解决方案管理器
Ctrl+W,C: 类视图
Ctrl+W,E: 错误列表
Ctrl+W,O: 输出视图
trl+W,P: 属性窗口
Ctrl+W,T: 任务列表
Ctrl+W,X: 工具箱
Ctrl+W,B: 书签窗口
Ctrl+W,U: 文档大纲
Ctrl+D,B: 断点窗口
Ctrl+D,I: 即时窗口
Ctrl+Tab: 活动窗体切换
Ctrl+Shift+N: 新建项目
Ctrl+Shift+O: 打开项目
Ctrl+Shift+S: 全部保存
Shift+Alt+C: 新建类
Ctrl+Shift+A: 新建项
Shift+Alt+Enter: 切换全屏编辑
Ctrl+B,T / Ctrl+K,K: 切换书签开关
Ctrl+B,N / Ctrl+K,N: 移动到下一书签
Ctrl+B,P: 移动到上一书签
Ctrl+B,C: 清除全部标签
Ctrl+I: 渐进式搜索
Ctrl+Shift+I: 反向渐进式搜索
Ctrl+F: 查找
Ctrl+Shift+F: 在文件中查找
F3: 查找下一个
Shift+F3: 查找上一个
Ctrl+H: 替换
Ctrl+Shift+H: 在文件中替换
Alt+F12: 查找符号(列出所有查找结果)
Ctrl+Shift+V: 剪贴板循环
Ctrl+左右箭头键: 一次可以移动一个单词
Ctrl+上下箭头键: 滚动代码屏幕,但不移动光标位置。
Ctrl+Shift+L: 删除当前行
Ctrl+M,M: 隐藏或展开当前嵌套的折叠状态
Ctrl+M,L: 将所有过程设置为相同的隐藏或展开状态
Ctrl+M,P: 停止大纲显示
Ctrl+E,S: 查看空白
Ctrl+E,W: 自动换行
Ctrl+G: 转到指定行
Shift+Alt+箭头键: 选择矩形文本
Alt+鼠标左按钮: 选择矩形文本
Ctrl+Shift+U: 全部变为大写
Ctrl+U: 全部变为小写
动机(Motivate):
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
意图(Intent):
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
-------《设计模式》GOF
结构图(Struct):
适用性:
1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
生活中的例子:
观察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。在ATM取款,当取款成功后,以手机、邮件等方式进行通知。
代码实现:
1 public class BankAccount
2 {
3 Emailer emailer; //强信赖关系
4 Mobile phoneNumber; //强信赖关系
5
6 private double _money;
7
8 public Emailer Emailer
9 {
10 get { return emailer; }
11 set { this.emailer = value; }
12 }
13 public Mobile PhoneNumber
14 {
15 get { return phoneNumber; }
16 set { this.phoneNumber = value; }
17 }
18 public double Money
19 {
20 get { return _money; }
21 set { this._money = value; }
22 }
23
24 public void WithDraw()
25 {
26 emailer.SendEmail(this);
27 phoneNumber.SendNotification(this);
28 }
29
30 }
1 public class Emailer
2 {
3 private string _emailer;
4 public Emailer(string emailer)
5 {
6 this._emailer = emailer;
7 }
8 public void SendEmail(BankAccount ba)
9 {
10 //..
11 Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emailer, ba.Money);
12 }
13 }
1 public class Mobile
2 {
3 private long _phoneNumber;
4 public Mobile(long phoneNumber)
5 {
6 this._phoneNumber = phoneNumber;
7 }
8 public void SendNotification(BankAccount ba)
9 {
10 Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber, ba.Money);
11 }
12 }
此时简单的客户端调用如下:
1 class Test
2 {
3 static void Main(string[] args)
4 {
5 BankAccount ba = new BankAccount();
6 Emailer emailer = new Emailer("abcdwxc@163.com");
7 Mobile mobile = new Mobile(13901234567);
8 ba.Emailer = emailer;
9 ba.PhoneNumber = mobile;
10 ba.Money = 2000;
11 ba.WithDraw();
12 }
13 }
运行结果如下:
由此可见程序可以正常运行,但请注意BandAccount和Emailer及Mobile之间形成了一种双向的依赖关系,即BankAccount调用了Emailer及Mobile的方法,而Emailer及Mobile调用了BnadAccount类的属性。如果有其中一个类变化,有可能会引起另一个的变化。如果又需添加一种新的通知方式,就得在BankAccount的WithDraw()方法中增加对该中通知方式的调用。
显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。
由此我们由左图转换到右图。
实例代码如下:
1 public interface IObserverAccount
2 {
3 void Update(BankAccount ba);
4 }
1 public class BankAccount
2 {
3 IObserverAccount emailer; //依赖于接口
4 IObserverAccount phoneNumber; //依赖于接口
5
6 private double _money;
7
8 public IObserverAccount Emailer
9 {
10 get { return emailer; }
11 set { this.emailer = value; }
12 }
13 public IObserverAccount PhoneNumber
14 {
15 get { return phoneNumber; }
16 set { this.phoneNumber = value; }
17 }
18 public double Money
19 {
20 get { return _money; }
21 set { this._money = value; }
22 }
23
24 public void WithDraw()
25 {
26 emailer.Update(this);
27 phoneNumber.Update(this);
28 }
29
30 }
1 public class Emailer : IObserverAccount
2 {
3 private string _emailer;
4 public Emailer(string emailer)
5 {
6 this._emailer = emailer;
7 }
8 public void Update(BankAccount ba)
9 {
10 //..
11 Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emailer, ba.Money);
12 }
13 }
1 public class Mobile : IObserverAccount
2 {
3 private long _phoneNumber;
4 public Mobile(long phoneNumber)
5 {
6 this._phoneNumber = phoneNumber;
7 }
8 public void Update(BankAccount ba)
9 {
10 Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber, ba.Money);
11 }
12 }
客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。
1 public class BankAccount
2 {
3 private List<IObserverAccount> Observers = new List<IObserverAccount>();
4
5
6 private double _money;
7
8 public double Money
9 {
10 get { return _money; }
11 set { this._money = value; }
12 }
13
14 public void WithDraw()
15 {
16 foreach (IObserverAccount ob in Observers)
17 {
18 ob.Update(this);
19
20 }
21 }
22 public void AddObserver(IObserverAccount observer)
23 {
24 Observers.Add(observer);
25 }
26 public void RemoverObserver(IObserverAccount observer)
27 {
28 Observers.Remove(observer);
29 }
30
31 }
此时客户端代码如下:
1 class Test
2 {
3 static void Main(string[] args)
4 {
5 BankAccount ba = new BankAccount();
6 IObserverAccount emailer = new Emailer("abcdwxc@163.com");
7 IObserverAccount mobile = new Mobile(13901234567);
8
9 ba.Money = 2000;
10 ba.AddObserver(emailer);
11 ba.AddObserver(mobile);
12
13 ba.WithDraw();
14 }
15 }
走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:
1 public abstract class Subject
2 {
3 private List<IObserverAccount> Observers = new List<IObserverAccount>();
4
5 private double _money;
6 public Subject(double money)
7 {
8 this._money = money;
9 }
10
11 public double Money
12 {
13 get { return _money; }
14 }
15
16 public void WithDraw()
17 {
18 foreach (IObserverAccount ob in Observers)
19 {
20 ob.Update(this);
21
22 }
23 }
24 public void AddObserver(IObserverAccount observer)
25 {
26 Observers.Add(observer);
27 }
28 public void RemoverObserver(IObserverAccount observer)
29 {
30 Observers.Remove(observer);
31 }
32
33 }
1 public interface IObserverAccount
2 {
3 void Update(Subject subject);
4 }
1 public class BankAccount : Subject
2 {
3 public BankAccount(double money)
4 : base(money)
5 { }
6
7 }
1 public class Emailer : IObserverAccount
2 {
3 private string _emalier;
4 public Emailer(string emailer )
5 {
6 this._emalier = emailer;
7 }
8 public void Update(Subject subject)
9 {
10 Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emalier, subject.Money);
11 }
12 }
1 public class Mobile : IObserverAccount
2 {
3 private long _phoneNumber;
4 public Mobile(long phoneNumber)
5 {
6 this._phoneNumber = phoneNumber;
7 }
8 public void Update(Subject subject)
9 {
10 Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber, subject.Money);
11 }
12 }
此时客户端实现如下:
1 class Test
2 {
3 static void Main(string[] args)
4 {
5 Subject subject = new BankAccount(2000);
6 subject.AddObserver(new Emailer("abcdwxc@163.com"));
7 subject.AddObserver(new Mobile(13901234567));
8
9 subject.WithDraw();
10 }
11 }
推模式与拉模式
对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。
拉模式实现如下:
1 public abstract class Subject
2 {
3 private List<IObserverAccount> Observers = new List<IObserverAccount>();
4
5
6 private double _money;
7
8 public double Money
9 {
10 get { return _money; }
11 }
12 public Subject(double money)
13 {
14 this._money = money;
15 }
16 public void WithDraw()
17 {
18 foreach (IObserverAccount ob in Observers)
19 {
20 ob.Update();
21
22 }
23 }
24 public void AddObserver(IObserverAccount observer)
25 {
26 Observers.Add(observer);
27 }
28 public void RemoverObserver(IObserverAccount observer)
29 {
30 Observers.Remove(observer);
31 }
32
33 }
1 public interface IObserverAccount
2 {
3 void Update();
4 }
1 public class BankAccount :Subject
2 {
3 public BankAccount(double money)
4 : base(money)
5 { }
6
7 }
1 public class Emailer : IObserverAccount
2 {
3 private string _emalier;
4 private Subject _subject;
5 public Emailer(string emailer,Subject subject)
6 {
7 this._emalier = emailer;
8 this._subject = subject;
9 }
10 public void Update()
11 {
12 //..
13 Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emalier,_subject.Money);
14 }
15 }
1 public class Mobile : IObserverAccount
2 {
3 private long _phoneNumber;
4 private Subject _subject;
5 public Mobile(long phoneNumber,Subject subject)
6 {
7 this._phoneNumber = phoneNumber;
8 this._subject = subject;
9 }
10 public void Update()
11 {
12 Console.WriteLine("Notified :Phone number is {0} You withdraw {1:C} ", _phoneNumber,_subject.Money);
13 }
14 }
此时客户端调用如下:
1 class Test
2 {
3 static void Main(string[] args)
4 {
5 Subject subject= new BankAccount(2000);
6 subject.AddObserver(new Emailer("abcdwxc@163.com",subject));
7 subject.AddObserver(new Mobile(13901234567,subject));
8
9 subject.WithDraw();
10 }
11 }
.NET中Observer实现:
用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。
1 public class Subject
2 {
3 public event NotifyEventHandler NotifyEvent;
4
5 private double _money;
6 public Subject(double money)
7 {
8 this._money = money;
9 }
10
11 public double Money
12 {
13 get { return _money; }
14 }
15
16 public void WithDraw()
17 {
18 OnNotifyChange();
19 }
20 public void OnNotifyChange()
21 {
22 if (NotifyEvent != null)
23 {
24 NotifyEvent(this);
25 }
26
27 }
28
29 }
1 public class Emailer
2 {
3 private string _emalier;
4 public Emailer(string emailer)
5 {
6 this._emalier = emailer;
7 }
8 public void Update(object obj)
9 {
10 if (obj is Subject)
11 {
12 Subject subject = (Subject)obj;
13
14 Console.WriteLine("Notified : Emailer is {0}, You withdraw {1:C} ", _emalier, subject.Money);
15 }
16 }
17 }
public delegate void NotifyEventHandler(object sender);
客户端调用如下:
1 class Test
2 {
3 static void Main(string[] args)
4 {
5 Subject subject = new Subject(2000);
6 Emailer emailer = new Emailer("abcdwxc@163.com");
7 subject.NotifyEvent += new NotifyEventHandler(emailer.Update);
8
9
10 subject.WithDraw();
11 }
12 }
Observer实现要点:
1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。
3.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。
动机(Motivate):
在软件构建过程中,如果某一特定领域的问题比较复杂,类似的模式不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。
在这种情况下,将特定领域的问题表达为某种文法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
意图(Intent):
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
结构图(Struct):
生活中的例子:
适用性:
1.当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
而当存在以下情况时该模式效果最好:
2.该文法简单对于复杂的文法,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达工,这样可以节省空间而且还可能节省时间。
3.效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种
形式。例如:正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍
是有用的。
代码实现:
客户端代码如下:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 string roman = "五千四百三十二"; //5432
6 Context context = new Context(roman);
7
8 //Build the 'parse tree'
9 ArrayList tree = new ArrayList();
10 tree.Add(new OneExpression());
11 tree.Add(new TenExpression());
12 tree.Add(new HundredExpression());
13 tree.Add(new ThousandExpression());
14
15 //Interpret
16 foreach (Expression exp in tree)
17 {
18 exp.Interpret(context);
19 }
20 Console.WriteLine("{0} = {1}", roman, context.Data);
21 //Wait for user
22 Console.Read();
23 }
24 }
创建一个抽象类Expression,来描述共同的操作。
1 public abstract class Expression
2 {
3 protected Dictionary<string, int> table = new Dictionary<string, int>(9);
4 public Expression()
5 {
6 table.Add("一", 1);
7 table.Add("二", 2);
8 table.Add("三", 3);
9 table.Add("四", 4);
10 table.Add("五", 5);
11 table.Add("六", 6);
12 table.Add("七", 7);
13 table.Add("八", 8);
14 table.Add("九", 9);
15 }
16 public virtual void Interpret(Context context)
17 {
18 if(context.Statement.Length==0)
19 {
20 return;
21 }
22 foreach(string key in table.Keys)
23 {
24 int value=table[key];
25 if(context.Statement.EndsWith(key + GetPostifix()))
26 {
27 context.Data +=value*Multiplier();
28 context.Statement = context.Statement.Substring(0,context.Statement.Length- this.GetLength());
29 }
30
31 if(context.Statement.EndsWith("零"))
32 {
33 context.Statement = context.Statement.Substring(0, context.Statement.Length - 1);
34 }
35 if (context.Statement.Length == 0)
36 {
37 return;
38 }
39 }
40 }
41
42 public abstract string GetPostifix();
43 public abstract int Multiplier();
44 public virtual int GetLength()
45 {
46 return this.GetPostifix().Length + 1;
47 }
48 }
然后创建一个公共类Context,定义一些全局信息。
1 public class Context
2 {
3 private string statement;
4 private int data;
5
6 //Constructor
7 public Context(string statement)
8 {
9 this.statement = statement;
10 }
11 //Properties
12 public string Statement
13 {
14 get { return statement; }
15 set { statement = value; }
16 }
17 public int Data
18 {
19 get { return data; }
20 set { data = value; }
21 }
22 }
1 public class OneExpression : Expression
2 {
3 public override string GetPostifix()
4 {
5 return "";
6 }
7 public override int Multiplier() { return 1; }
8 public override int GetLength()
9 {
10 return 1;
11 }
12 }
13 public class TenExpression : Expression
14 {
15 public override string GetPostifix()
16 {
17 return "十";
18 }
19 public override int Multiplier() { return 10; }
20 public override int GetLength()
21 {
22 return 2;
23 }
24 }
25 public class HundredExpression : Expression
26 {
27 public override string GetPostifix()
28 {
29 return "百";
30 }
31 public override int Multiplier() { return 100; }
32 public override int GetLength()
33 {
34 return 2;
35 }
36 }
37 public class ThousandExpression : Expression
38 {
39 public override string GetPostifix()
40 {
41 return "千";
42 }
43 public override int Multiplier() { return 1000; }
44 public override int GetLength()
45 {
46 return 2;
47 }
48 }
Interpreter实现要点:
Interpreter模式的应用场合是interpreter模式应用中的难点,只有满足"业务规则频繁变化,且类似的模式不断重复出现,并且容易抽象为语法规则的问题"才适合使用Interpreter模式。
使用Interpreter模式来表示文法规则,从而可以使用面向对象技巧来方便地“扩展”文法。
Interpreter模式比较适合简单的文法表示,对于复杂的文法表示,Interpreter模式会产生比较大的类层次结构,需要求助于语法分析生成器这样的标准工具。
依赖关系的转化:
动机(Motivate):
在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。
在这种情况下,我们可使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。
意图(Intent):
用一个中介对象来封装一系列对象交互。中介者使各对象不需要相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 ------《设计模式》GOF
结构图(Struct):
适用性:
1.一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
2.一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
3.想定制一个分布在多个类中的行为,而又不想生成太多的子类。
代码实现:
1 //Mediator
2 abstract class AbstractChatroom
3 {
4 public abstract void Register(Participant participant);
5 public abstract void Send(string from, string to, string message);
6 }
1 //ConcreteMediator
2 class Chatroom :AbstractChatroom
3 {
4 private Hashtable participants = new Hashtable();
5 public override void Register(Participant participant)
6 {
7 if (participants[participant.Name] == null)
8 {
9 participants[participant.Name]=participant;
10 }
11 participant.Chatroom = this;
12 }
13 public override void Send(string from, string to, string message)
14 {
15 Participant pto = (Participant)participants[to];
16 if (pto != null)
17 {
18 pto.Receive(from, message);
19 }
20 }
21 }
1 //AbstractColleague
2 class Participant
3 {
4 private Chatroom chatroom;
5 private string name;
6
7 //Constructor
8 public Participant(string name)
9 {
10 this.name = name;
11 }
12 //Properties
13 public string Name
14 {
15 get { return name; }
16 }
17 public Chatroom Chatroom
18 {
19 set { chatroom = value; }
20 get { return chatroom; }
21
22 }
23 public void Send(string to, string message)
24 {
25 chatroom.Send(name, to, message);
26 }
27 public virtual void Receive(string from, string message)
28 {
29 Console.WriteLine("{0} to {1}:'{2}'", from, name, message);
30 }
31 }
1 //ConcreteColleaguel
2 class Beatle :Participant
3 {
4 //Constructor
5 public Beatle(string name)
6 : base(name)
7 { }
8 public override void Receive(string from, string message)
9 {
10 Console.Write("To a Beatle: ");
11 base.Receive(from, message);
12 }
13 }
1 //ConcreteColleague2
2 class NonBeatle :Participant
3 {
4 //Constructor
5 public NonBeatle(string name)
6 : base(name)
7 { }
8 public override void Receive(string from, string message)
9 {
10 Console.Write("To a non-Beatle:");
11 base.Receive(from, message);
12 }
13 }
客户端调用如下:
1 static void Main(string[] args)
2 {
3 //create chatroom
4 Chatroom chatroom = new Chatroom();
5 //Create participants and register them
6 Participant George = new Beatle("George");
7 Participant Paul = new Beatle("Paul");
8 Participant Ringo = new Beatle("Ringo");
9 Participant John = new Beatle("John");
10 Participant Yoko = new Beatle("Yoko");
11 chatroom.Register(George);
12 chatroom.Register(Paul);
13 chatroom.Register(Ringo);
14 chatroom.Register(John);
15 chatroom.Register(Yoko);
16
17 //chatting participants
18 Yoko.Send("John", "Hi John");
19 Paul.Send("Ringo", "All you need is love");
20 Ringo.Send("George", "My sweet Lord");
21 Paul.Send("John", "Can't buy me love");
22 John.Send("Yoko", "My sweet love");
23 }
运行结果如下:
Mediator实现要点:
1.将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关系”为多“个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化。
2.随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。 这时候可以对Mediator对象进行分解处理。
3.Facade模式是解耦系统外到系统内(单向)的对象关系关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。
职责链模式(Chain of Responsibility Pattern)
动机(Motivate):
在软件构建过程中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接受者,如果显示指定,将必不可少地带来请求发送者与接受者的紧耦合。
如何使请求的发送者不需要指定具体的接受者?让请求的接受者自己在运行时决定来处理请求,从而使两者解耦。
意图(Intent):
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
结构图(Struct):
适用性:
1.有多个对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
2.你想在不明确接收者的情况下,向多个对象中的一个提交一个请求。
3.可处理一个请求的对象集合应被动态指定。
生活中的例子:
代码实现:
1 //Handler
2 abstract class Approver
3 {
4 protected Approver successor;
5 public void SetSuccessor(Approver successor)
6 {
7 this.successor = successor;
8 }
9 public abstract void ProcessRequest(Purchase purchase);
10
11 }
12
13
14
1 //ConcreteHandler
2 class Director :Approver
3 {
4 public override void ProcessRequest(Purchase purchase)
5 {
6 if (purchase.Amount < 10000.0)
7 {
8 Console.WriteLine("{0} approved request# {1}", this.GetType().Name, purchase.Number);
9
10 }
11 else if(successor !=null)
12 {
13 successor.ProcessRequest(purchase);
14 }
15 }
16 }
1
2
3
4 class VicePresident :Approver
5 {
6 public override void ProcessRequest(Purchase purchase)
7 {
8 if (purchase.Amount < 25000.0)
9 {
10 Console.WriteLine("{0} approved request# {1}", this.GetType().Name, purchase.Number);
11
12 }
13 else if (successor != null)
14 {
15 successor.ProcessRequest(purchase);
16 }
17 }
18 }
1
2 class President :Approver
3 {
4 public override void ProcessRequest(Purchase purchase)
5 {
6 if (purchase.Amount < 100000.0)
7 {
8 Console.WriteLine("{0} approved request# {1}", this.GetType().Name, purchase.Number);
9
10 }
11 else
12 {
13 Console.WriteLine("Request! {0} requires an executive meeting!", purchase.Number);
14 }
15 }
16 }
1
2
3 //Request details
4 class Purchase
5 {
6 private int number;
7 private double amount;
8 private string purpose;
9
10 //Constructor
11 public Purchase(int number, double amount, string purpose)
12 {
13 this.number = number;
14 this.amount = amount;
15 this.purpose = purpose;
16 }
17 //Properties
18 public double Amount
19 {
20 get { return amount; }
21 set { amount = value; }
22 }
23 public string Purpose
24 {
25 get { return purpose; }
26 set { purpose = value; }
27 }
28 public int Number
29 {
30 get { return number; }
31 set { number = value; }
32 }
33 }
客户端调用如下:
1
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 //Setup Chain of Responsibility
7 Director Larry = new Director();
8 VicePresident Sam = new VicePresident();
9 President Tammy = new President();
10 Larry.SetSuccessor(Sam);
11 Sam.SetSuccessor(Tammy);
12
13 //Generate and process purchase requests
14 Purchase p = new Purchase(1034, 350.00, "Supplies");
15 Larry.ProcessRequest(p);
16
17 p = new Purchase(2035, 32590.10, "Project X");
18 Larry.ProcessRequest(p);
19
20 p = new Purchase(2036, 122100.00, "Project Y");
21 Larry.ProcessRequest(p);
22
23 //Wait for user
24 Console.Read();
25 }
26 }
运行结果如下:
Chain of Responsibility实现要点:
1.Chain of Responsibility模式的应用场合在于“一个请求可能有多个接受者,但是最后真正的接受者只胡一个”,只有这时候请求发送者与接受者的耦合才胡可能出现“变化脆弱”的症状,职责链的目的就是将二者解耦,从而更好地应对变化。
2.应用了Chain of Responsibility模式后,对象的职责分派将更具灵活性。我们可以在运行时动态添加/修改请求的处理职责。
3.如果请求传递到职责链的未尾仍得不到处理,应该有一个合理的缺省机制。这也是每一个接受对象的责任,而不是发出请求的对象的责任。
对象状态的回溯:
对象状态的变化无端,如何回溯/恢复对象在某个点的状态?
动机:
在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
意图:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后可以将该对象恢复到原先保存的状态。
适用性:
1.必须保存一个对象某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
2.如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
代码实现:
1 class Memento
2 {
3 private string name;
4 private string phone;
5 private double budget;
6
7 //Constructor
8 public Memento(string name, string phone, double budget)
9 {
10 this.name = name;
11 this.phone = phone;
12 this.budget = budget;
13 }
14 //Properties
15 public string Name
16 {
17 get { return name; }
18 set { name = value; }
19 }
20 public string Phone
21 {
22 get { return phone; }
23 set { phone = value; }
24 }
25 public double Budget
26 {
27 get { return budget; }
28 set { budget = value; }
29 }
30 }
1 class ProspectMemory
2 {
3 private Memento memento;
4
5 //Property
6 public Memento Memento
7 {
8 set { memento = value; }
9 get { return memento; }
10 }
11 }
1 //Originator
2 class SalesProspect
3 {
4 private string name;
5 private string phone;
6 private double budget;
7
8 //Properties
9 public string Name
10 {
11 get { return name; }
12 set { name = value; Console.WriteLine("Name:" + name); }
13 }
14 public string Phone
15 {
16 get { return phone; }
17 set { phone = value; Console.WriteLine("Phone:" + phone); }
18 }
19 public double Budget
20 {
21 get { return Budget; }
22 set { budget = value; Console.WriteLine("Budget:" + budget); }
23 }
24 public Memento SaveMemento()
25 {
26 Console.WriteLine("\nSaving state --\n");
27 return new Memento(name, phone, budget);
28 }
29 public void RestoreMemento(Memento memento)
30 {
31 Console.WriteLine("\nRestoring state --\n");
32 this.Name = memento.Name;
33 this.Phone = memento.Phone;
34 this.Budget = memento.Budget;
35 }
36 }
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 SalesProspect s = new SalesProspect();
6 s.Name = "xiaoming";
7 s.Phone = "(010)65236523";
8 s.Budget = 28000.0;
9
10 //Store internal state
11 ProspectMemory m = new ProspectMemory();
12 m.Memento = s.SaveMemento();
13
14 //Continue changing originator
15 s.Name = "deke";
16 s.Phone = "(029)85423657";
17 s.Budget = 80000.0;
18
19 //Restore saved state
20 s.RestoreMemento(m.Memento);
21
22 //Wait for user
23 Console.Read();
24 }
25 }
Memento需要注意的几个要点:
1.备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。Memento模式适用于“由原发器管理,却又必须存储在原发器之外的信息”。
2.在实现Memento模式中,要防止原发器以外的对象访问备忘录对象。备忘录对象有两个接口,一个为原发器的宽接口;一个为其他对象使用的窄接口。
3.在实现Memento模式时,要考虑拷贝对象状态的效率问题,如果对象开销比较大,可以采用某种增量式改变来改进Memento模式。
算法与对象的耦合:
对象可能经常需要使用多种不同的算法,但是如果变化频繁,会将类型变得脆弱...
动机:
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
意图:
定义一系统的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
--------《设计模式》GOF
适用性:
1.许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
2.需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时[H087],可以使用策略模式。
3.算法使用客户不应该知道数据。可使用策略模式以避免暴露复杂的,与算法相关的数据结构。
4.一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
代码实现:
1 enum SortType
2 {
3 QuickSort,
4 ShellSort,
5 MergeSort,
6 }
1 class Sort
2 {
3 public void SortList(SortType s)
4 {
5 if (s == SortType.QuickSort)
6 {
7 ProcessA();
8 }
9 else if (s == SortType.ShellSort)
10 {
11 ProcessB();
12 }
13 else if (s == SortType.MergeSort)
14 {
15 ProcessC();
16 }
17 Console.WriteLine();
18 }
19
20 protected void ProcessA()
21 {
22 Console.WriteLine("QuickSort List");
23 }
24 protected void ProcessB()
25 {
26 Console.WriteLine("ShellSort List");
27 }
28 protected void ProcessC()
29 {
30 Console.WriteLine("MergeSort List");
31 }
32 }
客户端调用:
1 class Test
2 {
3 public static void Main()
4 {
5 Sort sort = new Sort();
6 sort.SortList(SortType.QuickSort);
7 sort.SortList(SortType.ShellSort);
8 sort.SortList(SortType.MergeSort);
9 }
10 }
由此可见,由于客户端新增调用方式的选择,就会修改SortType及Sort里的判断语句。在类Sort中会增加if语句的判断,用敏捷软件开发的语言说,你应该闻到了代码的臭味道了,也就是设计模式中说的存在了变化的地方。
重构以上代码,增加一层中间层来处理变化。类结构如下:
1 //Stategy 表达抽象算法
2 abstract class SortStrategy
3 {
4 public abstract void Sort(ArrayList list);
5 }
1 //ConcreteStrategy
2 class ShellSort :SortStrategy
3 {
4 public override void Sort(System.Collections.ArrayList list)
5 {
6 list.Sort(); //no-implement
7 Console.WriteLine("ShellSorted List");
8
9 }
10 }
1 //ConcreteStrategy
2 class MergeSort :SortStrategy
3 {
4 public override void Sort(System.Collections.ArrayList list)
5 {
6 list.Sort(); //no-implement
7 Console.WriteLine("MergeSort List ");
8 }
9 }
1 //ConcreteStrategy
2 class QuickSort :SortStrategy
3 {
4 public override void Sort(System.Collections.ArrayList list)
5 {
6 list.Sort(); //Default is Quicksort
7 Console.WriteLine("QuickSorted List");
8 }
9 }
1 //Context
2 class SortdList
3 {
4 private ArrayList list = new ArrayList();
5 private SortStrategy sortstrategy; //对象组合
6 public void SetSortStrategy(SortStrategy sortstrategy)
7 {
8 this.sortstrategy = sortstrategy;
9 }
10 public void Add(string name)
11 {
12 list.Add(name);
13 }
14 public void Sort()
15 {
16 sortstrategy.Sort(list);
17 //Display results
18 foreach (string name in list)
19 {
20 Console.WriteLine(" " + name);
21 }
22 Console.WriteLine();
23 }
24 }
客户端代码如下:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 //Two contexts following different strategies
6 SortdList studentRecords = new SortdList();
7
8 studentRecords.Add("Satu");
9 studentRecords.Add("Jim");
10 studentRecords.Add("Palo");
11 studentRecords.Add("Terry");
12 studentRecords.Add("Annaro");
13
14 studentRecords.SetSortStrategy(new QuickSort());
15 studentRecords.Sort();
16
17 studentRecords.SetSortStrategy(new ShellSort());
18 studentRecords.Sort();
19
20 studentRecords.SetSortStrategy(new MergeSort());
21 studentRecords.Sort();
22
23 Console.Read();
24 }
25 }
由此可见,更好地满足开放封闭原则。
Strategy模式的几个要点:
1.Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。所谓封装算法,支持算法的变化。
2.Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
3.与State类似,如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。
类层次结构的变化:
类层次结构中可能经常由于引入新的操作,从而将类型变得脆弱...
动机:
在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
意图:
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这引起元素的新操作。
结构:
适用性:
1.一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作"污染"这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
3.定义对象结构的类很少改变,但经常需要在结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
代码实现:
1 // MainApp startup application
2
3 class MainApp
4 {
5 static void Main()
6 {
7 // Setup employee collection
8 Employees e = new Employees();
9 e.Attach(new Clerk());
10 e.Attach(new Director());
11 e.Attach(new President());
12
13 // Employees are 'visited'
14 e.Accept(new IncomeVisitor());
15 e.Accept(new VacationVisitor());
16
17 // Wait for user
18 Console.Read();
19 }
20 }
21
22 // "Visitor"
23
24 interface IVisitor
25 {
26 void Visit(Element element);
27 }
28
29 // "ConcreteVisitor1"
30
31 class IncomeVisitor : IVisitor
32 {
33 public void Visit(Element element)
34 {
35 Employee employee = element as Employee;
36
37 // Provide 10% pay raise
38 employee.Income *= 1.10;
39 Console.WriteLine("{0} {1}'s new income: {2:C}",
40 employee.GetType().Name, employee.Name,
41 employee.Income);
42 }
43 }
44
45 // "ConcreteVisitor2"
46
47 class VacationVisitor : IVisitor
48 {
49 public void Visit(Element element)
50 {
51 Employee employee = element as Employee;
52
53 // Provide 3 extra vacation days
54 Console.WriteLine("{0} {1}'s new vacation days: {2}",
55 employee.GetType().Name, employee.Name,
56 employee.VacationDays);
57 }
58 }
59
60 class Clerk : Employee
61 {
62 // Constructor
63 public Clerk() : base("Hank", 25000.0, 14)
64 {
65 }
66 }
67
68 class Director : Employee
69 {
70 // Constructor
71 public Director() : base("Elly", 35000.0, 16)
72 {
73 }
74 }
75
76 class President : Employee
77 {
78 // Constructor
79 public President() : base("Dick", 45000.0, 21)
80 {
81 }
82 }
83
84 // "Element"
85
86 abstract class Element
87 {
88 public abstract void Accept(IVisitor visitor);
89 }
90
91 // "ConcreteElement"
92
93 class Employee : Element
94 {
95 string name;
96 double income;
97 int vacationDays;
98
99 // Constructor
100 public Employee(string name, double income,
101 int vacationDays)
102 {
103 this.name = name;
104 this.income = income;
105 this.vacationDays = vacationDays;
106 }
107
108 // Properties
109 public string Name
110 {
111 get{ return name; }
112 set{ name = value; }
113 }
114
115 public double Income
116 {
117 get{ return income; }
118 set{ income = value; }
119 }
120
121 public int VacationDays
122 {
123 get{ return vacationDays; }
124 set{ vacationDays = value; }
125 }
126
127 public override void Accept(IVisitor visitor)
128 {
129 visitor.Visit(this);
130 }
131 }
132
133 // "ObjectStructure"
134
135 class Employees
136 {
137 private ArrayList employees = new ArrayList();
138
139 public void Attach(Employee employee)
140 {
141 employees.Add(employee);
142 }
143
144 public void Detach(Employee employee)
145 {
146 employees.Remove(employee);
147 }
148
149 public void Accept(IVisitor visitor)
150 {
151 foreach (Employee e in employees)
152 {
153 e.Accept(visitor);
154 }
155 Console.WriteLine();
156 }
157 }
运行结果:
Visoitr模式的几个要点:
1.Visitor模式通过所谓双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作。
2.所谓双重分发却Visotor模式中间包括了两个多态分发(注意其中的多态机制);第一个为accept方法的多态辨析;第二个为visitor方法的多态辨析。
3.Visotor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visiotr模式适用"Element"类层次结构稳定,而其中的操作却经常面临频繁改动".
对象状态影响对象行为:
对象拥有不同的状态,往往会行使不同的行为...
动机:
在软件构建过程中,某些对象的状态如果改变以及其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。
如何在运行时根据对象的状态来透明更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?
意图:
允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。 ------《设计模式》GOF
结构图:
适用性:
1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.一个操作中含有庞大的多分支的等条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个分支放入一个独立的类中。这使得你可根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
代码实现:
class MainApp
{
static void Main()
{
// Open a new account
Account account = new Account("Jim Johnson");
// Apply financial transactions
account.Deposit(500.0);
account.Deposit(300.0);
account.Deposit(550.0);
account.PayInterest();
account.Withdraw(2000.00);
account.Withdraw(1100.00);
// Wait for user
Console.Read();
}
}
// "State"
abstract class State
{
protected Account account;
protected double balance;
protected double interest;
protected double lowerLimit;
protected double upperLimit;
// Properties
public Account Account
{
get{ return account; }
set{ account = value; }
}
public double Balance
{
get{ return balance; }
set{ balance = value; }
}
public abstract void Deposit(double amount);
public abstract void Withdraw(double amount);
public abstract void PayInterest();
}
// "ConcreteState"
// Account is overdrawn
class RedState : State
{
double serviceFee;
// Constructor
public RedState(State state)
{
this.balance = state.Balance;
this.account = state.Account;
Initialize();
}
private void Initialize()
{
// Should come from a datasource
interest = 0.0;
lowerLimit = -100.0;
upperLimit = 0.0;
serviceFee = 15.00;
}
public override void Deposit(double amount)
{
balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
amount = amount - serviceFee;
Console.WriteLine("No funds available for withdrawal!");
}
public override void PayInterest()
{
// No interest is paid
}
private void StateChangeCheck()
{
if (balance > upperLimit)
{
account.State = new SilverState(this);
}
}
}
// "ConcreteState"
// Silver is non-interest bearing state
class SilverState : State
{
// Overloaded constructors
public SilverState(State state) :
this( state.Balance, state.Account)
{
}
public SilverState(double balance, Account account)
{
this.balance = balance;
this.account = account;
Initialize();
}
private void Initialize()
{
// Should come from a datasource
interest = 0.0;
lowerLimit = 0.0;
upperLimit = 1000.0;
}
public override void Deposit(double amount)
{
balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
balance += interest * balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (balance < lowerLimit)
{
account.State = new RedState(this);
}
else if (balance > upperLimit)
{
account.State = new GoldState(this);
}
}
}
// "ConcreteState"
// Interest bearing state
class GoldState : State
{
// Overloaded constructors
public GoldState(State state)
: this(state.Balance,state.Account)
{
}
public GoldState(double balance, Account account)
{
this.balance = balance;
this.account = account;
Initialize();
}
private void Initialize()
{
// Should come from a database
interest = 0.05;
lowerLimit = 1000.0;
upperLimit = 10000000.0;
}
public override void Deposit(double amount)
{
balance += amount;
StateChangeCheck();
}
public override void Withdraw(double amount)
{
balance -= amount;
StateChangeCheck();
}
public override void PayInterest()
{
balance += interest * balance;
StateChangeCheck();
}
private void StateChangeCheck()
{
if (balance < 0.0)
{
account.State = new RedState(this);
}
else if (balance < lowerLimit)
{
account.State = new SilverState(this);
}
}
}
// "Context"
class Account
{
private State state;
private string owner;
// Constructor
public Account(string owner)
{
// New accounts are 'Silver' by default
this.owner = owner;
state = new SilverState(0.0, this);
}
// Properties
public double Balance
{
get{ return state.Balance; }
}
public State State
{
get{ return state; }
set{ state = value; }
}
public void Deposit(double amount)
{
state.Deposit(amount);
Console.WriteLine("Deposited {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}\n" ,
this.State.GetType().Name);
Console.WriteLine("");
}
public void Withdraw(double amount)
{
state.Withdraw(amount);
Console.WriteLine("Withdrew {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}\n" ,
this.State.GetType().Name);
}
public void PayInterest()
{
state.PayInterest();
Console.WriteLine("Interest Paid --- ");
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}\n" ,
this.State.GetType().Name);
}
}
结果:
State模式的几个要点:
1.State模式将所有一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦。
2.为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的----即要么彻底转换过来,要么不转换。
3.如果State对象没有实例变量,那么各个上下文可以共享 同一个State对象,从而节省对象开销。