2016-01-23 7 views
3

Il s'agit principalement d'une question de programmation fonctionnelle plutôt que d'Elixir, mais puisque j'apprends Elixir ce serait bien si quelqu'un peut y répondre en utilisant cette langue. Même ainsi, si quelqu'un veut donner une réponse plus générale, ce sera apprécié.Elixir - Changement de comportement

Je suis moi-même un programmeur OO et je ne peux pas comprendre comment changer le comportement d'un composant basé sur un fichier de configuration (par exemple).

Exemple: J'ai une application qui charge/enregistre des utilisateurs à partir d'une base de données. Dans un environnement de production, je souhaite que mes utilisateurs soient sauvegardés et récupérés à partir d'une base de données MongoDB, tandis qu'en développement et en test, je souhaite utiliser une carte en mémoire. Si je programmais un système donné dans une langue OO (disons Java), je ferais simplement une interface nommée "UserRepository" avec 2 implémentations: "MemoryUserRepository" et "MongoDBUserRepository". J'instancierais alors le Repository correspondant basé sur un fichier de configuration (ou le coderait en dur, peu importe) au démarrage et juste après, tous les objets qui interagissent avec le Repository ne connaîtront jamais son implémentation (ils utiliseront un référentiel, mais ne s'en souciera jamais si c'est en mémoire ou en mongo). Cela me donne la possibilité de créer autant d'implémentations que je veux et la seule chose que je dois faire pour changer le comportement du système est d'instancier l'implémentation que je veux utiliser.

Je veux le même comportement mais dans Elixir (utilisons le même exemple). Comme ce n'est pas un langage orienté objet, je ne peux pas utiliser l'approche ci-dessus. Évidemment, je veux qu'il soit extensible (je pourrais facilement passer une chaîne avec le type de référentiel que je veux utiliser dans chaque appel et utiliser la correspondance de modèles pour déterminer le comportement à utiliser, mais cela ne va pas bien parce que chaque fois vouloir ajouter une implémentation, je devrai regarder dans chaque morceau de code je suis le modèle correspondant au type et ajouter la nouvelle implémentation). Quelle serait la meilleure approche pour y parvenir?

Merci d'avance!

+0

Une correction. OO et Functional ne sont pas orthogonaux entre eux. True Elixir n'a pas de cours mais cela ne veut pas dire qu'il ne le peut pas. –

Répondre

7

Supposons que vous ayez ces deux (ou plus) mises en œuvre du référentiel, qui mettent en œuvre la même interface:

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 

Vous pouvez alors écrire un module de référentiel général qui sera juste en avant la fonction des appels à l'un des référentiels backends , sur la base d'une valeur de configuration dans votre config/fichier config.exs:

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

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

La configuration peut être fait pour qu'il soit spécifique l'environnement (il suffit de regarder les config.exs par défaut dans un projet de mélange fraîchement créé avec mix new my_app):

# 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 

Tout au long de votre code entier, vous pouvez simplement utiliser le module MyApp.Repository sans faire référence explicitement l'une des implémentations spécifiques:

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

En effet, les tables de fonctions virtuelles sont exactement comment le polymorphisme est réellement implémenté dans la plupart des langages OO. –

+0

Est-ce préférable à celui décrit dans http://stackoverflow.com/questions/27280964/change-backend-module-at-deploy-time-in-elixir?rq=1? Je pense que ce dernier est plus polyvalent car il vous permet de définir le backend au niveau du processus plutôt qu'au niveau du module. –

+0

Cela dépend de votre cas d'utilisation, mais je ne voulais pas trop compliquer les choses dans cette réponse.Honnêtement, j'avais déjà oublié la réponse à laquelle vous étiez lié ;-) Le mécanisme de base reste le même: spécifier le backend/adaptateur dans votre config mix, puis l'utiliser dans votre code. –