论坛首页 编程语言技术论坛

用 Ruby 踩踩四人帮

浏览 36126 次
该帖已经被评为精华帖
作者 正文
   发表时间:2009-04-18   最后修改:2009-04-18
上上周在书店看到一本《Ruby设计模式》,捡起来 10 分钟看完,扔了(别问我为什么……)

下面用 Ruby 写写设计模式,顺便批一批 Java 和 Gof。

1.Factory 和 Abstract Factory
class Factory
  attr_accessor :product
  def produce
    @product.new
  end
end
class Product
  #..
end
fac = Factory.new
fac.product = Product
fac.produce


Java写的工厂有这么简单,这么容易重用么?

2.Builder
# 工头
class Director
  def build_with builder
	acc = ''
	[:header, :body, :footer].each do |m|
	  acc += builder.__send__ m if builder.respond_to? m
	end
	acc
  end
end
# 工人
class HTMLBuilder
  def header; '<html><title>html builder</title>';end
  def body;	  '<body>html builder</body>'        ;end
  def footer; '</html>'                          ;end
end
class XMLBuilder
  def header; '<?xml version="1.0" charset="utf-8">';end
  def body;   '<root>xml builder</root>'            ;end
end
d = Director.new
puts(d.build_with HTMLBuilder.new)
puts(d.build_with XMLBuilder.new)

注意:Ruby的工序并不依赖于Builder的类,只要有方法签名就行了。
interface 这种束手束脚,加强耦合的东西完全不需要~

3.Prototype
依样画葫芦。
这里用一点 trick (evil ruby):
require 'evil'
class Prototype
  # ...
end
class Concrete
  include Prototype.as_module
end


4.Adapter
Ruby 包装方法易如反掌:
class Adaptee
  def talk; puts 'Adaptee';end
end
class Adapter < Adaptee
  alias talkee talk
  def talk
	puts 'before Adaptee'
	talkee
	puts 'after Adaptee'
  end
end
Adapter.new.talk


很多没学过设计模式的 Ruby 程序员天天用 adapter……

5.Composite
这个 pattern 是给没有多重继承又没法 mixin 的语言用的。
Ruby 只需要 include module。

6.Decorator
module Colorful
  attr_acessor :color
end
class Widget
end
w = Widget.new    # w 作为 Widget 的实例,没有 color 方法
w.color = 'blue' rescue puts 'w has no color'
w.extend Colorful # 现在 w 有 color 方法了
w.color = 'blue'
puts w.color


可怜的 Java 程序员需要学习设计模式才能写出 decorator。

7.Flyweight
# 重量级对象的瘦身法:如果创建参数相同,则返回同一个对象
class FlyweightFactory
  class Glyph
	def initialize key
	  @key = key
	  sleep 1 # 睡一秒,以体现这个对象创建的“重量级”
	  @square = key ** 2
	  @cubic = key ** 3
	end
	attr_reader :key, :square, :cubic
  end
  def produce key
	@glyphs ||= {}
    @glyphs[key] || (@glyphs[key] = Glyph.new key)
  end
end
ff = FlyweightFactory.new
g1 = ff.produce 2
g2 = ff.produce 2
puts (g1.object_id == g2.object_id)


不得不说 || 是很爽的语法。
另外 Ruby 的 Hash 可以用数组作 key,如果 Glyph 的构造函数需要更多参数,只需要把 produce 里的 key 改成 *key

8.Proxy
Proxy 和 Adapter 的区别只在接口上,它们在 Ruby 中是一样的。
这说明了:1.大道至简; 2.Gof 模式的语言局限性。

9.Chain of Responsibility
如果没有 proc,代码是做不到这么清晰简洁的:
class Chain
  def initialize
	@chain = []
  end
  def add_handler &block
	@chain << block
  end
  def handle req
	@chain.each do |e|
	  # 如果handler返回 false(未处理),则让下一个处理
	  result = e[req] 
	  return result if result
	end
	false
  end	
