深入理解装饰者模式

装饰者模式是软件设计中一个非常强大但常被误解的设计模式。在许多编程社区中,包括深爱的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

呼,这让松了一口气!对这种常见的误解感到恼火这么久,但从未花时间写下来。感觉很好,几乎是一种治疗!

希望现在能够欣赏装饰者真正是什么。可以争论它们导致太多的间接性,或者它们是简单案例的过度解决方案(可能是正确的)。有权不喜欢它并决定不使用它。但是,请,请,不要称表示器为装饰者。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485