设计模式学习

2.0 策略模式

策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开。

策略模式的目的就是将算法的使用与算法的实现分离开来。

在传统的面向对象的语言中,策略模式如何实现呢?

2.1 Java实现

  • 抽象策略角色

    public interface IStrategy {
        void trip(); // 出行的策略
    }
    
  • 具体的策略角色

    // 乘飞机
    public class AirStrategy implements IStrategy {
        @Override
        public void trip() {
            System.out.println("乘坐飞机!");
        }
    }
    
    // 乘火车
    public class TrainStrategy implements IStrategy {
        @Override
        public void trip() {
            System.out.println("乘坐火车!");
        }
    }
    
    // 步行
    public class WalkStrategy implements IStrategy {
        @Override
        public void trip() {
            System.out.println("步行!");
        }
    }
    
  • 创建环境角色

    // 用于与具体的策略进行交互,分离了算法与客户端的调用,
    // 使得算法独立于客户端,方便算法策略的切换。
    public class Context {
        private IStrategy iStrategy;
        
        public Context(IStrategy iStrategy) {
            this.iStrategy = iStrategy;
        }
        
        // 具体业务逻辑
        public void tripType() {
            iStrategy.trip();
        }
    }
    
  • 客户端调用

    public class StrategyClient {
        public static void main(String[] args) {
            // 策略角色
            IStrategy strategy = new AirStrategy();
            // 环境角色
            Context c = new Context(strategy);
            // 调用算法
            c.tripType();
        }
    }
    

因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。

而在 JavaScript 这种 类型模糊 的语言中没有这种困扰,任何对象都可以被替换使用。

所以使用JavaScript如何实现上述策略模式呢?

2.2 JavaScript实现

// 把strategy定义为函数
var strategies = {
    "Air": function() {
        console.log("乘坐飞机!");
    },
    "Train": function() {
        console.log("乘坐火车!");
    },
    "Walk": function() {
        console.log("步行!");
    }
}
// 上下文对象
var strategyContext = function(type) {
    return strategies[type]();
}

// 调用具体的策略
strategyContext("Walk");

使用策略模式,我们消除了程序中大片的条件分支语句。

通过Context将职责委托给某个策略对象,每个策略对象负责的算法已被各自封装在对象内部。当我们调用具体的策略时,它们会返回各自不同的计算结果,这正是对象多态性的体现,也是“它们可以相互替换”的目的。

// 这也是一种策略模式
var S = function( salary ){
 return salary * 4;
};
var A = function( salary ){
 return salary * 3;
};
var B = function( salary ){
 return salary * 2;
};
var calculateBonus = function( func, salary ){
 return func( salary );
};
calculateBonus( S, 10000 ); // 输出:40000 

在 JavaScript 中,除了使用类来封装算法和行为之外,使用函数当然也是一种选择。这些“算法”可以被封装到函数中并且四处传递,也就是我们常说的“高阶函数”。实际上在 JavaScript 这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函数发出“调用”的消息时,不同的函数会返回不同的执行结果。在 JavaScript 中,“函数对象的多态性”来得更加简单。

3.0 代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理模式可分为:

  • 保护代理: 过滤请求,如果不满足条件,直接在代理中被拒绝掉。
  • 虚拟代理: 虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。

3.1 虚拟代理实现图片预加载

实体对象:

// myImage对象拥有一个setSrc方法
var myImage = (function() {
    var imageNode = document.createElement('img');
    document.body.appendChild(imageNode);
    return {
        setSrc: function(src) {
            imageNode.src = src;
        }
    }
})();

代理对象:

var proxyImage = (function() {
    var img = new Image();
    img.onload = function() {
        // 实体对象设置实际图片
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            // 实体对象设置菊花图
            myImage.setSrc('loading.gif');
            img.src = src;
        }
    }
})();

实际调用:

proxyImage.setSrc('http://xx/music/xxx.jpg');

分析以上过程,实际有两个行为:给image设置src、图片预加载。

在不改变或者增加 myImage 的接口情况下,通过代理对象,给系统添加了新的行为,这是符合开放-封闭原则的。

