依赖注入是目前很多优秀框架都在使用的一个设计模式。Java的开发框架如Spring在用,PHP的Laravel/Phalcon/Symfony等也在用。好多不同语言的框架,设计思想大同小异,相互借鉴参考。熟悉了一个语言的开发框架,其它不同的框架甚至不同语言的开发框架,往往也很容易从设计理念和概念上理解。不过,有些语言因为设计特色,一些设计模式反而看似消失不见了。其实是融入了语言里面,不易察觉。我看见过这么一句话:“设计模式是编程语言固有缺陷的产物”。有一个讨论在这里:Why is IoC / DI not common in Python?

Dependency Injection 常常简称为:DI。它是实现控制反转(Inversion of Control – IoC)的一个模式。有一本依赖注入详解的书在这里:Dependency Injection 。它的本质目的是解耦,保持软件组件之间的松散耦合,为设计开发带来灵活性。

这里借用一套PHP代码的演化过程,解释依赖注入模式的出现过程。代码来自Phalcon框架文档。个人感觉,从演化出发,最能达成理解的目标,就如同数学推理一样让人信服,自然而然。想当年,我研究Windows时代的COM技术体系,看到有一本书也是这么做的 – Dan Box的《COM本质论》第1-2章,阐述了从Dll到COM组件的设计过程。

假设我们要开发一套组件,这个组件干啥目前并不重要,不过它需要连接数据库。最简单的实现,当然是把数据库的配置信息写在组件里面。

class SomeComponent

{

/**

* The instantiation of the connection is hardcoded inside

* the component, therefore it's difficult replace it externally

* or change its behavior

*/

public function someDbTask()

{

$connection = new Connection(

[

"host" => "localhost",

"username" => "root",

"password" => "secret",

"dbname" => "invo",

]

);

// ...

}

}

$some = new SomeComponent();

$some->someDbTask();

但是这么干问题很大,属于“代码的坏味道”。因为数据库配置写死了,完全没有灵活性可言。这也给后面的测试/部署/安全带来了隐患。为了解决这个问题,我们试试把配置信息拿出去,从外面传进来。

class SomeComponent

{

protected $_connection;

/**

* Sets the connection externally

*/

public function setConnection($connection)

{

$this->_connection = $connection;

}

public function someDbTask()

{

$connection = $this->_connection;

// ...

}

}

$some = new SomeComponent();

// Create the connection

$connection = new Connection(

[

"host" => "localhost",

"username" => "root",

"password" => "secret",

"dbname" => "invo",

]

);

好一点。不过如果我们在很多地方都要用这个组件,那么意味着每次用的时候,都要创建这么一个连接配置对象,不仅冗余,而且难以变更和管理。我们把这个连接配置对象单独放在一个地方管理,DRY原则。

class Registry

{

/**

* Returns the connection

*/

public static function getConnection()

{

return new Connection(

[

"host" => "localhost",

"username" => "root",

"password" => "secret",

"dbname" => "invo",

]

);

}

}

class SomeComponent

{

protected $_connection;

/**

* Sets the connection externally

*/

public function setConnection($connection)

{

$this->_connection = $connection;

}

public function someDbTask()

{

$connection = $this->_connection;

// ...

}

}

$some = new SomeComponent();

// Pass the connection defined in the registry

$some->setConnection(Registry::getConnection());

$some->someDbTask();

可行。不过有个问题,连接对象每次使用都是重复创建,浪费资源。再改一下,改成共享式,近似于单件模式。

class Registry

{

protected static $_connection;

/**

* Creates a connection

*/

protected static function _createConnection()

{

return new Connection(

[

"host" => "localhost",

"username" => "root",

"password" => "secret",

"dbname" => "invo",

]

);

}

/**

* Creates a connection only once and returns it

*/

public static function getSharedConnection()

{

if (self::$_connection === null) {

self::$_connection = self::_createConnection();

}

return self::$_connection;

}

/**

* Always returns a new connection

*/

public static function getNewConnection()

{

return self::_createConnection();

}

}

class SomeComponent

