zoukankan      html  css  js  c++  java
  • Javascript乱弹设计模式系列(3) - 装饰者模式(Decorator)

    前言

    博客园谈设计模式的文章很多,我也受益匪浅,包括TerryLee吕震宇等等的.NET设计模式系列文章,强烈推荐。对于我,擅长于前台代码的开发,对于设计模式也有一定的了解,于是我想结合Javascript来设计前台方面的“设计模式”,以对后台“设计模式”做个补充。开始这个系列我也诚惶诚恐,怕自己写得不好,不过我也想做个尝试,一来希望能给一些人有些帮助吧,二来从写文章中锻炼下自己,三来通过写文章对自己增加自信;如果写得不好,欢迎拍砖,我会虚心向博客园高手牛人们学习请教;如果觉得写得还可以,谢谢大家的支持了:)

    概述

    在软件开发中,经常因为需求变动而需要扩展功能,想到的可能是继承类的方式进行扩展(还可以是组合方式(这个别篇另谈)),但是随着功能越来越多,子类也会越来越多,这样很不利用维护;而且需求的变动还导致部分功能也许不再需要,所以能够按需求来动态地扩展所需要的功能,是很好的解决方式;这样,装饰者模式应油而生,它动态地解决了功能扩展的问题

    这里借用《Head First Design Pattern》归纳的五点:

    • 装饰者和被装饰者对象有相同的超类型
    • 你可以用一个或是多个装饰者包装一个对象
    • 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,可以用装饰过的对象代替它
    • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的
    • 对象可以在任何时候被装饰,所以可以在运行是动态地、不限量地用你喜欢的装饰者来装饰对象

    定义

    装饰者模式是动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

    类图

    原理分析

    现在结合Javascript,开始介绍Javascript装饰者模式,这里我举个多功能复印机的例子来说明:

    1. 首先最开始我定义一个复印机接口,它会什么呢?它有说明书,要插电,可查看时间,可修理,可进行复印,有价格

    那就可以这样子得到:

    var IManifdder = new Interface("IManifdder", [["getDescription"],["electrify"],["watch"],["repair"],["duplicate"],["getPrice"]]);



    2. 然后定义一个继承于该复印机接口的类Manifdder:

    function Manifdder() {
        Interface.registerImplements(
    this, IManifdder);
    }
    Manifdder.prototype 
    = {
        getDescription : 
    function() {
            
    //
        },
        electrify : 
    function() {
            
    //
        },
        watch : 
    function() {
            
    //
        },
        repair : 
    function() {
            
    //
        },
        duplicate : 
    function() {
            
    //打印操作
        },
        getPrice : 
    function() {
            
    return 500.0;
        }
    };

    3. 接着定义一个继承复印机接口的装饰者抽象类:

    function ManifdderDecorator(manifdder) {
        
    this.manifdder = manifdder;
        Interface.registerImplements(
    this, IManifdder);
    }
    ManifdderDecorator.prototype 
    = {
        getDescription : 
    function() {
            
    return this.manifdder.getDescription();
        },
        electrify : 
    function() {
            
    return this.manifdder.electrify();
        },
        watch : 
    function() {
            
    return this.manifdder.watch();
        },
        repair : 
    function() {
            
    return this.manifdder.repair();
        },
        duplicate : 
    function() {
            
    return this.manifdder.duplicate();
        },
        getPrice : 
    function() {
            
    return this.manifdder.getPrice();
        }
    };

    每个装饰者都有一个变量来保存IManifdder的引用,现在来实现它的具体类:

    //中国制造
    function MakeInChinaDecorator(manifdder) { 
        ManifdderDecorator.call(
    this,manifdder);
    }
    inheritClass(MakeInChinaDecorator, ManifdderDecorator);
    MakeInChinaDecorator.prototype.electrify 
    = function() {
        
    return this.manifdder.electrify() + ". 中国标准电压为220V.";
    };
    MakeInChinaDecorator.prototype.getPrice 
    = function() {
        
    return this.manifdder.getPrice() + 100.0;
    };

    //日本制造
    function MakeInJapanDecorator(manifdder) { 
        ManifdderDecorator.call(
    this,manifdder);
    }
    inheritClass(MakeInJapanDecorator);
    MakeInJapanDecorator.prototype.electrify 
    = function() {
        
    return this.manifdder.electrify() + ".日本标准电压为110V.";
    };
    MakeInJapanDecorator.prototype.getPrice 
    = function() {
        
    return this.manifdder.getPrice() + 200.0;
    };

    其中它覆盖了抽象类中的electrify和getPrice方法;

    现在执行它们:

    var manifdder = new Manifdder();
    alert(manifdder.getPrice());  
    // 得到500.0
    manifdder = new MakeInChinaManifdder(manifdder);
    alert(manifdder.getPrice()); 
    // 得到600.0

    如果再需要添加一个具体装饰类MakeInAmericaDecorator,只要再定义一个继承抽象类的子类即可;


    3. 什么叫做被装饰者方法之前或者之后,添加行为呢?接着看下下面的例子:

    在方法之后添加行为:

    function MakeInChinaDecorator(manifdder) {
        
    //
    }
    inheritClass(MakeInChinaDecorator, ManifdderDecorator);
    //
    MakeInChinaDecorator.prototype.getPrice = function() {
        
    return this.manifdder.getPrice() + 100.0;
    };

    实际上刚才上面的那个例子,就是方法之后添加行为的方式,当实例化MakeInChinaDecorator对象的时候,它的装饰目的是为了价格上的差异,当调用getPrice(方法之后)的时候,价格上才得以体现出来,在原来价格的基础上加上100.0。

    在方法之前添加行为,我这里在增加一个复印机的颜色装饰类:

    function ColorDecorator(manifdder, color) {
        ManifdderDecorator.call(
    this,manifdder);
        
    this.color = color;
    }
    inheritClass(ColorDecorator, ManifdderDecorator);
    ColorDecorator.prototype.getDescription 
    = function() {
        
    return this.manifdder.getDescription() + "" + "它的颜色是" + this.color;
    };

    当实例化ColorDecorator对象的时候,它的装饰目的已经在构造函数中得到颜色上的体现,因此它是在getDescription()方法调用之前就得到体现了。

    现在执行它:

    manifdder = new ColorDecorator(manifdder, "白色");
    alert(manifdder.getDescription()); 
    //得到“它的颜色是白色”


    4. 现在我要实现多功能复印机,还具有打印,扫描,传真功能,那么开始添加它们的装饰类:

    function PrintDecorator(manifdder) {
        ManifdderDecorator.call(
    this,manifdder);
    }
    inheritClass(PrintDecorator, ManifdderDecorator);
    PrintDecorator.prototype.getDescription 
    = function() {
        
    return this.manifdder.getDescription() + "" + "它具有打印功能. ";
    };
    PrintDecorator.prototype.getPrice 
    = function() {
        
    return this.manifdder.getPrice() + 220.0;
    };
    PrintDecorator.prototype.print 
    = function() {
        
    return "Print!";
    };

    现在执行它:

    var manifdder = new Manifdder();
    manifdder 
    = new PrintDecorator(manifdder);
    alert(manifdder.print()); 
    //得到“Print!”

    从结果可以看出,manifdder引用的对象具有方法print;

    如果我继续装饰一个MakeInChina:

    var manifdder = new Manifdder();
    manifdder 
    = new PrintDecorator(manifdder);
    manifdder 
    = new MakeInChinaDecorator(manifdder);
    alert(manifdder.print()); 
    //得到“manifdder.print is not a function”

    从结果可以看出,print已经找不到了。

    这说明MakeInChinaDecorator装饰manifdder的时候,它的父类ManifdderDecorator不包含print方法,所以自然就调用不到;

    这个问题要如何解决呢?我们看到ManifdderDecorator的构造函数传进来一个manifdder的引用,通过它的引用我们可以得到它可能含有的新方法,比如print,通过和接口方法的比较,将新进的方法,动态添加进ManifdderDecorator中,于是我们修改ManifdderDecorator抽象类,如下所示:

    function ManifdderDecorator(manifdder) {
        
    this.manifdder = manifdder;

        
    this.interface = IManifdder;
        
    for(var key in this.manifdder) 
        {
            
    if(typeof this.manifdder[key] !== "function")  //判断是否为方法类
                continue;
            
    var i;
            
    for(i = 0, len = this.interface.methods.length; i < len; i++) {
                
    if(key == this.interface.methods[i][0]) { //通过遍历比较在接口类中是否包含此方法,如果包含返回下一个
                    break;
                }
            }
            
    if(i < this.interface.methods.length)
                
    continue;
            
    var decorator = this;
            
    //采用匿名函数调用方式来定义新方法
            (function(methodName) {
                decorator[methodName] 
    = function() {
                    
    return decorator.manifdder[methodName]();
                };
            })(key);
        }

        Interface.registerImplements(
    this, IManifdder);
    };
    ManifdderDecorator.prototype 
    = {
        
    //
    };

    这样,如果在装饰类中定义了新方法,它的抽象类就会把该新方法动态定义出来,这样保证方法调用上的正确;

    然后再执行刚才的代码:

    var manifdder = new Manifdder();
    manifdder 
    = new PrintDecorator(manifdder);
    manifdder 
    = new MakeInChinaDecorator(manifdder);
    alert(manifdder.print()); 
    //得到“Print!”

    成功调用了!

    其他扫描,传真功能,和打印功能类似,创建ScanDecorator和FaxDecorator,这里不在多说了。


    实例分析

    今天要介绍的场景是个 个人服装秀(类似于QQ秀)的网页范例,可以更换头像,上衣,腰裤,背景等。

    效果图先让大家瞧瞧:

    这里介绍几个核心代码:

    1. 添加IPerson.js的IPerson接口类:

    var IPerson = new Interface("IPerson", [["getData"]]);

    2. 添加PersonDecorator.js的装饰者抽象类:

    PersonDecorator.js

    3. 添加ConcreteDecorator.js的具体装饰者:

    ConcreteDecorator.js

    4. 添加PersonOpr.js,创建操作类

    var PersonOpr = {
        display : 
    function(person) { //显示个人秀照片
             $("#imgHead").attr("src","images/" + person.head + ".gif");
            $(
    "#imgBody").attr("src","images/" + person.body + ".gif");
            $(
    "#imgFoot").attr("src","images/" + person.foot + ".gif");
            $(
    "#divBg").css("background-image""url(images/" + person.background + ".gif)");
        },
        init : 
    function() {
            
    //初始化皮肤列表
            //...

        }
    }

    至此,装饰者模式的思路已经应用在该个人服装秀中了。

    这里实现链接实例:

    装饰者模式Demo

    源代码就不提供下载了,无非就是html,js,css文件,从链接实例中可以查看源代码;

    总结

    该篇文章用Javascript设计装饰者模式的思路,重点从原理中分析,最后实现一个类似于QQ秀的应用网站。

    本篇到此为止,谢谢大家阅读

    参考文献:《Head First Design Pattern》
    《Professional Javascript Design Patterns》
    本系列文章转载时请注明出处,谢谢合作!

     相关系列文章:
    Javascript乱弹设计模式系列(6) - 单件模式(Singleton)
    Javascript乱弹设计模式系列(5) - 命令模式(Command)
    Javascript乱弹设计模式系列(4) - 组合模式(Composite)
    Javascript乱弹设计模式系列(3) - 装饰者模式(Decorator)
    Javascript乱弹设计模式系列(2) - 抽象工厂以及工厂方法模式(Factory)
    Javascript乱弹设计模式系列(1) - 观察者模式(Observer)
    Javascript乱弹设计模式系列(0) - 面向对象基础以及接口和继承类的实现

  • 相关阅读:
    JMeter 分布式调度压测部署
    jmeter 命令行运行与生成报告
    Apache启动报错:Invalid command 'AuthType', perhaps misspelled or defined by a module not included in it
    【TestNG】TestNG使用教程详解
    这才是Tomcat内存配置的正确姿势
    Tomcat GC参数详解
    MANIFEST.MF文件详解
    CMD命令打包文件夹成jar
    常用MIME类型
    表单序列化
  • 原文地址:https://www.cnblogs.com/liping13599168/p/1373677.html
Copyright © 2011-2022 走看看