Alex学Ruby[ Ruby Design Pattern - Strategy Pattern]

本文通过一个报告生成示例,深入探讨了策略模式的概念及其在Ruby语言中的应用。文章对比了策略模式与模板方法模式,并展示了如何利用策略模式提高代码的灵活性。
嗯,我学习了template method,是个不错的方法, 一个算法的不同部分你怎么处理? 当有时做这个事情,有时做那个事情的时候,你如何能得到那五步过程里的第三步?答案是用template方法,去使用subclass来填充具体的动作细节。那么我们就需要写两个子类来做这个和那个。。。但是事情并没有结束。很不幸,模板方法有一些缺点,事实上,这种模式是建立在继承的基础上的。不管你怎么设计你的子类代码,总是和超类纠缠不清,限制了运行时的灵活性。一旦我们选择了一个特别的变化,在template method的例子里, 创建一个htmlreport类,如果我们改变report format,我们需要创造一个全新的report类,只是为了选择一个format ? 我们有得选择吗?
另一种方法是GOF的建议:Prefer delegation. 我们并不需要为每一个变化去创建一个子类,而是,The GoF call this “pull the algorithm out into a separate object” 。

此处继续引出yd小生的追mm与设计模式关于Strategy模式的一段描述:
InBlock.gif跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多Strategy哦。
InBlock.gif
策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。


来看我们的例子:
class Report
  attr_reader :title, :text
  attr_accessor :formatter
  def initialize(formatter)
    @title = 'Monthly Report'
    @text = [ 'Things are going', 'really, really well.' ]
    @formatter = formatter
  end
  def output_report
    @formatter.output_report( @title, @text )
  end
end


上面的这个report类,被GOF叫做“context”类,它是策略(Strategy)的使用者, 假如诸葛亮给你三个泡mm的精囊妙计,那么你就是context类。Strategy pattern是基于组件和委托思想的,所以可以在运行时很容易的去选择strategy,看下面的例子:

report = Report.new(HTMLFormatter.new)
report.output_report

report.formatter = PlainTextFormatter.new
report.output_report



策略模式和模板模式有一个共同点:两个模式都可以使我们集中处理变化。模板模式中,我们选择的是具体的子类,而策略模式中,我们可以在运行时很容易去选择具体的策略类。哪个灵活呢?

优势:
策略模式的真正优势在于,context和strategy都是不同的类,好的地方是,数据得到了很好的分离。 但是我们如何在context和strategy之间共享数据呢 ?看下面代码:
class Report
  attr_reader :title, :text
  attr_accessor :formatter
  def initialize(formatter)
    @title = 'Monthly Report'
    @text = ['Things are going', 'really, really well.']
    @formatter = formatter
  end
  def output_report
    @formatter.output_report(self)
  end
end
这里report类传递了一个自身的引用到formatter strategy类里。
class Formatter
  def output_report(context)
    raise 'Abstract method called'
  end
end

class HTMLFormatter < Formatter
  def output_report(context)
    puts('<html>')
    puts(' <head>')
    puts(" <title>#{context.title}</title>")
    puts(' </head>')
    puts(' <body>')
    context.text.each do |line|
      puts(" <p>#{line}</p>")
    end
    puts(' </body>')
    puts('</html>')
  end
end

class PlainTextFormatter < Formatter
  def output_report(title, text)
    puts("***** #{title} *****")
    text.each do |line|
      puts(line)
    end
  end
end

ms这样是把问题解决了,但是, 把context传递到strategy这种方式看似简化了数据流,但实际上增加了context类和strategy类之间的耦合性,彼此纠缠不清。

到这里,我们基本理解了strategy 模式是什么。 这里我们也创建了一个Formatter模拟抽象类,也实现了两个子类 HTMLFormatter 和PlainTextFormatter,然而不幸的是,这是一种un-Ruby的实现,因为这个Formatter实际上什么也没干,我差点忘了我是在学习Ruby实现的设计模式。。。
在Ruby里, 两个类 HTMLFormatter 和PlainTextFormatter都有共同的 output_report方法,相当于在java里的common interface按Duck Typing的理论,有共同的行为已经决定了他们是同一类,所以这里已经不需要那个什么都不干的Formatter类了。

请记住,Ruby的世界,会非常坚定的为剔除Formatter基类投出赞成的一票。

但是现在看来,我们的代码,还是那么臃肿,strategy其实仅仅应该是个代码片段而已,仅仅存储一份算法,一份策略而已,需要一个类吗?我们来改改代码:
class Report
  attr_reader :title, :text
  attr_accessor :formatter
  def initialize(&formatter)
    @title = 'Monthly Report'
    @text = [ 'Things are going', 'really, really well.' ]
    @formatter = formatter
  end
  def output_report
    @formatter.call( self )
  end
end


我们现在只需要用Ruby的Proc对象来代替那些strategy类了:
HTML_FORMATTER = lambda do |context|
  puts('<html>')
  puts(' <head>')
  puts(" <title>#{context.title}</title>")
  puts(' </head>')
  puts(' <body>')
  context.text.each do |line|
    puts(" <p>#{line}</p>" )
  end
  puts(' </body>')
end

report = Report.new &HTML_FORMATTER
report.output_report


Ruby语言本身有些地方也使用了Strategy模式,比如在rdoc里,使用strategy处理不同的语言,比如c parser, Ruby parse等。
我们也看看sort方法:
arr = [3,2,1,5,6]
arr.sort
#=> [1, 2, 3, 5, 6]
arr.sort{ |a,b| b <=> a }
#=> [6, 5, 3, 2, 1]


这里也是一个策略模式。唯一的遗憾是,没有解决那个耦合性的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值