2016-01-23 77 views
3

這主要是一個函數式編程的問題,而不是一個藥劑的問題,但是因爲我在學習Elixir,如果有人能用這種語言回答它會很好。即使如此,如果有人想給出更一般的答案,它將不勝感激。藥劑 - 改變行爲

我自己是一個OO程序員,我無法圍繞如何根據配置文件(例如)更改組件的行爲。

例如: 我有一個從數據庫加載/保存用戶的應用程序。在生產環境中,我希望保存和檢索MongoDB數據庫中的用戶,而在開發和測試中我想使用內存映射。如果我使用面向對象編程語言編寫給定的系統(比如說Java),我只需要創建一個名爲「UserRepository」的Interface,其中包含2個實現:「MemoryUserRepository」和「MongoDBUserRepository」。然後,我會在啓動時及其之後實例化基於配置文件的相應Repository(或對其進行硬編碼,無所謂),所有與Repository交互的對象將永遠不會知道它的實現(它們將使用存儲庫,但永遠不會在意它是在內存還是在mongo)。 這給了我創建儘可能多的實現的能力,並且我需要做的改變系統行爲的唯一事情是實例化我想要使用的實現。

我想要相同的行爲,但在Elixir(讓我們使用相同的例子)。既然它不是面向對象的語言,我不能使用上面的方法。顯然我希望它是可擴展的(我可以很容易地傳遞一個字符串與我想要在每次調用中使用的存儲庫類型,並使用模式匹配來確定要使用的行爲,但這並不能很好地擴展,因爲每當我想要添加一個實現,我將不得不在每一段代碼中查找我匹配類型的模式並添加新的實現)。什麼是實現這一目標的最佳方法?

提前致謝!

+0

一個修正。 OO和功能不相互正交。真正的Elixir沒有課程,但這並不意味着它不能。 –

回答

7

假設你有兩個(或更多)存儲庫的實現,其實現相同的接口:

defmodule MyApp.Repository.Memory do 
    def get(key) do 
    # ... 
    end 

    def put(key, value) do 
    # ... 
    end 
end 

defmodule MyApp.Repository.Disk do 
    def get(key) do 
    # ... 
    end 

    def put(key, value) do 
    # ... 
    end 
end 

然後,你可以寫一個通用存儲庫模塊,只會前進的函數調用庫後端的一個的基礎上,在你的配置/ config.exs文件中的配置值:

defmodule MyApp.Repository do 
    @backend Application.get_env(:my_app, :repository_backend) 

    defdelegate [get(key), put(key, value)], to: @backend 
end 

配置可以製成使其環境特定的(只是看在默認config.exs與新創建一個混合項目210):

# config/config.exs 
import_config "#{Mix.env}.exs" 

# config/dev.exs 
config :my_app, repository_backend: MyApp.Repository.Memory 

# config/prod.exs 
config :my_app, repository_backend: MyApp.Repository.Disk 

縱觀整個代碼,然後你可以只使用MyApp.Repository模塊,而不明確地引用具體的實現方式之一:

MyApp.Repository.put(:foo, "Hello world!") 
value = MyApp.Repository.get(:foo) 
+0

事實上,虛擬函數表就是多數OO語言實際上如何實現多態。 –

+0

這是首選的http://stackoverflow.com/questions/27280964/change-backend-module-at-deploy-time-in-elixir?rq=1?我認爲後者更通用,因爲它讓您將後端設置爲流程級別而不是模塊級別。 –

+0

這取決於你的用例,但我不想在這個答案中過分複雜的東西。老實說,我已經忘記了你鏈接到的答案;-)基本的機制仍然是一樣的:在混音配置中指定後端/適配器,然後在你的代碼中使用它。 –