[原文:
Decorator Pattern with Ruby in 8 lines]
[中文名:Ruby中8行代码实现Decorator模式]
[出处:
http://www.lukeredpath.co.uk/2006/9/6/decorator-pattern-with-ruby-in-8-lines]
[作者:
Luke Redpath
]
[译者:
极地银狐.NET]
装饰模式是设计模式之一,它允许你在运行时动态地为一个存在的对象添加特性.在一个对象有很多种可以有不同方式组合的可以影响其特性的
变量时特别有用(dingsea:没看懂?翻译这段比较YY,以上大概是出版社的风格,其实用例子比较容易理解,向下看)
这个短小精悍的Ruby装饰模式实现,总结了这个星球上我最爱的最火的动态语言.
我从Eric Freeman, Elisabeth Freeman, Kathy Sierra, 和Bert Bates他们写的杰出的Head First Design Patterns 一书中借取一个例子.
假设你要计算一杯咖啡的价钱.你有一个实现了cost()方法的咖啡类.此例中我们出于示例目的硬编码其价格:
class Coffee
def cost
2
end
end
很好.不过如果我们要知道一杯加奶的咖啡多少钱怎么办?我们有一个新类:
class WhiteCoffee
def cost
2.4
end
end
好,但是现在我们要加奶油的咖啡怎么办?再要撒些东西在上边呢?(原文"And sprinkles",个人理解,欢迎讨论).明显地,不停地创建新类会导致在应用中出现类爆炸.为不同的咖啡和其调料(糖,奶...)组合创建新类是不现实的,这样会变糟糕--如果我们有不同种类的咖啡怎么办?然后我们
还不得不加一些调料在这些不同的咖啡中.这不是一个好办法.下面进入装饰模式.像本文题目提到的那样,这就是Ruby的8行代码实现:
module Decorator
def initialize(decorated)
@decorated = decorated
end
def method_missing(method, *args)
args.empty? ? @decorated.send(method) : @decorated.send(method, args)
end
end
这就是你所要的.你可以在任何你想要装饰的类中包含以上module.然后你可以使用这个装饰者就像直接使用装饰好的对象一样(原文:You can then use that decorator as if it was the object it is decorating,我就直说了,呵呵)默认地,所有传到装饰者的信息都会被转到被装饰的对象那儿.你可以根据需要装饰你的方法用来扩展:
class Milk
include Decorator
def cost
@decorated.cost + 0.4
end
end
那么怎样解决我们刚开头的咖啡问题呢?装饰模式在实战中的强大之处就在于他们可以像被装饰的对象一样工作(dingsea:嗯,大概是说,类A,被装饰过后,客户代码认为它还是A,呵呵).通过更进一步,你可以装饰其它的装饰者,只要它们有同样的接口.通过为不同的"扩展"创建装饰者,我们可以使用组合的装饰者创建咖啡类并计算此咖啡的总价.
class Whip
include Decorator
def cost
@decorated.cost + 0.2
end
end
class Sprinkles
include Decorator
def cost
@decorated.cost + 0.3
end
end
Whip.new(Coffee.new).cost
#=> 2.2
Sprinkles.new(Whip.new(Milk.new(Coffee.new))).cost
#=> 2.9
当然,为方便着想我们情不自禁创建几个工厂方法:
class CoffeeFactory
def self.latte
SteamedMilk.new(Espresso.new)
end
def self.cappuccino
Sprinkles.new(Cream.new(Milk.new(Coffee.new)))
end
end
order = Order.new
order.add(Coffee.new)
order.add(CoffeeFactory.cappuccino)
puts order.total
由于Ruby的高动态语言特性,装饰模式并不是在运行时扩展类的唯一方法,当然,我只是喜欢ruby中用如此简单的方法实现一个模式.在ruby中有关装饰模式的更多信息和实现,包括generic decorators和可选择的传统装饰模式,请参考
DecoratorPattern page at the RubyGarden.
最后,在使用装饰模式时保持某些恒等性是很好的.上边提到的RubyGarden的网页里是其中一种方法.同时我们没有使用继承,保留继承这一特性是很好的:
CoffeeFactory.cappucino.kind_of? Coffee
#=> true
我能想到很多种方法扩展我的装饰模式来完成功能,但是我把这些留给读者.看你们的啦.
更新:在经典Ruby-fashion中,我8行代码的装饰模式被,嗯,
Trevor Squires 0行的代码所打败.通过使用modules, super和extend,Trevor带来了这个同样保持恒等性的
alternative solution,I服了U(原文:Bow down to his Ruby-fu).
更新2:没人会停止不前.我看了Trevor的Ruby-fu(dingsea:没懂这个-fu是啥)并加入了一点我自己的东西.我仍然觉得我的装饰模式实现会有分量些,加入一些语法溏块(dingsea:应该是指易读性)
class Milk
include Decorator
end
class Whip
include Decorator
end
class Sprinkles
include Decorator
end
# normal coffee
Coffee.new
# coffee with milk, whip and sprinkles
Coffee.with :milk, :whip, :sprinkles
self.with方法可以把自己打包入一个可装饰module中,这处是它的
具体实现.Trevor的方法仍然在保持对象恒等性上有优势,但我有方法解决它.不过,唉,半夜一点了,该睡觉了(dingsea:我翻译的时候也是1:28AM啊,你睡的是不是太早了?)
更新3:Trevor用他自己的self.with实现进行了反击.个人角度讲我比较喜欢Coffee.with :sym, :sym式的语法胜过于offee.with Module, Module式的语法,当然这只是个人喜好.通过他的extend实现,可以消除符号对类的欺骗性(dingsea:越看越晕,指教),而且仍然保持恒等性,他的FU仍然很强.
然而,下边是Trevor的方法:你可以使用一次extend来装饰对象,但是要是我要撒两次东西怎么办?
Sprinkles.new(Sprinkles.new(Coffee.new))
# or with a bit of sugar (no pun intended)
Coffee.with :sprinkles, :sprinkles