依赖注射:实际需要时拉出所需的组件 [英] Dependency Injection: pulling required components when they are actually needed

查看:137
本文介绍了依赖注射:实际需要时拉出所需的组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

DI背后的要点是减轻一个类创建和准备依赖于它们的对象,并将其推入。这听起来很合理,但有时一个类不需要所有的对象,被推入它来执行其功能。这样做的原因是在早期的无效用户输入或所需对象之一抛出的异常之前发生的早期返回,或在运行代码块之前实例化对象所必需的某个值的不可用性。



更实际的例子:




  • 注入永远不会使用的数据库连接对象,因为用户数据不通过验证(前提是没有触发器用于验证此数据)

  • 注入收集输入的类似excel的对象(PHPExcel eg)库被拉入并且从不使用,因为验证在写入之前引发异常)

  • 在类中确定的变量值,而不是运行时的注入;例如,路由组件确定基于用户输入

  • 应该调用的控制器(或命令)类和方法,尽管这可能是设计问题,但是实质性服务类,这取决于很多组件,但是每个请求只使用其中的1/3(原因是为什么我倾向于使用命令类而不是控制器)



所以,以某种方式推动所有必要的组件与某些组件的创建和使用方式相反,延迟加载是一个有点不切实际和有影响力的性能。就PHP而言,更多的文件被加载,解析和编译。这是非常痛苦的,如果被推入的对象有自己的依赖。



