今天写了篇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__
但是,委托类可以大大提高类的灵活性。