end
c = Chain.new
c.add_handler {|req| req == 1 ? "1:handled" : puts "1:not my responsibility" }
c.add_handler {|req| req == 2 ? "2:handled" : puts "2:not my responsibility" }
puts(c.handle 1)
puts(c.handle 2)


10.Command
本质:一次调用可以同时执行多个方法。GUI 编程中处理事件很常用。
因为 Java 不能直接传递方法,所以把简单的问题复杂化了……
btw:真不理解 swing 优美在哪里……
class Command
  def initialize
	@executors = []
  end
  # 另一种方法是让 executors 保存一组对象,每个都带 execute 方法
  # ——但是这么简单的事情就需要一组接口,一组实现?
  def add_executor &block
	@executors << block
  end
  def execute
	@executors.each {|x| x.call }
  end
end
c = Command.new
c.add_executor{ puts 'executor 1' }
c.add_executor{ puts 'executor 2' }
c.execute

Command 是和 Chain 很相似的东西,可能某天会有人写一本 "Pattern of Patterns" 吧。

11.Template Method
Java 一说模板,C++ 和 Ruby 就笑了。
例(偷懒“重用”一下写过的代码):
# 穷举法检验 de Morgan 定理
class Fixnum
  %w[a1 a2 a3 b1 b2 b3].each_with_index do |name, idx|
    define_method name, do
      self & (1<<idx) == 0 ? false : true
    end
  end
end
0b1000000.times do |n|
  n.instance_eval %q[
    if !((a1&&b1) || (a2&&b2) || (a3&&b3)) != !(a1&&b1) && !(a2&&b2) && !(a3&&b3)
      puts 'blah'
    end
  ]
end


12.Iterator 和 Visitor
这些模式还是作古吧。
有太多简单的方式进行迭代(map, inject, each, each_with_index,sort ...)
关键点还是语言对泛型和匿名函数的支持。

13.Mediator
将各个类的相互依赖性扔到一个中介类之中。
老老实实的写个中介类?大概会像这样:
class Mediator
  def initialize seller, buyer
	@seller = seller
	@buyer = buyer
  end
  def sell
	@seller.sell
  end
  def buy
	@buyer.buy
  end
end

发现问题了吗? Mediator 出现的根源还是静态类型(是不会推断的那种)带来的耦合。
Duck Typing (check respond_to? instead of class) 早已解耦,根本不需要中介。

14.Strategy
提供运行时选择策略(算法)的可能。
假设 Array 有两种方法:bubble_sort 和 quick_sort
按照 Gof 的教诲,我们可能会这样想:
class Array
  def sort options
	if options[:strategy].to_sym == :bubble_sort
	  bubble_sort()
    elsif options[:strategy].to_sym == :quick_sort
	  quick_sort()
	end
  end
end
arr.sort :strategy => strategy

根本就是没事找事……看看 Ruby 动态选择调用方法多简单:
arr.__send__ strategy


15.Singleton
module SingletonClass
  class << self
	# methods
  end
end

听说有人用两百行实现了严格的 singleton,膜拜中。

结论:
如果没有类型声明和继承,很多依赖性都会消失无踪,接口也无用武之地了。
设计模式大都是 Interface Hack 汇总,枯燥无味、思想僵化、限制创造力,早该下架了。
学设计模式不如学 Ruby Python。
Java 语言(注意不是平台)的弱点导致大量的冗余代码。
所谓从善如流,有功夫写冗余代码不如多写几个测试。
Java 语言(注意不是平台)的历史意义在于:将程序员从过早考虑效率的传统中解放了出来,现在也该功成身退了。


   发表时间:2009-04-18  
看到讨论模式和贴代码实现模式的帖子或者我就头大
没有一点针对night_stalker...相反night_stalker总结的很棒,已经添加收藏

