装饰模式
姓名:高振松
学号:07770116
1、生活场景:看过美国科幻片的同学应该都会为美国人那惊人的想象力说折服吧。在美国的科幻片里有一种东西会经常在镜头里出现,那就是会飞行的交通工具(汽车,卡车之类的)。这些交通工具既可以在陆地上跑,也可以在天空中飞行或者在水中行驶。现假设,现在有一家汽车生产商,最近突破一项关键技术,使得所有类型的车辆都可以在天空中飞或者在水中行驶的功能。而你正好在给该生产商开发系统软件,现在要实现对所有车辆的模拟,你该如何进行编写。
2、我在这里展示我的不假思索的解决方案:
我记得在以前上面向对象的编程语言课时,老师在讲解类的继承特性时提到使用类的继承可以来扩展类的功能。在一般直觉下,我首先想到的是用类的继承关系来模拟上述问题。
类图如下: 私家车可以飞,卡车则可以在水中行驶。
实现代码:
私家车的基本功能: public class _FutureCar { public virtual string OperateA() { return "小型私家车在陆地上以一档速度行驶。"; } public virtual string OperateB() { return "小型私家车在陆地上以二档速度行驶。"; } public virtual string OperateC() { return "小型私家车在陆地上以三档速度行驶。"; } } 私家车的扩展功能: public class _FlyableFutureCar : _FutureCar { public override string OperateA() { string strDo = base.OperateA(); strDo += "\n 在启动飞行功能在空中飞行!"; return strDo; } public override string OperateB() { string strDo = base.OperateB(); strDo += "\n 在启动飞行功能在空中飞行!"; return strDo; } public override string OperateC() { string strDo = base.OperateC(); strDo += "\n 在启动飞行功能在空中飞行!"; return strDo; } } 卡车的基本功能: public class _FutureTruck { public virtual string OperateA() { return "大卡车在陆地上以一档速度拉货。"; } public virtual string OperateB() { return "大卡车在陆地上以二档速度拉货。"; } public virtual string OperateC() { return "大卡车在陆地上以三档速度拉货。"; } } 卡车的扩展功能: public class _DriveInWaterFutureTruck : _FutureTruck { public override string OperateA() { string strDo = base.OperateA(); strDo += "\n 在启动水中行驶功能在水中行驶!"; return strDo; } public override string OperateB() { string strDo = base.OperateB(); strDo += "\n 在启动水中行驶功能在水中行驶!"; return strDo; } public override string OperateC() { string strDo = base.OperateC(); strDo += "\n 在启动水中行驶功能在水中行驶!"; return strDo; } } 3、现在就让我们开探讨下这种方案中的缺陷。情形一: 如果生产商要生产可以在水中行驶的Car,和可以在空中飞行的卡车,则该怎么改代码?在这里唯一的方式则是添加新的类 DriveInWaterCar ,和 FlyableTruck。
这里的车辆和扩展类。功能还比较少,要是10种类型的车辆和10种类型的扩展功能的话,进行组合将可能会产生110个。
情形二:如果要动态地添加车辆和功能的话,将这样改代码?在这个情形中,按照继承的方式,删除的功能,只需要删除派生类即可。要是删除车辆的一个类别(如Truck),则要删除大量的代码,删除_FutureTruck, 及其派生类_DriveInWaterFutureTruck, FlyableTruck。对于添加,存在相同问题。也就是说,用一般的继承关系来处理上述问题,存在扩展不够开放问题。
4、归纳阶段
下面是我用装饰模式来解决类继承带来的缺陷。
在这展示装饰模式之前,我们先了解装饰模式的目的或者是其所针对的问题。
首先,装饰(Decorator)模式又名包装(Wrapper)模式[GOF95]。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。注意,装饰模式是继承关系的一个替代方案,实现与继承关系相同的效果。
其次,装饰模式,只是以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。就像西游记里的孙猴子一样,任它72变变来变去,本质上还是个猢狲。
5、验证阶段
好的,现在让我们来看看装饰模式是否解决了之前不假思索的缺陷。
2.扩展不够开放问题。由于装饰模式中,装饰类(FlyComponent或者DriveInWaterComponent)只是引用了被装饰类(FutureCar或者FutureTruck),同样对扩展也开放。添加一个新的被装饰类,不用被装饰类继承就可以被装饰。添加一个装饰类,不用继承被装饰类,就可以进行装饰被装饰类。
相关代码:
接口IComponent:
public interface IComponent { string OperateA(); string OperateB(); string OperateC(); } 被装饰类FutureCar 和 FutureTruck: public class FutureTruck : IComponent { #region IComponent 成员 public string OperateA() { return "大卡车在陆地上以一档速度在拉货。"; } public string OperateB() { return "大卡车在陆地上以二档速度在拉货。"; } public string OperateC() { return "大卡车在陆地上以三档速度在拉货。"; } #endregion } public class FutureCar : IComponent { #region IComponent 成员 public string OperateA() { return "小型私家车在陆地上以一档速度行驶。"; } public string OperateB() { return "小型私家车在陆地上以二档速度行驶。"; } public string OperateC() { return "小型私家车在陆地上以三档速度行驶。"; } #endregion } 装饰类: public abstract class Decorate : IComponent { protected IComponent m_COM = null; public abstract IComponent Component { set; } #region IComponent 成员 public abstract string OperateA(); public abstract string OperateB(); public abstract string OperateC(); #endregion } 被装饰类: public class FlyComponent : Decorate { public override IComponent Component { set { if (value is FutureCar) { base.m_COM = value; } else { throw new Exception("Component 不是 FutureCar类型!"); } } } public override string OperateA() { string strDo = base.m_COM.OperateA(); strDo += "\n 在启动飞行功能在空中飞行!"; return strDo; } public override string OperateB() { string strDo = base.m_COM.OperateB(); strDo += "\n 在启动飞行功能在空中飞行!"; return strDo; } public override string OperateC() { string strDo = base.m_COM.OperateC(); strDo += "\n 在启动飞行功能在空中飞行!"; return strDo; } } public class DriveInWaterComponent : Decorate { public override IComponent Component { set { if (value is FutureTruck) { base.m_COM = value; } else { throw new Exception("Component 不是 FutureTruck类型!"); } } } public override string OperateA() { string strDo = base.m_COM.OperateA(); strDo += "\n 在启动水中行驶功能在水中行驶!"; return strDo; } public override string OperateB() { string strDo = base.m_COM.OperateB(); strDo += "\n 在启动水中行驶功能在水中行驶!"; return strDo; } public override string OperateC() { string strDo = base.m_COM.OperateC(); strDo += "\n 在启动水中行驶功能在水中行驶!"; return strDo; } }
装饰模式的缺点:
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。使用条件:
在以下情况下应当使用装饰模式:
- 需要扩展一个类的功能,或给一个类增加附加责任。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
参考资料:《C# 设计模式》 [美] Jame W.Cooper著 电子工业出版社。