如何在实例化时切换实现?

时间:2013-03-11 03:19:23

标签: ruby

我在Ruby中有一个包含一些内容的类,我会调用FooBox

class FooBox
  ...
end

我有两个可能的FooBox支持数据存储,名为BoxABoxB,具有不同的特征,但界面相同:

class BoxA
  include Enumerable
  def put_stuff(thing)
    ...
  end
end


class BoxB
  include Enumerable
  def put_stuff(thing)
    ...
  end
end

如何实例化FooBox,并根据参数决定是使用BoxA还是BoxB实施来支持它?我不想将实现传递给构造函数;我只想传递一些东西来确定使用哪种。

class FooBox
  def initialize(implementation_choice)
    # ???
  end
end

2 个答案:

答案 0 :(得分:1)

我通常做这样的事情:

class BoxA
  def self.match? options
    # figure out if BoxA can be used given options
  end
end

# Implement BoxB (and other strategies) similarly to BoxA

class FooBox
  STRATEGIES = [BoxA, BoxB]

  def initialize options
    @options = options
  end

  def strategy
    @strategy ||= STRATEGIES.detect { |strategy| strategy.match? @options }
  end
end

这使得“知道”策略是否能够在策略本身中使用(而不是使上下文类单片化),然后只需选择列表中的第一个可以工作的人。< / p>

我已经多次使用这种模式(以及针对略有不同的问题的类似变化)并且发现它非常干净。

答案 1 :(得分:0)

简单的解决方案是为策略的类型和策略类创建一个映射,就像@Andrew Marshall的解决方案一样

但为了更好,我会考虑两件事:

  • 策略持有者(这里是FooxBox)现在需要了解每个盒子的实现,并将其名称硬编码给自己;这不灵活 方法,考虑有一天你想添加另一个策略,转到代码并添加它?使用ruby,我们可以轻松地进行“自我注册”。
  • 你不希望策略持有者会疯狂地返回实现,我的意思是'BoxA'和'BoxB'或者某天的'BoxXYZ'应该属于同一策略 概念,在Java中,它可能意味着所有这些都应该实现interface,使用ruby我们通常使用include SomeMoudle

在我的应用程序中,我使用以下解决方案(只是演示)

module Strategies
  def self.strategies
    @@strategies ||= {}
  end

  def self.strategy_for(strategy_name)
    @@strategies[strategy_name]
  end
end

module Strategy
  def self.included(base)
    base.class_eval do
      def self.strategy_as(strategy_name)
        Strategies.strategies[strategy_name] = self
      end
    end
  end 
end


class BoxA
  include Strategy

  strategy_as :box_a

  def do_stuff
    puts "do stuff in BoxA"
  end
end 

class BoxB
  include Strategy

  strategy_as :box_b

  def do_stuff
    p "do stuff in BoxB"
  end
end

## test 
Strategies.strategy_for(:box_a).new.do_stuff
Strategies.strategy_for(:box_b).new.do_stuff

如果要检测匹配阻止的策略,可以更改strategy_as以接受阻止。然后使用Strategies.strategy_for{...}.new.do_stuff