虚拟代理还有什么其他应用吗?

  • 虚拟代理合并HTTP请求
  • 虚拟代理实现惰性加载

3.2 高阶函数动态创建代理

创建缓存代理工厂

var mult = function() {
  var a = 1;
  for(var i = 0, l = arguments.length; i < l; i++) {
    a = a * arguments[i];
  }
  return a;  
}

var createProxyFactory = function(fn) {
  var catches = {};
  return function() { // proxy
    var key = [].join.call(arguments, ',');
    if(key in catches) {
        return catches[key];
    }
    return catches[key] = fn.apply(this,arguments);  
  }
}

var proxyMult = createProxyFactory(mult);
proxyMult(2,4,6);

想法:

代理模式和原来的对象提供相同的接口,但代理中加入的新的功能。在使用时,一定要想清楚要在代理中做什么,直接调用原对象有什么缺陷。

在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。

4.0 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象
的内部表示。

那么迭代器又可以分为 内部迭代器外部迭代器

  • 内部迭代器:

    迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

  • 外部迭代器:

    外部迭代器必须显式地请求迭代下一个元素。

5.0 发布-订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型来替代传统的发布—订阅模式。

优点: 模块解耦合。

**缺点:模块与模块之间的联系就被隐藏到了背后,给维护带来了一些麻烦。 **

思考两个问题:

  • 必须先订阅吗?
  • 全局事件命名冲突怎么解决?

5.1 Java实现

// 观察者接口
public interface IObserver {	
    
	public void update(String arg);	
	
    public void applyRegisterSubject(ISubject subject);
}

// 主题接口
public interface ISubject {
	
	public void registObserver(IObserver observer);
	
	public void removeObserver(IObserver observer);
	
	public void notifyObservers();
}

// 创建一个微博主题
public class WeiBoSubject implements ISubject {

	private String content;
	private List<IObserver> observerList;

	public WeiBoSubject() {
		observerList = new ArrayList<IObserver>();
	}

	@Override
	public void registObserver(IObserver observer) {
		observerList.add(observer);
	}

	@Override
	public void removeObserver(IObserver observer) {
		int index = observerList.indexOf(observer);
		if (index >= 0) observerList.remove(index);
	}

	@Override
	public void notifyObservers() {
		observerList.forEach(item -> item.update(content));
	}

	public void sendWeibo(String content) {
		this.content = content;
		notifyObservers();
	}

}

// 观察者-Tom
public class ObserverTom implements IObserver {

	@Override
	public void update(String arg) {
		System.out.println("Tom 关注 内容更新:" + arg);
	}

	@Override
	public void applyRegisterSubject(ISubject subject) {
		subject.registObserver(this);
	}

}

// 观察者-Ana
public class ObserverAna implements IObserver {

	@Override
	public void update(String arg) {
		System.out.println("Ana 关注 内容更新:" + arg);
	}

	@Override
	public void applyRegisterSubject(ISubject subject) {
		subject.registObserver(this);
	}

}

// 测试
public class test {
	
	public static void main(String[] args) {
		// 微博主题
		WeiBoSubject weiBoSubject = new WeiBoSubject();
		
		// 创建Tom 申请订阅
		IObserver tom = new ObserverTom();
		tom.applyRegisterSubject(weiBoSubject);
		
		// 创建Ana 申请订阅
		IObserver ana = new ObserverAna();
		ana.applyRegisterSubject(weiBoSubject);
		
		// 微博发送消息
		weiBoSubject.sendWeibo("微博更新...");
	}
	
}

输出:
Tom 关注 内容更新:微博更新...
Ana 关注 内容更新:微博更新...

6.0 命令模式

命令模式中的命令(command)指的是一个执行某些特定事情的指令。消除请求发送者和接受者之间的耦合。

除了这两点之外,命令模式还支持撤销、排队等操作。

例如:

点击按钮会执行某个command命令,约定调用command对象的execute()方法。

var setCommand = function(btn, command) {
    btn.onclick = function() {
        command.execute();
    }
}

具体的功能:

var MenuBar = {
    refresh: function() {
        console.log('刷新菜单目录...');
    }
}

封装命令类:

