为了账号安全,请及时绑定邮箱和手机立即绑定

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模式的一段描述:

跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦囊中有好多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( @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]

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

©著作权归作者所有:来自51CTO博客作者blackanger的原创作品,如需转载,请注明出处,否则将追究法律责任

Ruby设计模式休闲


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消