【问题标题】:How do I make a class conditionally return one of two other classes?如何让一个类有条件地返回其他两个类之一?
【发布时间】:2014-07-10 14:54:28
【问题描述】:

我有一个设计问题。

我正在用 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

那些使用我的 gem 的人必须拨打如下电话:

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

这似乎不对

我可以使用一个模块来返回适当的类,但随后我面临如何为我的 gem 提供单个公共接口的问题。我无法使用 .new 实例化模块。

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

非常感谢您的帮助。

【问题讨论】:

  • 你要找的东西叫Factory Pattern
  • 不使用Class,我认为你可以使用Module,使用if opts[:tier] == :local then include LocalClientModule else include ZkModule end

标签: ruby


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    您可以使用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
    

    【讨论】:

      【解决方案3】:

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

      常见的解决方案是创建一个返回相关类实例的方法(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
      

      【讨论】:

        猜你喜欢
        • 2020-11-23
        • 2020-08-09
        • 2020-12-20
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        • 2022-11-06
        • 2014-04-21
        相关资源
        最近更新 更多