嗯,我学习了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设计模式休闲