var RefreshMenuBarCommand = function(receiver) {
    this.receiver = receiver;
}
RefreshMenuBarCommand.prototype.execute = function() {
    this.receiver.refresh();
}

绑定按钮:

var refreshMenuBarCommand = new RefreshMenuBarCommand( MenuBar ); 

setCommand(btn1, refreshMenuBarCommand);

以上是模拟传统面向对象语言的命令模式实现。

命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品。

利用 闭包 实现:

var RefreshMenuBarCommand = function(receiver) {
    return {
        execute: function() {
            receiver.refresh();
        }
    }
}

var setCommand = function( button, command ) {
     button.onclick = function() {
     	command.execute();
     }
}

var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar ); 
setCommand(btn1, refreshMenuBarCommand);

6.1 撤销和重做

利用命令模式,可以方便的实现撤销功能,记录命令日志 甚至能实现撤销一系列命令。或是实现播放录像功能。

// 实际需要执行的函数
var Ryu = {
    attack: function() {},
    jump: function() {},
    ......
}
// 创建命令
var makeCommand = function(receiver, state) {
    return function() {
        return receiver[state]();
    }
}

// 键值映射
var commands = {
    "119": "jump",
    "115": "attack",
    ......
}

var commandStack = [];    
    
document.onkeypress = function(ev) {
    var keyCode = ev.keyCode,
        command = makeCommand(Ryu, commands[keyCode]);
    if(command) {
        command(); // 执行
        commandStack.push(command); // 保存进堆栈中
    }
}    

// 当我们需要播放录像时,只需要从堆栈中取出依次执行即可
while(command = commandStack.shift()) { 
	command();
}

6.2 组合模式实现宏命令

var MacroCommand = function() {
    var commandsList = [];
    return {
        add: function(command) {
            commandsList.push(command);
        },
        execute: function() {
            for(var i = 0, command; command = commandsList[i++]; ) {
                command.execute();
            }
        }
    }
}

跟许多其他语言不同,JavaScript 可以用高阶函数非常方便地实现命令模式。命令模式在 JavaScript 语言中是一种隐形的模式。

7.0 组合模式

组合模式将对象组合成树形结构,以表示 ”部分-整体” 的层次结构。

除此之外,利用对象多态性统一对待组合对象和单个对象。

请求从上到下沿着树进行传递,直到树的尽头。作为客户,只需要关心树最顶层的组合对象,客户只需要请求这个组合对象,请求便会沿着树往下传递,依次到达所有的叶对象。完成这棵树的深度优先搜索。

7.1 例子 - 扫描文件夹

/******************************* Folder ******************************/
var Folder = function( name ){
 this.name = name;
 this.files = [];
};
Folder.prototype.add = function( file ){
 this.files.push( file );
};
Folder.prototype.scan = function(){
 console.log( '开始扫描文件夹: ' + this.name );
 for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
  file.scan();
 }
};
/******************************* File ******************************/
var File = function( name ){
 this.name = name;
};
File.prototype.add = function(){
 throw new Error( '文件下面不能再添加文件' );
}; 
File.prototype.scan = function(){
 console.log( '开始扫描文件: ' + this.name );
}; 

var folder = new Folder( '学习资料' );
var folder1 = new Folder( 'JavaScript' );
var folder2 = new Folder ( 'jQuery' );
var file1 = new File( 'JavaScript 设计模式与开发实践' );
var file2 = new File( '精通 jQuery' );
var file3 = new File( '重构与模式' )
folder1.add( file1 );
folder2.add( file2 );
folder.add( folder1 );
folder.add( folder2 );
folder.add( file3 ); 

var folder3 = new Folder( 'Nodejs' );
var file4 = new File( '深入浅出 Node.js' );
folder3.add( file4 );
var file5 = new File( 'JavaScript 语言精髓与编程实践' );
// 接下来就是把这些文件都添加到原有的树中:
folder.add( folder3 );
folder.add( file5 ); 

整体树结构:

运用了组合模式之后,扫描整个文件夹的操作也是轻而易举的,我们只需要操作树的最顶端对象:

folder.scan();
开始扫描文件夹: 学习资料
VM3359:10 开始扫描文件夹: JavaScript
VM3359:23 开始扫描文件: JavaScript 设计模式与开发实践
VM3359:10 开始扫描文件夹: jQuery
VM3359:23 开始扫描文件: 精通 jQuery
VM3359:23 开始扫描文件: 重构与模式
VM3359:10 开始扫描文件夹: Nodejs
VM3359:23 开始扫描文件: 深入浅出 Node.js
VM3359:23 开始扫描文件: JavaScript 语言精髓与编程实践

有时候,为了实现某些功能,我们可以让子节点保存父节点的引用。

8.0 模板方法模式

基于继承的设计模式——模板方法(Template Method)模式。

模板方法模式的组成

  • 抽象父类(封装子类的算法框架、方法执行顺序等)
  • 具体子类(继承抽象父类,可以重写父类的方法)

具体例子:Coffee or Tea?

通过钩子方法可以满足某些特殊情况。

Beverage.prototype.init = function(){
 this.boilWater();
 this.brew();
 this.pourInCup();
 if ( this.customerWantsCondiments() ){ 
   // 如果挂钩返回 true,则需要调料
   this.addCondiments();
 }
}; 

对于JS来说,可以不通过继承的方式实现模板方法模式。

var Beverage = function( param ){
 var boilWater = function(){
   console.log( '把水煮沸' );
 };
 var brew = param.brew || function(){
   throw new Error( '必须传递 brew 方法' );
 };
 var pourInCup = param.pourInCup || function(){
   throw new Error( '必须传递 pourInCup 方法' );
 };
 var addCondiments = param.addCondiments || function(){
   throw new Error( '必须传递 addCondiments 方法' );
 };
 var F = function(){};
 F.prototype.init = function(){
   boilWater();
   brew();
   pourInCup();
   addCondiments();
 };
 return F;
}; 

Beverage 这个高阶函数被调用后会返回构造器FF 类中包含了模板方法 F.prototype.init 。该模板方法 封装了饮料子类的算法框架。

// 传入某些方法的具体实现
var Tea = Beverage({
 brew: function(){
  console.log( '用沸水浸泡茶叶' );
 },
 pourInCup: function(){
  console.log( '把茶倒进杯子' );
 },
 addCondiments: function(){
  console.log( '加柠檬' );
 }
});

var tea = new Tea();
tea.init(); // 调用模板方法

9.0 享元模式

享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

享元模式要求将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)。

 内部状态存储于对象内部。
 内部状态可以被一些对象共享。
 内部状态独立于具体的场景,通常不会改变。
 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

定义一个工厂来创建 upload 对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:(共享对象、共享内部状态)

var UploadFactory = (function(){
 var createdFlyWeightObjs = {};
 return {
   create: function( uploadType){
   if ( createdFlyWeightObjs [ uploadType] ){
     return createdFlyWeightObjs [ uploadType];
   }
   return createdFlyWeightObjs [ uploadType] = new Upload( uploadType);
  }
 }
})(); 

享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时便可以使用享元模式。
 一个程序中使用了大量的相似对象。
 由于使用了大量对象,造成很大的内存开销。
 对象的大多数状态都可以变为外部状态。
 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
可以看到,文件上传的例子完全符合这四点。

当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单例工厂。

如果不分离内外部状态,享元变成了一个对象池。

9.1 通用对象池的实现

var objectPoolFactory = function(createObjFn) {
    var objectPool = []; // 对象池
    
    return {
        // 创建对象
    	create: function() {
           var obj = objectPool.length === 0 ?
               createObjFn.apply(this, arguments) : objectPool.shift();
            return obj;
        },
        // 回收对象
        recover: function(obj) {
            objectPool.push(obj);
        }
    }
}

10.0 职责链模式

职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

例子:

  • 支付过 500 元定金的用户会收到 100 元的商城优惠券
  • 支付过 200 元定金的用户可以收到 50 元的优惠券
  • 没有支付定金进入普通购买模式,且库存有限不一定能买到

orderType: 订单类型

pay: 是否支付定金

stock: 库存

那么在尽量不使用if else的情况下,如何编码以上的业务逻辑呢?

答案就是:职责链模式。

10.1 可拆分的职责链节点