我看到3个方法,其中2个听起来不是很好:注入一个工厂

  • 注入注射器(反模式)

  • 注入一些外部函数,一旦达到相关点,就从
    类中调用一些外部函数(smtg像在数据验证完成后检索
    PHPExcel实例);这是我的
    由于其灵活性而倾向于使用



  • 问题是什么是处理这种情况的最佳方法/你们使用什么?



    更新
    @GordonM这里是3种方法的示例:

      //注入工厂示例
    接口IFactory {
    function factory();
    }
    class Bartender {
    protected $ _factory;

    public function __construct(IFactory $ f){
    $ this-> _factory = $ f;
    }
    public function order($ data){
    //验证$ data
    // ...返回或抛出异常
    //验证传递,订单必须被保存
    $ db = $ this-> _factory-> factory(); //!工厂实例* num必需组件
    $ db-> insert('orders',$ data);
    // ...
    }
    }

    / *
    注入提供者示例
    假设提供者准备必要的对象
    (即注入它们的依赖关系)
    * /
    接口IProvider {
    function get($ uid);
    }
    class Router {
    protected $ _provider;

    public function __construct(IProvider $ p){
    $ this-> _provider = $ p;
    }
    public function route($ str){
    // ...匹配$ str与路由解析类和方法
    $ inst = $ this-> _provider- >获得($类);
    // ...
    }
    }

    //注入回调(旧时尚方式)
    class MyProvider {
    protected $ _db ;
    public function getDb(){
    $ this-> _db = $ this-> _db? $ this-> _db:new mysqli();
    return $ this-> _db;
    }
    }
    class Bartender {
    protected $ _db;

    public function __construct(array $ callback){
    $ this-> _db = $ callback;
    }
    public function order($ data){
    //验证$ data
    // ...返回或抛出异常
    //验证传递,订单必须被保存
    $ db = call_user_func_array($ this-> _db,array());
    $ db-> insert('orders',$ data);
    // ...
    }
    }
    //在引擎盖下工作的方式:
    $ provider = new MyProvider();
    $ db = array($ provider,'getDb');
    新的调酒师($ db);

    //注入回调(PHP 5.3方式)
    class Bartender {
    protected $ _db;

    public function __construct(Closure $ callback){
    $ this-> _db = $ callback;
    }
    public function order($ data){
    //验证$ data
    // ...返回或抛出异常
    //验证传递,订单必须被保存
    $ db = call_user_func_array($ this-> _db,array());
    $ db-> insert('orders',$ data);
    // ...
    }
    }
    //在引擎盖下工作的方式:
    static $ conn = null;
    $ db = function()use($ conn){
    $ conn = $ conn? $ conn:new mysqli();
    return $ conn;
    };
    new Bartender($ db);


    解决方案

    我一直在想这个问题在计划一个我想要尽可能正确的重大项目(坚持LoD,没有硬编码依赖等)的计划。我的第一个想法是注入工厂的方法,但我不知道这是怎么回事。 Google的干净代码谈话声称,如果您通过一个对象来获取您真正想要的对象,那么您就违反了LoD。这似乎排除了注入工厂的想法,因为你必须通过工厂到达你想要的东西。也许我错过了一些点,使它没关系,但直到我知道我正在考虑其他方法。



    如何执行功能注入?我想象你正在传递一个回调函数来实现你想要的对象,但一个代码示例是好的。



    如果您可以使用您提到的三种样式的代码示例更新您的问题,可能会很有用。即使是反模式,我也非常希望看到注射注射器。


    The gist behind DI is to relieve a class from creating and preparing objects it depends on and pushing them in. This sounds very reasonable, but sometimes a class does not need all the objects, that are being pushed into it to carry out its function. The reason behind this is an "early return" that happens upon invalid user input or an exception thrown by one of the required objects earlier or the unavailability of a certain value necessary to instantiate an object until a block of code runs.

    More practical examples:

    • injecting a database connection object that will never be used, because the user data does not pass validation (provided that no triggers are used to validate this data)
    • injecting excel-like objects (PHPExcel e.g.) that collect input (heavy to load and instantiate because a whole library is pulled in and never used, because validation throws an exception earlier than a write occurs)
    • a variable value that is determined within a class, but not the injector at runtime; for instance, a routing component that determines the controller (or command) class and method that should be called based on user input
    • although this might be a design problem, but a substantial service-class, that depends on a lot of components, but uses only like 1/3 of them per request (the reason, why i tend to use command classes instead of controllers)

    So, in a way pushing in all necessary components contradicts "lazy-loading" in the way that some components are created and never used, that being a bit unpractical and impacting performance. As far as PHP is concerned - more files are loaded, parsed and compiled. This is especially painful, if the objects being pushed in have their own dependencies.

    i see 3 ways around it, 2 of which don't sound very well:

    • injecting a factory
    • injecting the injector (an anti-pattern)
    • injecting some external function, that gets called from within the class once a relevant point is reached (smtg like "retrieve a PHPExcel instance once data validation finished"); this is what i tend to use due to its flexibility

    The question is what's the best way of dealing with such situations / what do you guys use?

    UPDATE: @GordonM here are the examples of 3 approaches:

    //inject factory example
    interface IFactory{
        function factory();
    }
    class Bartender{
        protected $_factory;
    
        public function __construct(IFactory $f){
            $this->_factory = $f;
        }
        public function order($data){
            //validating $data
            //... return or throw exception
            //validation passed, order must be saved
            $db = $this->_factory->factory(); //! factory instance * num necessary components
            $db->insert('orders', $data);
            //...
        }
    }
    
    /*
    inject provider example
    assuming that the provider prepares necessary objects
    (i.e. injects their dependencies as well)
    */
    interface IProvider{
        function get($uid);
    }
    class Router{
        protected $_provider;
    
        public function __construct(IProvider $p){
            $this->_provider = $p;
        }
        public function route($str){
            //... match $str against routes to resolve class and method
            $inst = $this->_provider->get($class);
            //...
        }
    }
    
    //inject callback (old fashion way)
    class MyProvider{
        protected $_db;
        public function getDb(){
            $this->_db = $this->_db ? $this->_db : new mysqli();
            return $this->_db;
        }
    }
    class Bartender{
        protected $_db;
    
        public function __construct(array $callback){
            $this->_db = $callback;
        }
        public function order($data){
            //validating $data
            //... return or throw exception
            //validation passed, order must be saved
            $db = call_user_func_array($this->_db, array());
            $db->insert('orders', $data);
            //...
        }
    }
    //the way it works under the hood:
    $provider = new MyProvider();
    $db = array($provider, 'getDb');
    new Bartender($db);
    
    //inject callback (the PHP 5.3 way)
    class Bartender{
        protected $_db;
    
        public function __construct(Closure $callback){
            $this->_db = $callback;
        }
        public function order($data){
            //validating $data
            //... return or throw exception
            //validation passed, order must be saved
            $db = call_user_func_array($this->_db, array());
            $db->insert('orders', $data);
            //...
        }
    }
    //the way it works under the hood:
    static $conn = null;
    $db = function() use ($conn){
        $conn = $conn ? $conn : new mysqli();
        return $conn;
    };
    new Bartender($db);
    

    解决方案

    I've been thinking about this problem a lot lately in planning of a major project that I want to do as right as humanly possible (stick to LoD, no hard coded dependencies, etc). My first thought was the "Inject a factory" approach as well, but I'm not sure that's the way to go. The Clean Code talks from Google made the claim that if you reach through an object to get the object you really want then you're violating the LoD. That would seem to rule out the idea of injecting a factory, because you have to reach through the factory to get what you really want. Maybe I've missed some point there that makes it okay, but until I know for sure I'm pondering other approaches.

    How do you do the function injection? I'd imagine you're passing in a callback that does the instantiation of the object you want, but a code example would be nice.

    If you could update your question with code examples of how you do the three styles you mentioned it might be useful. I'm especially keen to see "injecting the injector" even if it is an antipattern.

    这篇关于依赖注射:实际需要时拉出所需的组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