zoukankan      html  css  js  c++  java
  • 设计模式:装饰模式

    装饰模式  


    一、引子 
        装饰模式?肯定让你想起又黑又火的家庭装修来。其实两者在道理上还是有很多相像的 
    地方。家庭装修无非就是要掩盖住原来实而不华的墙面,抹上一层华而不实的涂料,让生活 
    多一点色彩。而墙还是那堵墙,他的本质一点都没有变,只是多了一层外衣而已。 
        那设计模式中的装饰模式,是什么样子呢? 


    二、定义与结构 
        装饰模式(Decorator)也叫包装器模式(Wrapper)。GOF 在《设计模式》一书中给出 
    的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比 
    生成子类更为灵活。 
        让我们来理解一下这句话。我们来设计“ 门”这个类。假设你根据需求为“ 门”类作了如下 
    定义: 
                                        Door 

                                        open() 
                                        close() 
                                        lock() 


        现在,在系统的一个地方需要一个能够报警的Door,你来怎么做呢?你或许写一个Door 
    的子类AlarmDoor,在里面添加一个子类独有的方法alarm()。嗯,那在使用警报门的地方 
    你必须让客户知道使用的是警报门,不然无法使用这个独有的方法。而且,这个还违反了 
    Liskov 替换原则。 
        也许你要说,那就把这个方法添加到Door 里面,这样不就统一了?但是这样所有的门 
    都必须有警报,至少是个“哑巴”警报。而当你的系统仅仅在一两个地方使用了警报门,这明 
    显是不合理的——虽然可以使用缺省适配器来弥补一下。 
        这时候,你可以考虑采用装饰模式来给门动态的添加些额外的功能。 
        下面我们来看看装饰模式的组成,不要急着去解决上面的问题,到了下面自然就明白了! 
    1)  抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。 
    2)  具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的 
        类。 
    3)  装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。 
    4)  具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。 
        看下装饰模式的类图: 

                                    Compone 
                                        nt 


                                   defaultMethod() 


           ConcreteComponent                       Decorator 


          defaultMethod()                         defaultMethod() 


                               ConcreteDecoratorA            ConcreteDecoratorB 
                               addedState : int              addedState : int 


                               defaultMethod()               defaultMethod() 
                               addedBehaviour()              addedBehaviour() 
                               setState()                    setState() 
                               getState()                    getState() 


        图中ConcreteComponent 可能继承自其它的体系,而为了实现装饰模式,他还要实现 
    Component  接口。整个装饰模式的结构是按照组合模式来实现的——两者都有类似的结构 
    图,都基于递归组合来组织可变数目的对象。但是两者的目的是截然不同的,组合 
     (Composite)模式侧重通过递归组合构造类,使不同的对象、多重的对象可以“一视同仁”; 
    而装饰(Decorator)模式仅仅是借递归组合来达到定义中的目的。 


    三、举例 
        这个例子还是来自我最近在研究的JUnit,如果你对JUnit 还不太了解,可以浏览一些 
    关于Junit 的文章。不愧是由GoF 之一的Erich   Gamma 亲自开发的,小小的东西使用了N 
    种的模式在里面。下面就来看看JUnit 中的装饰模式。 
        在JUnit 中,TestCase 是一个很重要的类,允许对其进行功能扩展。 
        在junit.extensions 包中,TestDecorator、RepeatedTest 便是对TestCase 的装饰模式 
    扩展。下面我们将它们和上面的角色对号入座。 

                                       Test 


                        TestCase                TestDecorator 


                                                 RepeatedTest 


    呵呵,看看源代码吧,这个来的最直接! 
     //这个就是抽象构件角色 


     public interface Test { 
         /** 
           * Counts the number of test cases that will be run by this test. 
           */ 
         public abstract int countTestCases(); 
         /** 
           * Runs a test and collects its result in a TestResult instance. 
           */ 
         public abstract void run(TestResult result); 



    //具体构件对象,但是这里是个抽象类 


    public abstract class TestCase extends Assert implements Test { 
          …… 
          public int countTestCases() { 
               return 1; 
          } 
          …… 
          public TestResult run() { 
               TestResult result= createResult(); 
               run(result); 
               return result; 
          } 
          public void run(TestResult result) { 
               result.run(this); 
          } 
          …… 



       //装饰角色 


       public class TestDecorator extends Assert implements Test { 
              //这里按照上面的要求,保留了一个对构件对象的实例 


              protected Test fTest; 


              public TestDecorator(Test test) { 
                   fTest= test; 
              } 
              /** 
               * The basic run behaviour. 
               */ 
              public void basicRun(TestResult result) { 
                   fTest.run(result); 
              } 
              public int countTestCases() { 
                   return fTest.countTestCases(); 
              } 
              public void run(TestResult result) { 
                   basicRun(result); 
              } 
              public String toString() { 
                   return fTest.toString(); 
              } 
              public Test getTest() { 
                   return fTest; 
              } 
       } 


        //具体装饰角色,这个类的增强作用就是可以设置测试类的执行次数 


    public class RepeatedTest extends                TestDecorator { 
        private int fTimesRepeat; 


        public RepeatedTest(Test test, int repeat) { 
             super(test); 
             if (repeat < 0) 
                   throw new IllegalArgumentException("Repetition count must be > 0"); 
             fTimesRepeat= repeat; 
        } 
        //看看怎么装饰的吧 


        public int countTestCases() { 
             return super.countTestCases()*fTimesRepeat; 
        } 
        public void run(TestResult result) { 

            for (int i= 0; i < fTimesRepeat; i++) { 
                if (result.shouldStop()) 
                    break; 
                super.run(result); 
            } 
         } 
         public String toString() { 
            return super.toString()+"(repeated)"; 
         } 
      } 
        使用的时候,就可以采用下面的方式: 


        TestDecorator test = new RepeatedTest(new TestXXX() , 3); 
        让我们在回想下上面提到的“ 门”的问题,这个警报门采用了装饰模式后,可以采用下面 
    的方式来产生。 


        DoorDecorator alarmDoor = new AlarmDoor(new Door()); 


    四、应用环境 
         GOF 书中给出了以下使用情况: 
    1)  在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 
    2)  处理那些可以撤消的职责。 
    3)  当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持 
        每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类 
        定义被隐藏,或类定义不能用于生成子类。 
        来分析下JUnit 的使用是属于哪种情况。首先实现了比静态继承更加灵活的方式,动态 
    的增加功能。试想为Test  的所有实现类通过继承来增加一个功能,意味着要添加不少的功 
    能类似的子类,这明显是不太合适的。 
        而且,这就避免了高层的类具有太多的特征,比如上面提到的带有警报的抽象门类。 


    五、透明和半透明 
        对于面向接口编程,应该尽量使客户程序不知道具体的类型,而应该对一个接口操作。 
    这样就要求装饰角色和具体装饰角色要满足Liskov 替换原则。像下面这样: 


        Component c = new ConcreteComponent(); 
        Component c1 = new ConcreteDecorator(c); 
        JUnit 中就属于这种应用,这种方式被称为透明式。而在实际应用中,比如java.io 中往 
    往因为要对原有接口做太多的扩展而需要公开新的方法(这也是为了重用)。所以往往不能 
    对客户程序隐瞒具体的类型。这种方式称为“半透明式”。 


        在java.io 中,并不是纯装饰模式的范例,它是装饰模式、适配器模式的混合使用。 


    六、其它 
        采用Decorator 模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅 
    在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于 
    那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困 
    难。这是GOF 提到的装饰模式的缺点,你能体会吗?他们所说的小对象我认为是指的具体 

    装饰角色。这是为一个对象动态添加功能所带来的副作用。 

    下载:

    http://download.csdn.net/detail/undoner/5335717

    深入浅出设计模式-中文版

    http://www.lsoft.cn

    LSOFT.CN (琅软中国) - 创造玉石般的软件,完美的用户体验!


  • 相关阅读:
    【正视CSS 03】block与position,出门在外的朋友端午节快乐
    【正视CSS 07】再看verticalalign
    【正视CSS 06】构建我们自己的世界观!
    【正视CSS 02】inline与lineheight、float、absolute的故事
    【初窥javascript奥秘之闭包】叶大侠病都好了,求不踩了:)
    【javascript面向对象之路】让我们一起来坦克大战吧01
    Python学习之三:《Dive in Python》学习笔记二
    恢复GMail选择栏(All None Read Unread….)的油猴(GreaseMonkey)脚本
    群聊天时学习到的两个JS知识(变量范围,Foreach顺序)
    利用宏自动附加到WebServer进程
  • 原文地址:https://www.cnblogs.com/wuyida/p/6301020.html
Copyright © 2011-2022 走看看