var order500 = function(orderType, pay, stock) {
    if(orderType === 1 && pay === true) {
        console.log( '500 元定金预购,得到 100 优惠券' );       
    } else {
        return 'nextSuccessor'; 
        // 我不知道下一个节点是谁,反正把请求往后面传递
    }
}

var order200 = function( orderType, pay, stock ) {
     if ( orderType === 2 && pay === true ){
     	console.log( '200 元定金预购,得到 50 优惠券' );
     }else{
     	return 'nextSuccessor';
     }
};

var orderNormal = function( orderType, pay, stock ) {
     if ( stock > 0 ){
     	console.log( '普通购买,无优惠券' );
     }else{
     	console.log( '手机库存不足' );
     }
}; 

接下来需要把函数包装进职责链节点:

var Chain = function(fn) {
    this.fn = fn;
    this.successor = null;
}
Chain.prototype.setNextSuccessor = function(successor) {
    return this.successor = successor;
}
Chain.prototype.passRequest = function() {
    var ret = this.fn.apply(this, arguments);
    if(ret === 'nextSuccessor') {
        return this.successor && 
         this.successor.passRequest.apply(this.successor, arguments);
    }
}

// 现在我们把 3 个订单函数分别包装成职责链的节点:
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );

// 然后指定节点在职责链中的顺序:
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );

// 最后把请求传递给第一个节点:
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

职责链中节点Chain,持有下一个节点的引用,可以通过当前节点内函数的返回值,判断是否需要将请求传递给下个节点。

10.2 异步职责链

function Chain(fn) {
	this.successor = null;
	this.fn = fn;
}

Chain.prototype.setNextSuccessor = function(successor) {
	return this.successor = successor;
}

Chain.prototype.execute = function() {
	this.fn.apply(this, arguments);
}

Chain.prototype.next = function() {
	this.successor.execute(this.successor, arguments);
}

测试:

var fn1 = new Chain(function() {
	console.log( 1 );
 	return this.next();
});

var fn2 = new Chain(function() {
    console.log( 2 );
    var self = this;
 	setTimeout(() => this.next(), 1000 );
});

var fn3 = new Chain(function() {
	console.log( 3 );
});

fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 );
fn1.execute(); 

通过上一个节点显示的调用next方法,请求才会被传递给下一个节点。

10.3 AOP实现职责链

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

Function.prototype.after = function(nextFn) {
    const fn = this;
    return function() {
        var ret = fn.apply(this, arguments);
        if(ret === 'nextSuccessor') {
            return nextFn.apply(this, arguments);
        }
        return ret;
    }
}

