如何使一个类有条件地返回另外两个类中的一个?

时间:2014-07-10 05:35:11

标签: ruby

我遇到了设计问题。

我正在用ruby编写REST客户端。由于我无法控制的原因,它必须扩展另一个使用我的网络zookeeper实例的gem来进行服务查找。我的客户端使用用户提供的层,并根据该值查询zookeeper注册表以获取相应的服务URL。

问题是我还需要能够针对本地运行的受测试服务版本运行我的客户端。当服务在本地运行时,显然没有涉及zookeeper,所以我只需要能够针对localhost资源url发出GET请求。

当用户实例化我的gem时,他们会调用类似:

client = MyRestClient.new(tier: :dev)

或以本地模式

client = MyRestClient.new(tier: :local)

我想避免有条件地攻击MyRestClient中的构造函数(以及MyRestClient中的所有GET方法)来根据:local:requests_via_the_zk_gem更改请求。

我正在寻找一种优雅而干净的方式来处理Ruby中的这种情况。

一种想法是创建两个客户端类,一个用于:local,另一个用于:not_local。但后来我不知道如何提供一个返回正确客户端对象的gem接口。

如果MyClient的构造函数看起来像这样:

class MyClient
  attr_reader :the_klass 
  def initialize(opts={})
    if opts[:tier] == :local
      @the_klass = LocalClass.new  
    else
      @the_klass = ZkClass.new
    end
    @the_klass
  end
end

然后我结束了类似的事情:

test = MyClient.new(tier: :local)
=> #<MyClient:0x007fe4d881ed58 @the_klass=#<LocalClass:0x007fe4d883afd0>>
test.class
=> MyClient
test.the_klass.class
=> LocalClass

然后使用我的宝石的人必须拨打电话:

@client = MyClient.new(tier: :local)
@client.the_klass.get

这似乎不正确

我可以使用模块返回相应的类,但后来我遇到了如何为我的gem提供单个公共接口的问题。我不能用.new。

实例化一个模块

我的感觉是,这是一个常见的OO问题,我还没有碰到它。也有可能答案是盯着我,我还没有找到它。

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:2)

一种常见的模式是将服务传递到客户端,例如:

class MyClient
  attr_reader :service

  def initialize(service)
    @service = service
  end

  def some_method
    service.some_method
  end
end

用以下方法创建:

client = MyRestClient.new(LocalClass.new)
# or
client = MyRestClient.new(ZkClass.new)

您可以将这两个移动到类方法中:

class MyClient

  self.local
    new(LocalClass.new)
  end

  self.dev
    new(ZkClass.new)
  end

end

而是致电:

client = MyRestClient.local
# or
client = MyRestClient.dev

答案 1 :(得分:1)

您可以使用method_missing从客户端委派给实际的班级。

def method_missing(m, *args, &block)
  @the_class.send(m, *args, &block)
end

因此,只要在您的课程中调用一个不存在的方法(例如您的示例中为get),就会在@the_class上调用它。

定义相应的respond_to_missing? btw:

是一种很好的风格
def respond_to_missing?(m, include_private = false)
  @the_class.respond_to?(m)
end

答案 2 :(得分:1)

您描述的用例看起来像是一个经典的工厂方法用例。

这个的常见解决方案是创建一个返回相关类实例的方法( not new):

class MyClient

  def self.create_client(opts={})
    if opts[:tier] == :local
      LocalClass.new  
    else
      ZkClass.new
    end
  end
end

现在您的用法是:

test = MyClient.create(tier: :local)
=> #<LocalClass:0x007fe4d881ed58>
test.class
=> LocalClass