Pour comprendre l'injection de dépendances, j'ai lu plusieurs articles Java.
Aujourd'hui, le dernier que j'ai lu, qui était le plus complet sur l'injection de dépendances, était celui-ci : https://martinfowler.com/articles/injection.html
Mais je ne suis toujours pas convaincu par l'utilité d'utiliser un framework d'injection de dépendances, quel que soit le projet.
Dans le code source de injeqt, j'ai lu un exemple de "hello world" :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | #include <injeqt/injector.h> #include <injeqt/module.h> #include <QtCore/QObject> #include <iostream> #include <memory> #include <string> class hello_service : public QObject { Q_OBJECT public: hello_service() {} virtual ~hello_service() {} std::string say_hello() const { return {"Hello"}; } }; class world_service : public QObject { Q_OBJECT public: world_service() {} virtual ~world_service() {} std::string say_world() const { return {"World"}; } }; class hello_factory : public QObject { Q_OBJECT public: Q_INVOKABLE hello_factory() {} virtual ~hello_factory() {} Q_INVOKABLE hello_service * create_service() { return new hello_service{}; } }; class hello_client : public QObject { Q_OBJECT public: Q_INVOKABLE hello_client() : _s{nullptr}, _w{nullptr} {} virtual ~hello_client() {} std::string say() const { return _s->say_hello() + " " + _w->say_world() + "!"; } private slots: INJEQT_INIT void init() { std::cerr << "all services set" << std::endl; } INJEQT_DONE void done() { std::cerr << "ready for destruction" << std::endl; } INJEQT_SET void set_hello_service(hello_service *s) { _s = s; } INJEQT_SET void set_world_service(world_service *w) { _w = w; } private: hello_service *_s; world_service *_w; }; class module : public injeqt::module { public: explicit module() { _w = std::unique_ptr<world_service>{new world_service{}}; add_type<hello_client>(); add_type<hello_factory>(); add_factory<hello_service, hello_factory>(); add_ready_object<world_service>(_w.get()); } virtual ~module() {} private: std::unique_ptr<world_service> _w; }; int main() { auto modules = std::vector<std::unique_ptr<injeqt::module>>{}; modules.emplace_back(std::unique_ptr<injeqt::module>{new module{}}); auto injector = injeqt::injector{std::move(modules)}; auto client = injector.get<hello_client>(); auto hello = client->say(); std::cout << hello << std::endl; } #include "hello-world.moc" |
Dans la suite de mon message, pour faciliter la compréhension, je vais décomposer hello_service en deux classes : une classe abstraite hello_service_base et une classe concrète qui en dérive, hello_service_impl. De même, je vais décomposer world_service en world_service_base et world_service_impl.
Alors, pour avoir au moins autant d'indirections que dans l'exemple qui utilise injeqt, j'aurais codé ainsi :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | #include <iostream> #include <memory> #include <string> class hello_service_base { public: hello_service_base() {} virtual ~hello_service_base() {} virtual std::string say_hello() const = 0; }; class hello_service_impl final : public hello_service_base { public: std::string say_hello() const final {return "Hello";} }; class world_service_base { public: world_service_base() {} virtual ~world_service_base() {} virtual std::string say_world() const = 0; }; class world_service_impl final : public world_service_base { public: std::string say_world() const final {return "World";} }; //! Rappel de l'intérêt d'une fabrique : //! On a une indirection sur la création d'un objet hello_service_base. //! Un jour, on pourrait changer l'implémentation de create_service() //! pour retourner un objet d'un autre type que hello_service_impl. class hello_factory final { public: static hello_factory& instance() { static hello_factory soleInstance; return soleInstance; } std::unique_ptr<hello_service_base> create_service() const { return std::make_unique<hello_service_impl>(); } private: ~hello_factory() {} }; class hello_client final { public: hello_client(hello_service_base& h, world_service_base& w) : m_hello_service(h), m_world_service(w) {} std::string say() const { return m_hello_service.say_hello() + " " + m_world_service.say_world() + "!"; } private: hello_service_base& m_hello_service; world_service_base& m_world_service; }; //! Indirection sur la construction des objets qui dérivent de hello_service_base et de world_service_base. //! Pour faire une analogie avec l'injection de dépendances dans Spring de Java, //! la classe présente a le même rôle que le fichier XML qui contient les "beans". class configuration final { public: static configuration& instance() { static configuration soleInstance; return soleInstance; } hello_service_base& get_hello_service() {return *m_hello_service;} world_service_base& get_world_service() {return m_world_service;} private: configuration() : m_hello_service{hello_factory::instance().create_service()}, m_world_service{} {} std::unique_ptr<hello_service_base> m_hello_service; world_service_impl m_world_service; }; int main() { hello_service_base& hello_service = configuration::instance().get_hello_service(); world_service_base& world_service = configuration::instance().get_world_service(); hello_client client{hello_service, world_service}; std::cout << client.say() << std::endl; } |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | class hello_client final { public: std::string say() const { return m_hello_service.say_hello() + " " + m_world_service.say_world() + "!"; } private: hello_service_impl m_hello_service; world_service_impl m_world_service; }; |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class hello_client final { public: hello_client(hello_service_base& h, world_service_base& w) : m_hello_service(h), m_world_service(w) {} std::string say() const { return m_hello_service.say_hello() + " " + m_world_service.say_world() + "!"; } private: hello_service_base& m_hello_service; world_service_base& m_world_service; }; |
Les développeurs Java disent aussi que, avec l'injection de dépendances, on peut configurer les objets utilisés.
En général, il s'agit d'un fichier de configuration XML (voir les "beans" dans Spring de Java) ou d'un bout de code lié à un certain framework.
Mais je ne vois pas l'utilité de passer par un framework. Il suffit d'écrire une classe comme la classe configuration de mon exemple.