舞步者

带她一起去周游世界
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

设计模式之九装饰模式(Decorator Pattern)

Posted on 2008-06-03 12:03  Kevin_Zhang  阅读(145)  评论(0)    收藏  举报
顾名思义此模式要帮助实现的是修饰的效果。
举个例子来说,我们在游戏中开发一种坦克,除了有不同型号的坦克外,我们还希望在不同的场合中为其增加以下一种或多种功能:如红外夜视功能,水陆两栖功能,卫星定位功能等等。
按照我们的做法,我们会先抽象出一个Tank的抽象类,里面有代表坦克普遍功能的方法,如:
 public abstract class Tank
    
{
        
public abstract void Shot();
        
public abstract void Run();
    }
对于不同的型号,我们新建具体的型号类,来实现上面的抽象类:
public class T50:Tank
    
{
        
public override void Shot()
        
{
            
        }

        
public override void Run()
        
{
            
        }

    }

 
public class T75 : Tank
    
{
        
public override void Shot()
        
{

        }

        
public override void Run()
        
{

        }

    }

public class T90 : Tank
    
{
        
public override void Shot()
        
{

        }

        
public override void Run()
        
{

        }

    }
对于要新增的功能,我们写成几个接口:
 public interface IA  //夜视
    {
        
//
    }
 
 
public interface IB//水陆两栖
    {
        
//
    }

public interface IC //卫星定位
    {
        
//
    }
那么如果我们要让T50的坦克具有夜视的功能,我们要新建一个T50A的类继承T50和IA接口:
public class T50A:T50,IA{...}
如果要让T50的坦克具有夜视和水陆两栖的功能,需要再建一个类,继承T50类和IA,IB两个接口:
public class T50AB:T50,IA,IB{...}
如果要让T50的坦克具有夜视和水陆两栖以及维修那个定位的功能,还要再建一个类,继承T50类和IA,IB和IC三个接口:
public class T50ABC:T50,IA,IB{...}
我们现在才只是考虑为T50添加一些额外的功能,还没有涉及其他型号,如果再有其他的很多型号,或者许多额外的功能要添加,那么我们的子类会迅速膨胀.
很显然我们上面的实现违背了单一职责原则,更严重的是不能很好的应对变化,类的层次体系结构过于复杂,导致将来难以维护。
那么,我们该如何解决上面出现的问题呢?这就要用到我们的装饰模式了。
先看结构图:

”装饰“这个词我的个人理解是给在某个对象的基础上给它添加某些额外的功能,但有一个基本的原则就是不能改变这个对象的类型,否则那就不能叫装饰了。
对于现在我们面临的问题,那些额外的一些功能比如夜视,红外,卫星定位应该作外坦克的装饰功能,也就是说并不是因为有了这些功能坦克就不是坦克了;那么我们可以让额外的功能做成类,并继承自坦克基类;由于这些额外的功能是变化的,所以我们进行抽象,让抽象出来的基类来继承坦克基类;而后在具体的子类当中来实现具体的额外功能,这些额外的是附属在坦克原有功能的基础上的一个加工。下面来看看这些“装饰类”的实现代码:
public abstract class Decorator:Tank
    
{
        
private Tank _tank;
        
public Decorator(Tank tank)
        
{
            
this._tank = tank;
        }

        
public override void Run()
        
{
            _tank.Run();
        }

        
public override void Shot()
        
{
            _tank.Shot();
        }

    }

    
public class DecoratorA:Decorator
    
{
        
public DecoratorA(Tank tank)
            : 
base(tank)
        
{ }
        
public override void Run()
        
{
            
//水陆两栖扩展
            
//
            
            
base.Run();
        }

        
public override void Shot()
        
{
            
//水陆两栖扩展
            
//

            
base.Shot();
        }

    }

    
public class DecoratorB : Decorator
    
{
        
public DecoratorB(Tank tank)
            : 
base(tank)
        
{ }
        
public override void Run()
        
{
            
//红外夜视扩展
            
//

            
base.Run();
        }

        
public override void Shot()
        
{
            
//红外夜视扩展
            
//

            
base.Shot();
        }

    }

    
public class DecoratorC : Decorator
    
{
        
public DecoratorC(Tank tank)
            : 
base(tank)
        
{ }
        
public override void Run()
        
{
            
//卫星导航扩展
            
//

            
base.Run();
        }

        
public override void Shot()
        
{
            
//卫星导航扩展
            
//

            
base.Shot();
        }

    }
有些功能比如Shot方法可能跟一些附加功能搭不上关系,比如水陆两栖,那么
我们可以在具体的类里面不需要重写Shot方法,比如说我们的DecoratorA里面完全可以不需要重写Shot方法;我们这里都统一写全了,没有多大关系。
现在我们来看看我们的代码结构,修饰类继承与Tank抽象类,而且有趣的是修饰类还包含了Tank抽象类的实例;这使得修饰类同时具备了IS-A和HAS-A的关系;这里其主要作用的是HAS-A的关系;IS-A使得我们在实现类里面可以重写坦克的原油方法,以达到功能扩展的目的。
基本的代码写好了,那么客户端我们该怎么用呢?
看看下面的客户端代码:
 static void Main(string[] args)
        
{
            Tank tank 
= new T50();
            
//具备水陆两栖功能
            Decorator decoratorA = new DecoratorA(tank);
            decoratorA.Run();
            decoratorA.Shot();

            
//具备红外夜视功能
            Decorator decoratorB = new DecoratorB(tank);
            decoratorB.Run();
            decoratorB.Shot();

            
//具备卫星导航功能
            Decorator decoratorC = new DecoratorB(tank);
            decoratorC.Run();
            decoratorC.Shot();

            
//三种功能全部具备
            Decorator decoratorAl = new DecoratorB(decoratorA);
            Decorator decoratorAll 
= new Decoratorc(decoratorAl);
            decoratorAll.Run();
            decoratorAll.Shot();


        }
通过上面的代码我们注意到这么一点:我们将DecoratorA作为参数传递给DecoratorB的构造函数,原因很简单,因为我们的Decorator类继承自Tank类型,所以当然可以传递。
上面的代码实现了扩展功能的动态添加,也就是运行时的添加,我们之前的继承为类型引入的是静态特质,也就是编译时的添加,这样就缺少灵活性,也就造成了子类会随着扩展功能的添加而急速膨胀的情形;现在好了,我们只是在需要某种功能的时候才进行包装;其次,如果再有新的扩展功能要添加,那么只需要再添加一个对应的修饰子类,而不要再添加其他坦克子类,避免了子类的膨胀;我们说装饰模式很好的符合了面向对象设计原则中的“优先使用对象组合而非继承”和"开放-封闭“原则。