2014-07-10 48 views
0

我有一個設計問題。如何讓一個班有條件地返回其他兩個班之一?

我正在用ruby編寫一個REST客戶端。由於我無法控制的原因,它必須擴展另一個使用我的網絡zookeeper實例進行服務查找的gem。我的客戶端使用用戶提供的層,並根據該值向zookeeper註冊中心查詢相應的服務url。

問題是我還需要能夠運行我的客戶端對本地運行的測試服務版本。當服務在本地運行時,zookeeper顯然不參與,所以我只需要能夠對本地主機資源url進行GET請求。

當用戶實例我的寶石,他們稱這樣的:

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 

這看起來不對

我可以使用模塊來返回適當的類,但是然後我面臨着如何爲我的寶石提供單個公共接口的問題。我不能用.new實例化一個模塊。

我的感覺是,這是一個常見的面向對象問題,我還沒有碰到它。也有可能答案是在我面前凝視我,我還沒有找到它。

非常感謝任何幫助。

+0

你在找什麼叫[工廠模式](http://en.wikipedia.org/wiki/Factory_method_pattern) – bjhaid

+1

而不是使用'Class',我想你可以使用'Module',如果opts [:tier] ==:local,那麼包含LocalClient其他模塊包括ZkModule end'。 – huocp

回答

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

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

使用情況,你所描述像是一個經典的工廠方法使用情況。

這種常見的解決辦法是創建返回相關的類實例的方法(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 
相關問題