委托类:太酷了

今天写了篇Google MapReduce的新闻。满脑子都是Google大神Jeff Dean。

刚才偶尔看到Reddit上有链接指向Jeff Dean的一篇博客,大喜,顺手翻译如下。快翻完才醒悟过来,人家Google的大牛是搞底层的,怎么会用Ruby呢,而且Google公司官方语言只有C/C++、Java、Python(似乎最近都受限了)、JavaScript几种,也不能用啊。再仔细一看。噢,同名同姓的另一个人,不过水平看上去也不错,是Rails框架的核心开发人员之一,而且文章简明也算有料。就放这里吧。

如果你注意到类的责任超过一个,可以很容易地用Ruby的DelegateClass将其分为多个更内聚的类。

比如有一个Person类,表示系统中可以卖东西或者发表文章的人。不能用子类,因为人可以同时是作者和卖家。首先可能这些写:

class Person < ActiveRecord::Base
  has_many :articles
  has_many :comments, :through => :articles
  has_many :items
  has_many :transactions

  def is_seller?
    items.present?
  end

  def amount_owed
    # => some fancy math
  end

  def is_author?
    articles.present?
  end

  def can_post_article_to_homepage?
    # => some fancy permissions
  end
end

这看上去还不错,可是当有了新需求会怎么样呢:这个人还可以是买家。

那就得这么改改了:

class Person < ActiveRecord::Base
  #  ...
  has_many :purchased_items
  has_many :purchased_transactions

  def is_buyer?
    purchased_items.present?
  end

  # ...
end

显然,这违反了面向对象中的开闭原则。其次,交易的买家和卖家也容易弄混淆。最后,代码在问题分离方面很烂。

想像一下再来一个需求怎样:现在Person类是由一个XML Web服务或者一个非ActiveRecord类驱动的。

不能用ActiveRecord,has_many代码就不能用了,必须重新所有代码,新功能开发就别想了。

DelegateClass来也

如果不想改Person,可以写一个委托类,扩展Person,比如这样:

class Person < ActiveRecord::Base
end

class Seller < DelegateClass(Person)
  delegate :id, :to => :__getobj__

  def items
    Item.for_seller_id(id)
  end

  def transactions
    Transaction.for_seller_id(id)
  end

  def is_seller?
    items.present?
  end

  def amount_owed
    # => some fancy math
  end
end

class Author < DelegateClass(Person)
  delegate :id, :to => :__getobj__

  def articles
    Article.for_author_id(id)
  end

  def comments
    Comment.for_author_id(id)
  end

  def is_author?
    articles.present?
  end

  def can_post_article_to_homepage?
    # => some fancy permissions
  end
end

调用还需要加一个步骤,不能这样:

person = Person.find(1)
person.items

需要加:

person = Person.find(1)
seller = Seller.new(person)
seller.items
seller.first_name # => calls person.first_name

现在,加一个Buyer就很简单了,只需写一个Buyer委托类:

class Buyer < DelegateClass(Person)
  delegate :id, :to => :__getobj__

  def items
    Item.for_buyer_id(id)
  end

  def is_buyer?
    purchased_items.present?
  end
end

这样,在需要Person由ActiveRecord::Base之外的其他东西驱动时,委托类无需任何修改。

委托类当然也不是万灵丹药,某些行为比如 #reload 会让人很晕:

person = Person.find(1)
seller = Seller.new(person)
seller.class # => Seller
seller.reload.class # => Person

另一个不爽的地方,是id是无法默认委托的,必须加这样一行代码:

  delegate :id, :to => :__getobj__

但是,委托类可以大大提高类的灵活性。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值