PHP依赖注入和松耦合

PHP依赖注入和松耦合,php,design-patterns,interface,dependency-injection,Php,Design Patterns,Interface,Dependency Injection,我正在考虑几种不同的方法,非常感谢您的意见!我正在考虑下面的两个选择。我有两个问题要问 首选将依赖项注入主“容器”类的构造函数中,还是在容器类内创建新实例 在第二个示例中,通过构造函数注入类的依赖项,然后通过类的属性在其中进行维护。然后,当调用方法(route()、render())时,将从内部调用依赖项。我开始采用这种方法,但现在更倾向于采用与第一个示例类似的方法。我认为第一个例子更可取,但是在第二个例子中使用DI方法有什么好处吗 实际上不需要将类中的任何内容存储为属性。我可能可以重新安排一切

我正在考虑几种不同的方法,非常感谢您的意见!我正在考虑下面的两个选择。我有两个问题要问

  • 首选将依赖项注入主“容器”类的构造函数中,还是在容器类内创建新实例

  • 在第二个示例中,通过构造函数注入类的依赖项,然后通过类的属性在其中进行维护。然后,当调用方法(route()、render())时,将从内部调用依赖项。我开始采用这种方法,但现在更倾向于采用与第一个示例类似的方法。我认为第一个例子更可取,但是在第二个例子中使用DI方法有什么好处吗

  • 实际上不需要将类中的任何内容存储为属性。我可能可以重新安排一切来使用这种技术,而不会有太多麻烦,我想我更喜欢它。通过这种方式,我还可以将所有的工作移出构造函数,然后通过方法简单地访问所有内容。我走对了吗

    class App
    {
        private $config;
        private $router;
        private $renderer; 
    
        public function __construct(IConfig $config, IRouter $router, IRenderer $renderer)
        {
            $this->config = $config;
            $this->router = $router;
            $this->renderer = $renderer;
    
            $this->run();           
        }
    
        public function run()
        {
            $data = $this->router->route(new Request, $config->routes);         
            $this->renderer->render($data);
        }   
    
    }
    
    
    class App
    {
        private $config;
        private $router;
        private $renderer; 
    
        public function __construct()
        {
            $this->config = new Config;
    
            $this->run();               
        }
    
        public function run()
        {
            $this->router = new Router(new Request, $config->routes);
            $this->router->route();
    
            $this->renderer = new Renderer($this->router->getData());
            $this->renderer->render();
        }
    
    }
    

    最好将依赖项注入构造函数中

    在构造函数中创建实例会在两个类之间创建紧密耦合。使用具有清晰签名的构造函数,如

     public function __construct(IConfig $config, IRouter $router, IRenderer $renderer)
    
    我可以立即知道这个组件需要做什么

    给定一个类似

    public function __construct(); 
    
    目前还不知道该组件需要什么功能。它与每个路由器、请求和渲染器的特定实现产生了强烈的耦合,在深入挖掘类的本质之前,所有这些都不明显

    总而言之,第一种方法是文档化的、可扩展的和可测试的。
    第二种方法不透明,高度耦合,不易测试。

    虽然Orangepill提出了一个很好的观点,但我想我也应该插手。我也倾向于用一个清晰的构造函数来定义我的构造函数,但我不希望在创建实例时传递所需的对象
    有时,您会创建一个实例,从DB或某种Http请求检索数据。在您的例子中,第一个示例期望传递三个依赖项,但是谁能说您总是需要这三个依赖项呢

    输入延迟加载。下面的代码示例相当长,但(IMO)非常值得研究。如果我使用服务,我不想加载所有依赖项,除非我确定我会使用它们。这就是我定义构造函数的原因,这样我可以通过以下任一方式创建实例:

    $foo = new MyService($configObj);
    $bar = new MyService($configObj, null, $dbObj);//don't load curl (yet)
    $baz = new MyService($configObj, $curlObj);//don't load db (yet)
    
    如果我想运行一些测试,我仍然可以在构建实例时注入依赖项,或者我可以依赖一个测试配置对象,或者我也可以使用
    setDb
    setCurl
    方法:

    $foo->setCurl($testCurl);
    
    坚持第一种构造实例的方法,我可以有把握地说,如果我只调用
    getViaCurl
    方法,
    Db
    类将永远不会被加载
    getViaDb
    方法有点复杂(就像
    getDb
    方法一样)。我不建议您使用这样的方法,但这只是为了向您展示这种方法的灵活性。我可以将参数数组传递给
    getViaDb
    方法,该方法可以包含自定义连接。我还可以传递一个布尔值来控制我对该连接所做的操作(仅在这一次调用中使用它,或者将该连接分配给
    MyService
    实例)

    我希望这不太清楚,但我很累,所以我不太擅长解释这些东西。
    这里的代码,无论如何…它应该是相当自我解释

    class MyService
    {
        private $curl = null;
        private $db = null;
        private $conf = null;
        public function __construct(Config $configObj, Curl $curlObj = null, Db $dbObj = null)
        {
            $this->conf = $configObj;//you'll see why I do need this in a minute
            $this->curl = $curlObj;//might be null
            $this->db = $dbObj;
        }
    
        public function getViaCurl(Something $useful)
        {
            $curl = $this->getCurl();//<-- this is where the magic happens
            return $curl->request($useful);
        }
    
        public function getViaDb(array $params)
        {
            if (isset($params['custom']))
            {
                $db = $this->getDb($params['custom'], $params['switch']);
            }
            else
            {//default
                $db = $this->getDb();
            }
            return $db->query($params['request']);
        }
    
        public function getCurl()
        {//return current Curl, or load default if none set
            if ($this->curl === null)
            {//fallback to default from $this->conf
                $this->curl = new Curl($this->conf->getSection('CurlConf'));
            }
            return $this->curl;
        }
    
        public function setCurl(Curl $curlObj)
        {//inject after instance is created here
             if ($this->curl instanceof Curl)
             {//close current connection
                 $this->curl->close();
             }
             $this->curl = $curlObj;
        }
    
        public function setDb(Db $dbObj)
        {
            if ($this->db instanceof Db)
            {//commit & close
                $this->db->commit();
                $this->db->close();
            }
            $this->db = $dbObj;
        }
    
        //more elaborate, even:
        public function getDb(Db $custom = null, $switch = false)
        {
            if ($custom && !!$swith === true)
            {
                $this->setDb($custom);
                return $this->db;
            }
            if ($custom)
            {//use custom Db, only this one time
                return $custom;
            }
            if ($this->db === null)
            {
                $this->db = new Db($this->conf->getSection('Db'));
            }
            return $this->db;
        }
    }
    
    classmyservice
    {
    private$curl=null;
    私有$db=null;
    private$conf=null;
    公共函数构造(配置$configObj,Curl$curlObj=null,Db$dbObj=null)
    {
    $this->conf=$configObj;//您马上就会明白我为什么需要这个了
    $this->curl=$curlObj;//可能为空
    $this->db=$dbObj;
    }
    公共函数getViaCurl(一些$usiver)
    {
    $curl=$this->getCurl();//请求($use);
    }
    公共函数getViaDb(数组$params)
    {
    如果(isset($params['custom']))
    {
    $db=$this->getDb($params['custom'],$params['switch']);
    }
    其他的
    {//默认值
    $db=$this->getDb();
    }
    返回$db->query($params['request']);
    }
    公共函数getCurl()
    {//返回当前旋度,如果未设置,则加载默认值
    如果($this->curl==null)
    {//从$this->conf返回默认值
    $this->curl=newcurl($this->conf->getSection('CurlConf');
    }
    返回$this->curl;
    }
    公共函数setCurl(Curl$curlObj)
    {//在此处创建实例后注入
    if($this->curl instanceof curl)
    {//关闭当前连接
    $this->curl->close();
    }
    $this->curl=$curlObj;
    }
    公共函数setDb(Db$dbObj)
    {
    if($this->db instanceof db)
    {//提交并关闭
    $this->db->commit();
    $this->db->close();
    }
    $this->db=$dbObj;
    }
    //更详细的,甚至是:
    公共函数getDb(Db$custom=null,$switch=false)
    {
    如果($custom&&!!$swith===true)
    {
    $this->setDb($custom);
    返回$this->db;
    }
    如果($custom)
    {//使用自定义数据库,仅此一次
    返回$custom;
    }
    如果($this->db==null)
    {
    $this->db=newdb($this->conf->getSection('db');
    }
    返回$this->db;
    }
    }
    
    我知道这是一个古老的回答,但是,实现cUrl和DB的接口不是这样吗?