{

protected $_connection;

/**

* Sets the connection externally

*/

public function setConnection($connection)

{

$this->_connection = $connection;

}

/**

* This method always needs the shared connection

*/

public function someDbTask()

{

$connection = $this->_connection;

// ...

}

/**

* This method always needs a new connection

*/

public function someOtherDbTask($connection)

{

}

}

$some = new SomeComponent();

// This injects the shared connection

$some->setConnection(

Registry::getSharedConnection()

);

$some->someDbTask();

// Here, we always pass a new connection as parameter

$some->someOtherDbTask(

Registry::getNewConnection()

);

这就是“依赖注入”模式了,它解决了组件的依赖项和组件之间的过度耦合问题。不过还有个麻烦:如果这个组件依赖项很多怎么办?每次都要创建并设置一大堆依赖项。

// Create the dependencies or retrieve them from the registry

$connection = new Connection();

$session = new Session();

$fileSystem = new FileSystem();

$filter = new Filter();

$selector = new Selector();

// Pass them as constructor parameters

$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);

// ... Or using setters

$some->setConnection($connection);

$some->setSession($session);

$some->setFileSystem($fileSystem);

$some->setFilter($filter);

$some->setSelector($selector);

每次使用这个组件,都要创建一堆附加的依赖项。如果以后我们修改组件依赖,那么必须挨个改掉。代码的坏味道又来了。再改。

class SomeComponent

{

// ...

/**

* Define a factory method to create SomeComponent instances injecting its dependencies

*/

public static function factory()

{

$connection = new Connection();

$session = new Session();

$fileSystem = new FileSystem();

$filter = new Filter();

$selector = new Selector();

return new self($connection, $session, $fileSystem, $filter, $selector);

}

}

估计好多人走到这一步就会停下脚步了。代码用个工厂模式不就行了嘛。可是你对比下开头的代码,组件和它的依赖项的耦合不就又来了么?现在,问题又回到开头了。

一个更好的办法是使用依赖注入容器。它就如同一个全局的注册表,像桥一样获取依赖项,并解耦。

use Phalcon\Di;

use Phalcon\DiInterface;

class SomeComponent

{

protected $_di;

public function __construct(DiInterface $di)

{

$this->_di = $di;

}

public function someDbTask()

{

// Get the connection service

// Always returns a new connection

$connection = $this->_di->get("db");

}

public function someOtherDbTask()

{

// Get a shared connection service,

// this will return the same connection every time

$connection = $this->_di->getShared("db");

// This method also requires an input filtering service

$filter = $this->_di->get("filter");

}

}

$di = new Di();

// Register a "db" service in the container

$di->set(

"db",

function () {

return new Connection(

[

"host" => "localhost",

"username" => "root",

"password" => "secret",

"dbname" => "invo",

]

);

}

);

// Register a "filter" service in the container

$di->set(

"filter",

function () {

return new Filter();

}

);

// Register a "session" service in the container

$di->set(

"session",

function () {

return new Session();

}

);

// Pass the service container as unique parameter

$some = new SomeComponent($di);

$some->someDbTask();”

问题解决。获取依赖项只要通过DI容器接口操作,不需要的部分甚至都不会创建,节约了资源。

在Java的Spring框架里面,依赖注入和控制反转设计思想是近似的,道理相同但是实现不同。因为编程语言各有各的设计特点可以利用。

Spring框架的依赖注入容器接口是:ApplicationContext.

ApplicationContext context

= new ClassPathXmlApplicationContext("applicationContext.xml");

但是Spring使用DI,有好几种方法,比如注解式,利用了语言的功能。

具体解释参考这篇文章,不翻译了。

