• 设计模式之装饰模式


    设计模式之装饰模式

    装饰模式非常强调实现技巧,我们一般用它应对类体系快速膨胀的情况。

    在项目中,是什么原因导致类型体系会快速膨胀呢?在多数情况下是因为我们经常要为类型增加新的职责(功能),尤其在软件开发和维护阶段,这方面需求更为普遍。

    面向对象中每一个接口代表我们看待对象的一个特定方面。在Java编码实现过程中由于受到单继承的约束,我们通常也会将期望扩展的功能定义为新的接口,进而随着接口不断增加,实现这些接口的子类也在快速膨胀,如新增3个接口的实现,就需要8个类型(包括MobileImpl),4个接口则是16个类型,这种几何基数的增长我们承受不了。为了避免出现这种情况,之前我们会考虑采用组合接口的方式解决,但客户程序又需要从不同角度看待组合后的类型,也就是可以根据里氏替换原则用某个接口调用这个子类。所以面临的问题是,既要has a、又要is a,装饰模式解决的就是这类问题。

    经典回顾

    装饰模式的意图非常明确:动态为对象增加新的职责。

     

    这里有两个关键词:动态和增加,也就是说,这些新增的功能不是直接从父类继承或是硬编码写进去的,而是在运行过程中通过某种方式动态组装上去的。例如:我们在录入文字的时候,最初只需要一个Notepad功能的软件,然后增加了很多需求

    字体可以加粗。

    文字可以显示为不同颜色。

    字号可以调整。

    字间距可以调整。

    ……

    不仅如此,到底如何使用这些新加功能,需要在客户使用过程中进行选择,也就是说,新的职责(功能或成员)是需要动态增加的。为了解决这类问题,装饰模式给出的解决方案如图12-1所示。

     

     

    根据Notepad的示例要求,设计如图12-2所示的静态结构。

     

     

     

    区别于经典装饰模式设计,在图12-2中我们将Decorator定义为具体类,而不是抽象类,主要是为了简化示例结构。

    首先,我们需要的是显示文字,这时可以指定一个名为Text的接口,它有名为Content的读/写属性。

    然后,我们把所有需要用来装饰的类型抽象出一个名为Decorator的接口,它继承自这个Text,因此其实体类必须实现Content属性方法。

    接着,我们把没有任何新增功能的Text做出一个“毛坯”的实体类型,命名为TextImpl

    最后,我们把操作字体bold()setColor()的方法填充到每个具体的装饰类型中。

    这样,概念上当TextImpl需要某种新增功能时,直接为其套上某个具体装饰类型就可以了。这就好像给TextImpl类穿上一层又一层的“马甲”。该模式这么做主要是为了适用于哪些情景呢?

    在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

    毕竟客户程序依赖的仅仅是Component接口,至于这个接口被做过什么装饰,只有实施装饰的对象才知道,而客户程序只是依据Component的接口方法调用它,这样,装饰类在给Component穿上新“马甲”的同时也会随着客户程序的调用一并执行了。

    屏蔽某些职责,也就是在套用某个装饰类型时,并不增加新的特征,只把既有方法屏蔽。

    也就是说,装饰类不仅能否充当“马甲”,也能起到“口罩”的作用,让Component现有的某些方法“闭嘴”。尽管我们使用装饰模式一般是为了增加功能(做“加法”),但并不排斥它也具有方法屏蔽的作用(做“减法”),只不过平时用的比较少而已。

    避免因不断调整职责导致类型快速膨胀的情况。

    下面看一段示例代码:

    Java  抽象部分

    public interface Text {

        String getContent();

        void setContent(String content);

    }

     

    /** implements Text 说明Decorator is a Text*/

    public class Decorator implements Text{

     

        /** 构造方式注入has atext接口 */

        private Text text;

        public Decorator(Text text){

            this.text = text;

        }

     

        @Override

        public String getContent(){

            return text.getContent();

        }

       

        @Override

        public void setContent(String content){

            text.setContent(content);  

        }

    }

    Java  具体装饰类型

     

    /**

     * 具体装饰类,属于“马甲”

     */

    class BoldDecorator extends Decorator{

       

        public BoldDecorator(Text text){

            super(text);

        }

     

        public String bold(String data){

            return "<b>" + data + "</b>";

        }

       

        @Override

        public String getContent() {   

            return bold(super.getContent());

        }

     

        @Override

        public void setContent(String content) {

            super.setContent(content);

        }

       

    }

     

    /**

     * 具体装饰类

     * 属于“马甲”

     */

    class ColorDecorator extends Decorator{

       

        public ColorDecorator(Text text){

            super(text);

        }

       

        public String setColor(String data){

            return "<color>" + data + "</color>";  

        }

     

        @Override

        public String getContent() {   

            return setColor(super.getContent());

        }

     

        @Override

        public void setContent(String content) {

            super.setContent(content);

        }

       

    }

     

    /**

     * 具体装饰类

     * 属于“口罩”

     */

    class BlockAllDecorator  extends Decorator{

       

        public BlockAllDecorator (Text text){

            super(text);

        }

     

        @Override

        public String getContent() {   

            return null;

        }

     

        @Override

        public void setContent(String content) {

            super.setContent(content);

        }

       

    }

    Unit Test

    Text text;

     

    @Before

    public void setUp(){

        text = new TextImpl();

    }

     

    /** 验证套用单个装饰对象的效果 */

    @Test

    public void testSingleDecorator(){

        text = new Decorator(text);

       

        //下面开始套用装饰模式,开始给text穿“马甲”

        text = new BoldDecorator(text);

        text.setContent("H");

        assertEquals("<b>H</b>", text.getContent());

    }

     

    /** 验证套用多个装饰对象的效果 */

    @Test

    public void testMultipleDecorators(){

        text = new Decorator(text);

       

        //下面开始套用装饰模式,开始给text穿“马甲”

        text = new BoldDecorator(text);

        text = new ColorDecorator(text);

        text = new BoldDecorator(text);

        text.setContent("H");

        assertEquals("<b><color><b>H</b></color></b>",text.getContent());

    }

     

    /** 验证装饰类型的撤销(/“口罩”)效果*/

    @Test

    public void testBlockEffectDecorator(){

        text = new Decorator(text);

       

        //下面开始套用装饰模式,开始给text穿“马甲”

        text = new BoldDecorator(text);

        text = new ColorDecorator(text);

        text.setContent("H");

        assertEquals("<color><b>H</b></color>", text.getContent());

       

        //下面开始套用装饰模式,开始给text戴“口罩”

        text = new BlockAllDecorator(text);

        assertNull(text.getContent());

    }

    从上面的示例中不难看出,装饰模式实现上特别有技巧,也很“八股”,它的声明要实现Component定义的方法,但同时也会保留一个对Component的引用,Component接口方法的实现其实是通过自己保存的Component成员完成的,而装饰类只是在这个基础上增加一些额外的处理。而且,使用装饰模式不仅仅是为了“增加”新的功能,有时候我们也用它“撤销”某些功能。项目中我们有3个要点必须把握:

    Component不要直接或间接地使用Decorator,因为它不应该知道Decorator的存在,装饰模式的要义在于通过外部has a + is a的方式对目标类型进行扩展,对于待装饰对象本身不应有太多要求。

    Decorator也仅仅认识Component抽象依赖于抽象、知识最少。

    某个ConcreteDecorator最好也不知道ComponentImpl的存在,因为ConcreteDecorator只能服务于这个ComponentImpl(及其子类)。

    此外,使用装饰模式解决一些难题的同时,我们也要看到这个模式的缺点:

    开发阶段需要编写很多ConcreteDecorator类型。

    运行时动态组装带来的结果就是排查故障比较困难,从实际角度看,Component提交给客户程序的是最外层ConcreteDecorator的类型,但它的执行过程是一系列ConcreteDecorator处理后的结果,追踪和调试相对困难。

    在实际项目中,我们往往会将一些通用的功能做成装饰类型单独编译,而且一般也鼓励这么做,因为可以减少重复开发,但这样会人为增加排查和调试的难度。好在反编译Java byte code不是太难。

     

    本文节选自《模式——工程化实现及扩展(设计模式Java 版)》一书。

    本书详细信息:

    http://blog.csdn.net/broadview2006/article/details/7450002

     

  • 相关阅读:
    Android Lint简介
    免费HTTP数据抓包Fiddler2[4.6.1.2]以及显示中文包内容的方法
    IE6、7下bug
    图表插件
    学习:使用svg
    jQuery Transit
    jQuery基础学习笔记(1)
    HTTP协议详解学习
    html5学习笔记
    html释疑
  • 原文地址:https://www.cnblogs.com/broadview/p/2442560.html
Copyright © 2020-2023  润新知