除了ruby实现模式,我甚至还看过javascript实现所有模式,任何一个模式,只要你说的出名字,在wikipedia上就马上能搜到各种常用/非常用语言的实现,可是这样又能怎么样呢?
我始终认为,任何一种语言拿过来,我都可以用它很轻易的实现各种模式,难点在于当你面对问题的时候,何时应该用模式以及挑选哪一个模式...

Ruby设计模式中文版翻译的质量,我的评价很低,不讨论.
但是英文原版值得一读,并不用去刻意记住每种模式是如何实现,看看每种模式后面作者提供的一个"Using' and Abusing"以及模式"in the Wild"这两个章节,仔细揣摩ruby的实现模式在真是世界中是怎么被广泛应用的.我认为这个此书的亮点

总的来说,此书设计内容尚浅,但是在介绍每个模式的过程中,循序渐进的将Ruby的一些有趣的语言特性用非常生动的方式介绍出来,非常建议入门以及中级ruby程序员阅读 :-)

night_stalker在文章最后用白色字体的一段总结性论述,我不敢赞同,以及文章标题太具有煽动性...hehe, another story了...我不打算跟night_stalker讨论两个地方,避免口水仗.

(btw: 我不是书托,如果非要说我是,好吧,那么我就是英文原版的书托)
9 请登录后投票
   发表时间:2009-04-18   最后修改:2009-04-18
很棒的文章! 设计模式是为了弥补语言的不足而出现的,语言本身越强大,需要的设计模式越少。正如文章中提到的,很多时候你在用Ruby语言提供的某种特性或者api,其实不知不觉中已经在使用设计模式了。

以Flyweight为例子进一步来说,如果我遇到这样的需求,可能不会照搬Java的实现,用Hash的default值特性可以替换 :
class Glyph
  def initialize key
    @key = key
    sleep 1 # 睡一秒,以体现这个对象创建的“重量级”
    @square = key ** 2
    @cubic = key ** 3
  end
  attr_reader :key, :square, :cubic
end

#用Hash的default来替代FlyweightFactory
ff = Hash.new { |hash, key| hash[key] = Glyph.new(key) }

g1 = ff[2]
g2 = ff[2]
puts(g1.object_id == g2.object_id)


不过,有时候一板一眼地用Class/Module还是有必要的,比如Command,从单元测试角度考虑,用Class比用proc容易写测试代码。
0 请登录后投票
   发表时间:2009-04-18  
这个 lazy hash 很 cool,又一个模式被除名……
0 请登录后投票
   发表时间:2009-04-18  
Ruby代码

   1. class Factory 
   2.   attr_accessor :product 
   3.   def produce 
   4.     @product.new 
   5.   end 
   6. end 
   7. class Product 
   8.   #.. 
   9. end 
  10. fac = Factory.new 
  11. fac.product = Product 
  12. fac.produce 

没有特别仔细的看。但这段代码似乎有点华而不实。Factory是为了简化对象创建。如上面这段代码所演示,那不如不用Factory,直接Product.new算了。以前我们写大工程的时候,一个复杂的类,往往有一个跟他匹配的Factory。而不是一个Factory通吃。
0 请登录后投票
   发表时间:2009-04-19   最后修改:2009-04-19
lmxbitihero 写道
Ruby代码

   1. class Factory 
   2.   attr_accessor :product 
   3.   def produce 
   4.     @product.new 
   5.   end 
   6. end 
   7. class Product 
   8.   #.. 
   9. end 
  10. fac = Factory.new 
  11. fac.product = Product 
  12. fac.produce 

没有特别仔细的看。但这段代码似乎有点华而不实。Factory是为了简化对象创建。如上面这段代码所演示,那不如不用Factory,直接Product.new算了。以前我们写大工程的时候,一个复杂的类,往往有一个跟他匹配的Factory。而不是一个Factory通吃。