python 依赖注入_Dependency Injection-依赖注入详解相关推荐

  1. pycharm导入python环境是空的_PyCharm导入python项目并配置虚拟环境的教程详解

    PyCharm导入python项目并配置虚拟环境的教程详解 进入PyCharm后,点击File→Open,然后在弹窗中选择需要导入项目的文件夹: 打开了python项目后,需要配置该项目对应的pyth ...

  2. python中argparse模块关于 parse_args() 函数详解(全)

    目录 前言 1. 函数讲解 2. 基本用法 3. 实战讲解 前言 原理:命令行解析使用argparse包 作用:命令行传参赋值 可用在机器学习深度学习 或者 脚本运行等 了解这个函数需要了解其背后的原 ...

  3. 【python教程入门学习】Python函数定义及传参方式详解(4种)

    这篇文章主要介绍了Python函数定义及传参方式详解(4种),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 一.函数初识 1.定 ...

  4. python argv 详解_对python中的argv和argc使用详解

    主要问题 为什么argv中第一个,即index=0的内容就是文件名? python中argc是用什么实现的? 概念解释 argc:argument counter,命令行参数个数 argv:argum ...

  5. Python中的__name__和__main__含义详解

    背景 在写Python代码和看Python代码时,我们常常可以看到这样的代码: ? 1 2 3 4 5 def main():     ...... if __name__ == "__ma ...

  6. 对python 数据处理中的LabelEncoder 和 OneHotEncoder详解

    对python 数据处理中的LabelEncoder 和 OneHotEncoder详解_起飞的木木的博客-CSDN博客_labelencoder原理

  7. python协程详解_对Python协程之异步同步的区别详解

    一下代码通过协程.多线程.多进程的方式,运行代码展示异步与同步的区别. import gevent import threading import multiprocessing # 这里展示同步和异 ...

  8. python的raw_ input是什么意思-对python中raw_input()和input()的用法详解

    最近用到raw_input()和input()来实现即时输入,就顺便找了些资料来看,加上自己所用到的一些内容,整理如下: 1.raw_input() raw_input([prompt]) -> ...

  9. python中str和input_对python中raw_input()和input()的用法详解

    最近用到raw_input()和input()来实现即时输入,就顺便找了些资料来看,加上自己所用到的一些内容,整理如下: 1.raw_input() raw_input([prompt]) -> ...

  10. python中文编码-python中文编码与json中文输出问题详解

    前言 python2.x版本的字符编码有时让人很头疼,遇到问题,网上方法可以解决错误,但对原理还是一知半解,本文主要介绍 python 中字符串处理的原理,附带解决 json 文件输出时,显示中文而非 ...

最新文章

  1. 我的世界java测试版下载_我的世界中国版PC不删档测试版_网易我的世界JAVA版测试版单机游戏下载...
  2. wp如何代码实现锁屏
  3. Lidar SLAM | 地面三维激光雷达测试报告
  4. python写一个计时器_Python 实现一个计时器
  5. 为什么阿里强制 boolean 类型变量不能使用 is 开头
  6. dell保修(dell保修多久)
  7. Windows XP更新后出现“你可能是盗版软件受害者”解决方法
  8. 1.3 飞桨开源深度学习平台介绍
  9. JavaScript自定义事件--高级技巧
  10. STM32的QSPI通信(学习笔记)
  11. 怎样修改游戏服务器里的数据库,修改游戏服务器中的数据库
  12. 信号线上串接电阻的作用
  13. 取消“打开文件-安全警告”的方法
  14. 怎样阻止Linux服务器执行rm -rf /*命令
  15. Sql Server事务+隔离级别+阻塞+死锁
  16. 深度解析IC产品的质量与可靠性测试方法
  17. 自学python能找到工作么-27岁0基础自学Python,多久可以找到工作?
  18. 一文带你全面了解跨境电商10大平台
  19. Attention Mechanisms in Computer Vision: A Survey综述详解
  20. excel 数据透视表的使用 每个id每天数据不同的汇总

热门文章

  1. 视频转换为GIF的方法有哪些
  2. 中国城市地区经纬度表
  3. 空调省电十大诀窍 【80后必看】
  4. linux adb 权限不够,root后adb shell权限问题
  5. PermitRootLogin是基于UID还是用户名?
  6. SSH Config 允许使用root密码登陆 PermitRootLogin
  7. 我收藏的奥运会高清背景
  8. githubhosts无法建立 SSL 连接。
  9. 电脑不支持MOV怎么办 怎么快速将mov格式转换为MP4 1
  10. java-算法-递归-汉诺塔