设计模式(21)装饰模式 Decorator Pattern

  装饰模式是为已有功能动态地添加更多额外功能的一种模式。就增加功能来说,装饰模式比生成子类更为灵活。

当系统需要新功能时,一般做法是向旧的类中添加新的代码,这些新加的代码通常影响了原有类的核心职责或行为,在主类中加入新的字段、方法或是逻辑,从而增加了主类的复杂性,而这些新加入的代码仅仅是为了满足一些只在某种特定情况下才会发生的特殊行为的需要。装饰模式提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所有要装饰的对象,这样当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择性地,按顺序的使用装饰功能包装的对象了。

 装饰模式的关键特性在于它不依赖于继承来实现扩展行为,相对于继承方式更加灵活:

   实现接口的方法,改变Component的原有行为。添加新的状态和行为. 通过构造行为传递的对象,访问任何公开的成员。


解决问题:

1.需要扩展一个类的功能,或给一个类增加附加责任。
2.需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
3.需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实

装饰模式的特点: 
(1)装饰对象和真实对象有相同的接口。客户端可以用与真实对象相同的方式和装饰对象交互。 
(2)装饰对象包含一个真实对象的引用(reference)。 
(3)装饰对象接受所有来自客户端的请求,它把这些请求转发给真实的对象。 
(4)装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。 


使用场景:
希望动态的增强类的某个对象的功能,而又不影响到该类的其他对象。eg:我们现在有一个车辆生成线,该生成线生产的汽车加一次油能够行使100公里。但是我们现在要一辆加一次油能够行使150公里的汽车,我们现在不可能去重新造一个生产线,也不能去修改现有的生产线,那我们现在改怎么完成这项工作呢?一种好的解决办法就是利用该生成线生成的汽车,我们对其进行改装,对其加一个邮箱,使其能够行使150公里。

模式中的角色:
抽象组件(AbstractComponent):抽象组件是一个抽象类。抽象组件定义了“被封装者”需要进行“装饰”的方法。
具体组件(ConcreteComponent):具体组件是抽象组件的一个子类,具体组件的实例称作“被装饰者”。
装饰(Decorator):装饰也是抽象组件的一个子类,但装饰还包含一个抽象组件声明的变量以及保存“被装饰者”的引用。装饰可以是抽象类也可以是一个非抽象类,如果是非抽象类,那么该类的实例称作“装饰者”。
具体装饰(ConcreteDecorator):具体装饰是装饰的一个非抽象子类,具体装饰的实例称作“装饰



      装饰模式在现实生活中随处可见。比如我们买了一辆车,然后我们第一件要做的事,就是把自己的爱车装饰一下。可供选择的配件包括ABS刹车防抱死系统,车载音响,倒车雷达等等,可以选择其中的一件或是多件来装饰我们的爱车。把上述场景转化为程序如下:

有一个MyCarDecorate类,就是一辆裸车。要实现对裸车的装饰,有如下两种方法:

1.在MyCarDecorate里面添加相应的方法。这种做法显然不对。一来违反了面向对象的开放-封闭原则,二来也不易扩展。试想一下,如果每个用户都有不同的需求,那你这个类不是每次调用前都要修改一次吗?

2.使用子类继承的方法。比如用户需要裸车+倒车雷达,则添加一个新类Radar,继承MyCarDecorate;如果用户需要裸车+雷达+音响,则再新加一个Audio类,继承Radar;如果用户只需要裸车+音响,则还需要添加一个新类AudioOnly类,继承自MyCarDecorate…………。看似好像解决了问题,但如果配件有上百种,那么你的子类数量就会呈指数形式增长。所以这种做法也不可取。

 

下面就用本节的主角,装饰模式来解决这个难题吧。

为了把车内装饰同车本身分离开,我们要用到一个Show接口,定义如下:

public interface ICarShowHandle
    {
        void Show();
    }

MyCarDecorate裸车类继承ICarShowHandle接口

public class MyCarDecorate : ICarShowHandle
    {
        private string _carName;

        public MyCarDecorate() { }
        public MyCarDecorate(string carName)
        {
            this._carName = carName;
        }

        public void Show()
        {
            Console.WriteLine(string.Format("这就是我的{0}小车", _carName));
        }
    }

为了把裸车的代码同装饰分离,我们需要一个汽车装饰的基类,同样需要继承自ICarShowHandle

public class CarTrim : ICarShowHandle
    {
        private ICarShowHandle _myCar;

        public CarTrim(ICarShowHandle myCar)
        {
            this._myCar = myCar;
        }

        public virtual void Show()
        {
            if (_myCar != null)
                _myCar.Show();
        }
    }

下面就是让各种装饰一一继承上面这个汽车装饰类。

public class CarRadar : CarTrim
    {
        public CarRadar(ICarShowHandle myCar) : base(myCar) { }

        public override void Show()
        {
            Console.WriteLine("具有倒车雷达的功能");
            base.Show();
        }
    }

public class CarAudio : CarTrim
    {
        public CarAudio(ICarShowHandle myCar) : base(myCar) { }

        public override void Show()
        {
            Console.WriteLine("具有车载音响系统");
            base.Show();
        }
    }

public class CarABS:CarTrim
    {
        public CarABS(ICarShowHandle myCar) : base(myCar) { }

        public override void Show()
        {
            Console.WriteLine("具有ABS刹车防抱死系统");
            base.Show();
        }
    }

调用方法如下:

1.ICarShowHandle myCar = new CarABS(new CarRadar(new CarAudio(new MyCarDecorate("飞度"))));
   myCar.Show();

显示结果为:

装饰模式图片1

2.ICarShowHandle myCar = new CarABS(new CarRadar(new MyCarDecorate("宝马")));
   myCar.Show();


优点:
1)Decorator模式与继承关系的目的都是扩展对象功能,但是Decorator可以提供比继承更多的灵活性。
2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。


缺点:
1)这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
2)装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否适合。当然也可以改变Component接口,增加新的公开的行为,实现"半透明"的装饰者模式。在实际项目中要做出最佳选择。


代理模式,装饰模式,适配器模式之间的区别: 
1、代理模式更多的是过程的控制功能; 
2、装饰者模式主要是给被装饰者增加新职责的; 
3、适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值