var order = order500.after( order200 ).after( orderNormal );
order( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
order( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券

10.4 优缺点分析

😂优点:

  • 每种情况都有自己的处理函数,互不影响。
  • 节点对象可以自由的拆分重组。
  • 手动指定起始节点。

😱缺点:

  • 不能保证某个请求一定会被链中的节点处理。
  • 职责链过长可能会带来性能损耗。

11.0 中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。

缺点:

最大的缺点是系统中会新增一个中介者对象。

因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。

12.0 装饰者模式

装饰者模式在JS中也可以使用AOP装饰函数去实现。

AOP:AOP是对OOP(面向对象编程)的一个横向的补充,主要作用就是把一些业务无关的功能抽离出来,例如日志打印、统计数据、数据验证、安全控制、异常处理等等。方便模块解耦。

用装饰器函数来实现AOP在实际的编程中还是挺有用的,有利于把逻辑划分成更小粒度的模块,同时也符合函数式编程(FP)的思想。

12.1 JS使用AOP实现装饰者模式

Function.prototype.before = function(beforefn) {
    var _self = this;
    return function() {
        beforefn.apply(this, arguments);
        return _self.apply(this, arguments);
    }
}

Function.prototype.after = function(afterfn) {
    var _self = this;
    return function() {
        var ret = _self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
}

我们可以把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起,这有助于我们编写一个松耦合和高复用性的系统。

例如:

  • 利用AOP实现数据上报
var showLogin = function() {
    console.log('打开登录浮层...');    
}

var log = function(tag) {
    // 真正的上报代码略
    (new Image).src = 'http:// xxx.com/report?tag=' + tag; 
}

// 利用AOP织入逻辑,数据上报
showLogin = showLogin.after(log);
  • 利用AOP动态改变函数的参数
var func = function(params) {
    console.log(params);
}

func = func.before(function(params){
    params.b = 'b';
});

func({a: 'a'});

12.2 Java实现装饰者模式

装饰者可以在所委托的装饰者行为之前或之后加上自己的行为,以达到特定的目的。

java.IO类也使用了装饰器模式:

new ObjectOutputStream(
    new BufferedOutputStream(
        new FileOutputStream( "io.txt" )
    )
);

一般来说装饰者模式有下面几个参与者:

  • Component:装饰者和被装饰者共同的父类,是一个接口或者抽象类,用来定义基本行为
  • ConcreteComponent:定义具体对象,即被装饰者
  • Decorator:抽象装饰者,继承自Component,从外类来扩展ConcreteComponent。对于ConcreteComponent来说,不需要知道Decorator的存在,Decorator是一个接口或抽象类
  • ConcreteDecorator:具体装饰者,用于扩展ConcreteComponent
/**
 * 定义抽象的甜品类
 * 装饰者与被装饰者共同的父类
 * 
 * 方法:
 * 抽象描述
 * 抽象费用
 * @author admin
 *
 */
public abstract class Sweet {
	
	public abstract String description();
	
	public abstract Integer cost();
}




/**
 * 具体的甜品-蛋糕
 * 被装饰者
 * @author admin
 *
 */
public class Cake extends Sweet {

	@Override
	public String description() {
		return "蛋糕";
	}

	@Override
	public Integer cost() {
		return 100;
	}
}



/**
 * 定义抽象装饰者类
 * @author admin
 *
 */
public abstract class Decorator extends Sweet {
	public abstract String description();
}



/**
 * 定义具体的装饰者- 有着蜡烛的蛋糕
 * @author admin
 *
 */
public class CandleDecorator extends Decorator {

	private Sweet sweet;

	public CandleDecorator(Sweet sweet) {
		this.sweet = sweet;
	}

	@Override
	public String description() {
		return sweet.description() + ",蜡烛";
	}

	@Override
	public Integer cost() {
		return sweet.cost() + 20;
	}
}



/**
 * 定义具体的装饰者 - 有着水果的蛋糕
 * @author admin
 *
 */
public class FruitDecorator extends Decorator {

	private Sweet sweet;

	public FruitDecorator(Sweet sweet) {
		this.sweet = sweet;
	}

	@Override
	public String description() {
		return sweet.description() + ",水果";
	}

	@Override
	public Integer cost() {
		return sweet.cost() + 10;
	}
}



public class Test {
	public static void main(String[] args) {
		
		// 普通蛋糕
		Cake cake = new Cake();
		System.out.println(cake.description() + " , " + cake.cost());
		
		// 蜡烛蛋糕
		CandleDecorator candle = new CandleDecorator(cake);		
		System.out.println(candle.description() + " , " + candle.cost());
		
		// 水果蛋糕
		FruitDecorator fruit = new FruitDecorator(cake);
		System.out.println(fruit.description() + " , " + fruit.cost());
		
		// 蜡烛水果蛋糕
		FruitDecorator fruitCandle = new FruitDecorator(candle);
		System.out.println(fruitCandle.description() + " , " + fruitCandle.cost());
	}
}

// 输出:
蛋糕 , 100
蛋糕,蜡烛 , 120
蛋糕,水果 , 110
蛋糕,蜡烛,水果 , 130

13.0 状态模式

用JS实现开关灯的小例子:

// 状态机
var FSM = {
	off: {
		buttonWasPressed: function() {
			console.log("关灯");
			this.curState = FSM.on;
		}
	},
	on: {
		buttonWasPressed: function() {
			console.log("开灯");
			this.curState = FSM.off;
		}
	}
}

// 状态对象
var Light = function() {
	this.curState = FSM.off;
}

Light.prototype.click = function() {
	this.curState.buttonWasPressed.call(this);
}

var light = new Light();
light.click()
// -> 关灯
light.click()
// -> 开灯