看过好多对装饰模式的讲解,他们几乎都有一句相同的话:对现有类功能的扩展。不知道大家怎么理解这句话的,之前我把”对功能的扩展“理解成”加功能=加方法“,比如Person类本来有两个功能:Eat 和 Run ,使用装饰模式后,可以再加一个功能:Sleep,这显然是不能的。增加Sleep意味着修改接口,装饰模式的功能并不是这样,那是怎样的呢?
还拿Person类来说明,比如现在有个需求:在吃饭前要洗手。 ”洗手“这个功能就是对Eat功能的扩展,添加“洗手”这个功能可以通过装饰模式添加,又比如吃完后,“男人要刷锅刷碗”, “刷锅刷碗”这个功能要添加给男人身上,也可以通过装饰模式动态给男人扩展这个功能,这个功能何时调用呢,当然是在吃饭的时候了,也就是调用Eat的时候,所以这些代码要写到Eat方法里。
再举个汽车的例子:
IMachine 接口是机器的接口,包含:Start()和Stop()两个功能。Car是IMachine的一个实现,SUVCar是Car的一个实现;
类:
1 /// <summary> 2 /// 机器 3 /// </summary> 4 interface IMachine 5 { 6 void Start(); 7 void Stop(); 8 }
IMachine
1 /// <summary> 2 /// 汽车 3 /// </summary> 4 abstract class Car : IMachine 5 { 6 7 public abstract void Start(); 8 9 public abstract void Stop(); 10 } 11 12 /// <summary> 13 /// suv汽车 14 /// </summary> 15 class SUVCar : Car 16 { 17 public override void Start() 18 { 19 Console.WriteLine("我是SUV,发动机启动"); 20 } 21 22 public override void Stop() 23 { 24 Console.WriteLine("我是SUV,发动机熄火"); 25 } 26 }
Car 和 SUVCar
这样的话,在Program里直接调用:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine("****汽车的功能****"); 6 IMachine car = new SUVCar(); //SUV汽车的功能 7 ControlMachine(car); 8 9 Console.WriteLine(""); 10 Console.ReadLine(); 11 } 12 13 static void ControlMachine(IMachine m) 14 { 15 m.Start(); 16 Console.WriteLine("机器在运转....."); 17 m.Stop(); 18 } 19 }
没问题,但是如果有新的需求时,比如:当汽车发动机启动时自动打开大灯,发动机熄火时关闭大灯,这如何实现呢?
可以用一下两种方式:1、继承的方式,添加一个能自动管理大灯的SUVCar,继承自Car,重写Start和Stop方法,然后Main中的IMachine car = new SUVCar(); 改成IMachine car = new LightSUVCar();即可。
2、装饰模式实现,原理就是 添加 控制灯光功能类,这个控制灯光的功能对汽车而言是个装饰,汽车装饰上了这个功能,就具有了这个功能。如何装饰呢?这个灯光控制的功能就像个带灯光控制功能的汽车盒子,它能容纳一个汽车,也具有汽车的所有功能,只是这些功能室通过调用汽车的功能来实现的,同时添加自己的功能。
好,那我们先定义一个可以容纳汽车的盒子(装饰类):
class LightCarDecotar { private IMachine mac = null; public LightCarDecotar(IMachine c) { mac = c; } }
这个类可以通过构造函数来容纳一个机器(IMachine),当然也就可以容纳汽车了。构造函数的参数IMachine可以修改成Car,这要看你调用地方的接口是如何使用的。好,能够容纳后,还需要让这个盒子拥有汽车的功能,如何拥有呢,那就是让它继承接口:IMachine。由于汽车继承了IMachine,所以它继承了这个接口就拥有了相关功能:
/// <summary> /// 汽车启动时自动开启远光灯,熄火后自动关闭远光灯 /// </summary> class LightCarDecotar : IMachine { private IMachine mac = null; public LightCarDecotar(IMachine c) { mac = c; } public void Start() { Console.WriteLine("车灯打开"); mac.Start(); } public void Stop() { mac.Stop(); Console.WriteLine("车灯关闭"); } }
到这里,这个汽车车灯控制的装饰类搞定了,在Main中调用:
class Program { static void Main(string[] args) { Console.WriteLine("****汽车的功能****"); IMachine car = new SUVCar(); //SUV汽车的功能 ControlMachine(car); Console.WriteLine(""); Console.WriteLine("****为汽车添加灯光功能****"); IMachine lightCar = new LightCarDecotar(car); //为汽车添加灯光功能 ControlMachine(lightCar); Console.WriteLine(""); Console.ReadLine(); } static void ControlMachine(IMachine m) { m.Start(); Console.WriteLine("机器在运转....."); m.Stop(); } }
由于装饰类:LightCarDecotar需要容纳一个汽车,所以要通过构造函数把汽车传递进去,由因为它继承自IMachine接口,故可以直接传递给ControlMachine方法的参数。运行结果:
同样的,给汽车再加一个”鸣笛“功能,鸣笛装饰类:
/// <summary> /// 汽车启动时自动鸣笛,关闭时也自动鸣笛 /// </summary> class VoiceCarDecorator : IMachine { private IMachine mac = null; public VoiceCarDecorator(IMachine c) { mac = c; } public void Start() { Console.WriteLine("喇叭鸣笛"); mac.Start(); } public void Stop() { mac.Stop(); Console.WriteLine("喇叭鸣笛"); } }
调用的方式同上。
这是汽车就用了两个功能的装饰类了,再想一下,可能还会有这样的需求:汽车启动时即”开灯“又”鸣笛“,这怎么办呢?两种:1,再加一个装饰类,类中同时写”鸣笛“和”开灯“的代码。不过第一种方法貌似会出现重复的代码(鸣笛和开灯),这时就体现出装饰模式的强大之处和构思巧妙了。由于它能容纳一个汽车,而它自己就是汽车(因为继承了IMachine),所以它能容纳同类(装饰类)。
对于即“开灯”又“鸣笛”的汽车,可以这样写:
IMachine voiceCar = new VoiceCarDecorator(car); //为汽车添加鸣笛功能 IMachine lightVoiceCar = new LightCarDecotar(voiceCar); //为汽车添加鸣笛和灯光功能 ControlMachine(lightVoiceCar);
先创建一个能鸣笛的车,然后创建一个能开灯的车,并把能鸣笛的车传递进去,装饰完的车就既可以鸣笛又可以开灯了。运行结果:
综上所述,您看出装饰模式的优势没?
例子中的类比较少,假设Car的子类有10种,其中的5种要加鸣笛,2种加开灯,3种加“既鸣笛又开灯”,如果用继承方式来实现,则要至少要加10个类通过重写方法来实现。但是如果通过装饰模式实现,则只需要加两个类(两个功能装饰类),然后在调用的时候按需装饰,装饰出来的对象就具有了装饰的功能。
以上例子是自己构想的,解释若有不确之处还请指出。谢谢