嗯,直接 Product.new 是最简单直接的。
例子比较 naive,只体现了更换产品的方便性,不过往往 produce 过程比较复杂才用 Factory。
实际上可以打开类重新定义 produce,也可以直接定义一个新的 Factory。
0 请登录后投票
   发表时间:2009-04-19  
楼主懂得从设计模式这个角度来看到语言的演进这一点很不错,不过批Java就没有意义了,就像你懂了C之后就跑去批汇编一样。更重要的是了解不同语言的特点,按需使用。
2 请登录后投票
   发表时间:2009-04-19  
很棒的文章!

给你更正两个地方:
template method是与builder相似的行为模式,一般用abstract class实现。ruby在方法中raise NotImplementedError模拟声明抽象方法。
例子中展示的运行时修改行为,java只能通过外部手段实现一小部分。

singleton用来保证该类的实例在系统中只存在一份,ruby用include Singleton实现。

我的理解是这样的:一个功能的完成是”用机器实现人类需求“的过程,通常分三个阶段:分析-设计-实现。
分析阶段侧重于人类需求,实现阶段侧重于机器实现,设计阶段则是将人类需求映射到机器实现的规划过程,这当中既要针对需求特性(目标),又要结合开发工具提供的能力和限制(资源),对问题域进行建模

设计模式是对设计过程中遇到的相似情况的归纳总结。

我觉得GOF设计模式很大程度上假定以静态语言作为开发工具,而且过于侧重了实现(其实我一直把GOF当成实现模式,大家BS我吧~)。这就不难理解为什么有那么多Factory、Decorator、Adapter,很多时候就是为了跳过编译器或语法层面设置的沟沟坎坎,跟业务描述之间距离太远。

越容易将问题域映射到实现域,或者说建模越直观,这个开发工具的效用就越好。
套路是一回事,重复是另一回事。即便套路有了,如果每次执行这个套路还需要大量不断的重复,也是很悲哀的。

以动态语言为开发工具,在纷繁的开发过程中,又有哪些典型场景需要提取出来,形成套路,甚至写一本全新的设计模式出来,期待中~
3 请登录后投票
   发表时间:2009-04-19  
liusong1111 写道

以动态语言为开发工具,在纷繁的开发过程中,又有哪些典型场景需要提取出来,形成套路,甚至写一本全新的设计模式出来,期待中~



我目前在开发的一个项目(用的是RubyGnome),设计了一套plugin机制,让程序成为一个可以二次开发的平台,可以由第三方来增强功能。

plugin,这个可以作为动态语言(特别象ruby这样的动态语言)擅长的一个典型场景,不妨叫作“plugin模式”,呵呵~




8 请登录后投票
   发表时间:2009-04-19   最后修改:2009-04-19
liusong1111 写道
很棒的文章!

给你更正两个地方:
template method是与builder相似的行为模式,一般用abstract class实现。ruby在方法中raise NotImplementedError模拟声明抽象方法。
例子中展示的运行时修改行为,java只能通过外部手段实现一小部分。

singleton用来保证该类的实例在系统中只存在一份,ruby用include Singleton实现。


用 raise 模拟抽象方法我也想过,但抽象方法在 ruby 是多此一举的。
譬如:
def some_method
  raise "No implementation"
end
就是毫无意义的,你不定义它,也可以跳出一个 "method missing"。

检查方法是否实现,用 obj.respond_to? :some_method 更合理一些。

singleton 模式出现的原因是:在 1994 年,类不是对象。
但 ruby 的 class 和 module 都是对象,它们已经是 Singleton 了……
include Singleton 是一种实现方法,ruby 的核心库里也有 singleton,但是 Singleton module 的内容就很复杂了。

flyisland 写道
...不过批Java就没有意义了,就像你懂了C之后就跑去批汇编一样。更重要的是了解不同语言的特点,按需使用。


我承认有点偏激…… 批 java 主要的原因是竟然有人以为语法的不足可以用库来实现。
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics