装饰者模式是软件设计中一个非常强大但常被误解的设计模式。在许多编程社区中,包括深爱的Ruby社区,装饰者模式被滥用,不仅改变了它的规范,而且完全破坏了它的目的。以下是常见的装饰者模式的实现方式:
class UserDecorator
def initialize(user)
@user = user
end
def email
@user.email
end
def full_name
"#{@user.first_name} #{@user.last_name}"
end
def full_address
"#{@user.address.number} #{@user.address.street}, #{@user.address.city}, #{@user.address.state}"
end
end
User = Struct.new(:first_name, :last_name, :email, :address)
Address = Struct.new(:number, :street, :city, :state)
user_decorator = UserDecorator.new(
User.new(
"Oddly",
"Functional",
"hi@oddlyfunctional.com",
Address.new("123", "St. Nowhere", "New York", "NY")
)
)
user_decorator.email
# => "hi@oddlyfunctional.com"
user_decorator.full_name
# => "Oddly Functional"
user_decorator.full_address
# => "123 St. Nowhere, New York, NY"
正如大家所知,装饰者是一个表示层组件,它包装了一个模型实例,并为表示目的暴露了适当的方法(例如,格式化完整地址或完整名称,同时将不会改变的方法委托给包装实例,如电子邮件)。带着一丝恼怒地说,这不是装饰者。可以称它为表示器或其他东西,但它的目标和用途与装饰者完全不同。这种误解在社区中被强化,如gems(是的,在看,Draper),导致越来越少的开发者知道装饰者真正是什么。
但装饰者到底是什么呢?对于正式定义,可以查看原始的四人帮的《设计模式》书籍,但简单来说,装饰者是一个类,它包装了一个实例并实现了一个与该实例共同定义的接口,以便以可组合的方式动态和透明地向包装实例添加行为。“哈哈,看起来像老师,说起来容易做起来难”,一定在想。实际上非常简单实用。系好安全带,要给展示一些代码!
# 使用 Forwardable,一个标准库模块,使委托变得更容易
# 方法不会改变到它们的原始实现。
# 检查它的文档:
# http://ruby-doc.org/stdlib-2.3.1/libdoc/forwardable/rdoc/Forwardable.html
require 'forwardable'
class UserContactEmailDecorator
extend Forwardable
def_delegators :@user, :first_name, :last_name
def initialize(user)
@user = user
end
def email
"#{full_name} <#{@user.email}>"
end
private
def full_name
"#{@user.first_name} #{@user.last_name}"
end
end
class UserUppercaseNamesDecorator
extend Forwardable
def_delegators :@user, :email
def initialize(user)
@user = user
end
def first_name
@user.first_name.upcase
end
def last_name
@user.last_name.upcase
end
end
# 省略了地址,因为在这个例子中不会使用它
User = Struct.new(:first_name, :last_name, :email)
user = User.new("Oddly", "Functional", "hi@oddlyfunctional.com")
# 可以按想要的方式组合装饰者
decorated_user = UserContactEmailDecorator.new(UserUppercaseNamesDecorator.new(user))
decorated_user.email
# => "ODDLY FUNCTIONAL "
decorated_user.first_name
# => "ODDLY"
decorated_user.last_name
# => "FUNCTIONAL"
# 可能猜到了顺序很重要
decorated_user = UserUppercaseNamesDecorator.new(UserContactEmailDecorator.new(user))
decorated_user.email
# => "Oddly Functional " # 不同!
decorated_user.first_name
# => "ODDLY"
decorated_user.last_name
# => "FUNCTIONAL"
# 也可以单独使用它们
decorated_user = UserContactEmailDecorator.new(user)
decorated_user.email
# => "Oddly Functional "
decorated_user.first_name
# => "Oddly"
decorated_user.last_name
# => "Functional"
decorated_user = UserUppercaseNamesDecorator.new(user)
decorated_user.first_name
# => "ODDLY"
decorated_user.last_name
# => "FUNCTIONAL"
decorated_user.email
# => "hi@oddlyfunctional.com"
与之前的错误称为装饰者不同,真正的装饰者允许程序员在运行时组合任意行为,从不知道接收的是哪个类的间接性中受益,并有信心任何装饰者实例和原始类实例将实现相同的共同接口。它允许无限嵌套,这有点令人敬畏(rack,有人吗?)。通过添加或删除方法来改变接口是不可能实现的,因为客户端类或调用者无法将任何可能被装饰的实例视为定义的共同接口的成员。
虽然这些例子仍然实现了模型的不同表示方式,但装饰者模式中没有任何内容引用类将如何被使用。为了证明这一点,下面是一个不涉及表示上下文的用例:
class Operator
def run
# 做一些事情
end
end
class OperationLoggerDecorator
def initialize(operator, logger)
@operator = operator
# 一个重要的要点是,具有相同的接口
#
# 并不意味着具有相同的构造函数。哪个客户端代码
#
# 实例化装饰者*知道*它在做什么。
@logger = logger
end
def run
@logger.info("Initiating operation...")
result = @operator.run
@logger.info("Finished with result: #{result}")
result
# 返回结果供客户端使用
end
end
class OperationNotifierDecorator
def initialize(operator)
@operator = operator
end
def run
result = @operator.run
Notification.create("Operation finished with result: #{result}")
result
end
end
# 可以自由地组合装饰者!
operator = Operator.new
operator.run
OperationLoggerDecorator.new(operator).run
OperationNotifierDecorator.new(operator).run
OperationLoggerDecorator.new(OperationNotifierDecorator.new(operator)).run
OperationNotifierDecorator.new(OperationLoggerDecorator.new(operator)).run
# 或者,以更现实的方式:
Settings = Struct.new(:log?, :notify?)
# 在一个真正的应用程序中,设置会
#
# 存储在某个地方,可能是数据库。
settings = Settings.new(true, true)
if settings.log?
operator = OperationLoggerDecorator.new(operator)
end
if settings.notify?
operator = OperationNotifierDecorator.new(operator)
end
operator.run
呼,这让松了一口气!对这种常见的误解感到恼火这么久,但从未花时间写下来。感觉很好,几乎是一种治疗!
希望现在能够欣赏装饰者真正是什么。可以争论它们导致太多的间接性,或者它们是简单案例的过度解决方案(可能是正确的)。有权不喜欢它并决定不使用它。但是,请,请,不要称表示器为装饰者。