• 《让僵冷的翅膀飞起来》系列之三——从Adapter模式到Decorator模式(转)


    一、 考察对象的Adapter模式

    从上文看到,经过引入Adapter模式,原有的结构得到了改进。但我们还需要从客户的角度分析程序,使结构更加地合理。(这里,我们仅限于考察对 象的Adapter模式。类的Adapter模式不存在下述问题。这也印证了一个事实,就是:对象的Adapter模式和类的Adapter模式各有优 势,也各有缺点,设计时应根据实际情况考察。)

    1、扩展的功能是否合理?

    假设用户希望调用VedioMedia同时具有Play()和Resize()功能。从前面的描述来看,客户只需要实例化VedioAdapter类对象,就可以调用了。看来结构是正确的。

    2、类型的扩展是否合理?

    从目前的需求来看,要调用RM和MPEG类型的对象,没有任何问题。但是正如吕震宇所说,在VedioMedia类的Resize()方法中有一股 腐化的味道。坏味道的根源就是if 条件语句。如果要增加新的视频类型,就需要修改Resize()方法了。这是一个设计的权衡。其实这个味道虽然够坏,但好处是简单,也不用更多的对象;但 耦合性比较差。

    如果我们的目标是希望更好的架构以支持耦合的松散,目前的结构就需要微调了。调整后的类图如下:


     

    这样需要改变VedioAdapter类的代码:
    public abstract class VedioAdapter:IVedioScreen
    {
     protected vedioMedia _vedio;
     
     public VedioAdapter(vedioMedia vedio)
     {
      _vedio = vedio;
     }

     public void Play()
     {
      _vedio.Play();  
     }

    public abstract void Resize();
    }

    然后实现RMAdapter和MPEGAdatper:
    public class RMAdapter:VedioAdapter
    {
     public RMAdapter(VedioMedia vedio):base(vedio){}
     
     public override void Resize()
     {
      MessageBox.Show("Change the RM screen's size.");
     }
    }
    (MPEGAdapter的代码省略)

    这样一改,要扩展就容易了,不过比之以前设计要复杂些,希望不会有人说我过度设计。如果要考虑正确性的话,RMAdapter的构造函数还需要考虑 异常情况。由于构造函数的参数为VedioMedia类型,因此,客户在调用时可能会传入MPEG类型,此时RMAdapter类型的Play()行为就 会发生改变。这也是和Decorator最大的不同,就是我们必须限制对象的Play()方法不能做任何改变。
    public RMAdapter(VedioMedia vedio):base(vedio)
    {
     if (!(vedio is RM))
      throw new Exception("VedioMedia object is not correct!");
    }

    3、 是否与原有客户系统兼容?

    如果在原有的客户系统中提供了如下的类及方法:
    public class MediaFile
    {
     public static void Play(IMedia media)
     {
      media.Play();
     }
    }

    那么客户如下的调用是没有任何问题的:
    MediaFile.Play(new RM());

    然而,当客户要使用新的Adapter对象呢?例如:
    MediaFile.Play(new RMAdapter());
    显然是有 问题了,因为RMAdpater类没有实现IMedia接口,且RMAdpater类的Play()方法和IMedia接口的Play()方法在性质上也 是有区别的。此时,采用原有的设计就不正确了。改进的方法很简单,就是让VedioAdapter类实现IMedia接口就可以了:


     

    public abstract class VedioAdapter:IVedioScreen,IMedia
    {
     ……
    }

    根据吕震宇所说,当VedioAdapter类实现IMedia接口时,言外之意就是该Adapter也适合AudioMedia类型了。是否如此 呢?可以说是一半对,一半不对。对的原因,是由于AudioMedia类也实现了IMedia接口;但别忘了,我们适配的并非类,而是对象,也就是在 VedioAdapter中传递进来的VedioMedia对象。(如果我们将传递进来的对象扩展为IMedia,那就糟糕了。震宇兄的结论就完全成立 了。)同时,我们在构造函数的异常处理,也保证了AudioMedia类型对于VedioAdapter是非法的。

    写到这里,我觉得本文已经超出了原来的设想,有些研究的味道了。嗯,还算不错。那么就继续研究下去吧。

    二、 引入Decorator模式

    按照最初的需求,我引入Decorator模式试一试。最初的需求是,需要为RM和MPEG类在不改变原有代码的情况下,添加Resize()方法,而其原来的Play()方法不变。调整设计类图:


     上图的橙红色区域为Decorator模式的主体,至于IVedioScreen接口,仅仅是为VedioDecorator增加Resize()方法而引入的,其本身与Decorator无关,除非我要实现的Decorator功能,通过该接口来实现。代码如下:
    public abstract class VedioDecorator:VedioMedia,IVedioScreen
    {
     private VedioMedia _vedio;
     
     public VedioAdapter(vedioMedia vedio)
     {
      _vedio = vedio;
     }

     public VedioMedia Vedio
     {
      get {return _vedio;}
     }
    public abstract void Resize();
    }

    注意看,与前面Adapter模式的VedioAdapter类比较,除增加了对VedioMedia的派生外,还减少了Play(),因为该方法 已经从VedioMedia类中派生获得。这样的话,RMDecorator和MPEGDecorator,也需要做相应的改变:
    public class RMDecorator:VedioDecorator
    {
     public RMDecorator (VedioMedia vedio):base(vedio)
    {
      if (!(vedio is RM))
       throw new Exception("VedioMedia object is not correct!");
    }
     
     public override void Play()
     {
      Vedio.Play();
     }
     public override void Resize()
     {
      MessageBox.Show("Change the RM screen's size.");
     }
    }

    到这个时候,我忽然觉得引入Decorator模式,已经有些力不从心了。为什么呢?最大的障碍就是我们的需求不能更改VedioMedia类型 Play()方法的既有行为。这个时候,所谓的Decorator已经失去了原来的意义。其实我觉得,此时的设计,应该是结合了Adapter模式与 Decorator模式而衍生出的新的结构。

    那么,我为什么还要在本文提出引入Decorator模式呢。这来源我对于设计模式一向的观点:不要为了模式而模式!GOF的23种模式,并非茴香 豆的“茴”字,我也并非孔乙己,要你回答“茴”字的写法,却忽略了使用设计模式的真正精神。设计模式归根结底是拿来用的。只要符合你的要求,各种模式随你 怎么变都可以。因此,不管是前文所述的Adapter模式,还是改进后的Adapter模式,或者引入的Decorator模式,其中的变化是灵活的,选 择权最终还是你。

    三、 正宗的Decorator模式

    不过,我还是很有兴趣继续探讨下去,仍然借助媒体播放这个例子,来谈一谈Decorator模式的一般应用。现在我们要求RM和MPEG媒体在播放 前,首先要显示媒体文件的版权信息。请注意,这个需求,并非是为RM等媒体增加ShowCopyright()方法,而Play()方法保持不变。恰恰相 反,新的需求装饰了Play()的行为,它要求Play()的同时能够支持ShowCopyright的功能。类图如下:

    在这里,VedioDecorator是装饰类的抽象类,而CopyRightVedioDecorator类则具体装饰了Play()的功能。
    public abstract class VedioDecorator:VedioMedia
    {
     private VedioMedia _vedio;
     
     public VedioAdapter(vedioMedia vedio)
     {
      _vedio = vedio;
     }

     public VedioMedia Vedio
     {
      get {return _vedio;}
     }
    }
    然后实现CopyRightVedioDecorator类,为Play()方法装饰显示版权信息的功能:
    public class CopyRightVedioDecorator:VedioDecorator
    {
     private CopyRight _copyRightMark;
     
     public CopyRight CopyRightMark
     {
      get {return _copyRightMark;}
      set {_copyRightMark = value;}
     }
     public override void Play()
     {
      _copyRightMark.ShowCopyRight();
      Vedio.Play();
     }
    }

    我们还可以继续装饰VedioMedia的Play行为,例如,要求在播放媒体文件之前,必须放一段广告,那么我们可以继续提供一个AdvertisementVedioDecorator装饰类。道理与上一样,不再赘述。

    通过本例,我们可以看到Decorator模式与对象的Adapter模式的区别。
    实现的区别:
    1、 Decorator抽象类应继承要装饰的类,同时又聚合该类的实例对象;而对象的Adapter模式则只聚合,不继承;
    2、 Decor模式并没有引入新的接口,除非要装饰的行为需要使用该接口;而对象的Adapter模式则引入了新的接口,以此来装配原有的对象,使其具有了新接口的方法;

    因此,适用的场景也就有所不同:
    1、 Decorator模式如其名,一般并不提供新的行为,而是在原有的行为上进行补充,即装饰的含义。
    2、 Adapter模式则是为对象引入新的行为,使其匹配新的接口,即为适配的意义所在。

  • 相关阅读:
    DEDECMS5.5/5.6/5.7列表页调用TAG标签(热门标签)的两种方法
    DEDE列表页和内容页调用顶级栏目ID的方法
    解决dede图集上传图片时跳出302错误
    DEDE用{dede:sql}标签取出当前文档的附加表中的内容
    DEDE模板中如何运行php脚本和php变量的使用
    织梦DEDECMS {dede:arclist},{dede:list}获取附加表字段内容
    把DEDE的在线文本编辑器换成Kindeditor不显示问题
    ExtJS:文件上传实例
    ExtJS:GridPanel之renderer:function()和itemdblclick : function()方法参数详解
    ExtJS:菜单ComboBox及级联菜单应用
  • 原文地址:https://www.cnblogs.com/whitetiger/p/1091715.html
Copyright © 2020-2023  润新知