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的装饰者抽象类:

ContractedBlock.gif ExpandedBlockStart.gif PersonDecorator.js
function PersonDecorator(person) {
    
this.person = person;
    
this.interface = IPerson;
    
for(var key in this.person) 
    {
        
if(typeof this.person[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.person[methodName]();
            };
        })(key);
    }
    Interface.registerImplements(
this, IPerson);
}
PersonDecorator.prototype 
= {
    getData : 
function() {
        
throw new Error("抽象方法,不能调用!");
    }
}

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

ContractedBlock.gif ExpandedBlockStart.gif ConcreteDecorator.js
// 头像装饰者
function HeadDecorator(person, model) {
    PersonDecorator.call(
this, person);
    
this.model = model;
}
inheritClass(HeadDecorator, PersonDecorator);
HeadDecorator.prototype.getData 
= function() {
    
var index = this.person.head.lastIndexOf("_");
    
if(index != -1)
        
this.person.head = this.person.head.slice(0, index);
    
this.person.head = this.person.head + "_" + this.model;
    
return this.person;
};

// 上衣装饰者
function BodyDecorator(person, model) {
    PersonDecorator.call(
this, person);
    
this.model = model;
}
inheritClass(BodyDecorator, PersonDecorator);
BodyDecorator.prototype.getData 
= function() {
    
var index = this.person.body.lastIndexOf("_");
    
if(index != -1)
        
this.person.body = this.person.body.slice(0, index);
    
this.person.body = this.person.body + "_" + this.model;
    
return this.person;
};

// 腰裤装饰者
function FootDecorator(person, model) {
    PersonDecorator.call(
this, person);
    
this.model = model;
}
inheritClass(FootDecorator, PersonDecorator);
FootDecorator.prototype.getData 
= function() {
    
var index = this.person.foot.lastIndexOf("_");
    
if(index != -1)
        
this.person.foot = this.person.foot.slice(0, index);
    
this.person.foot = this.person.foot + "_" + this.model;
    
return this.person;
};

// 背景装饰者
function BackgroundDecorator(person, model) {
    PersonDecorator.call(
this, person);
    
this.model = model;
}
inheritClass(BackgroundDecorator, PersonDecorator);
BackgroundDecorator.prototype.getData 
= function() {
    
this.person.background = "bg" + this.model;
    
return this.person;
};

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) - 面向对象基础以及接口和继承类的实现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值