依赖注入dependency-injection最通俗的讲解

这篇文章解释了什么是依赖注入(又称控制反转),以及它如何改善定义业务逻辑的代码。 服务和依赖服务可以是您编写的类,也可以是来自导入库的类。例如,它可以是一个 logger 或一个 database connection。因此,您可以编写一个无需任何外部帮助即可单独运行的服务,但也可能您会很快您会达到一个点,即其中一个服务将不得不使用另一个服务的代码的地步。 让我们看一个小的例子 我们将创建一个EmailSender。此类将用于发送电子邮件。它必须在数据库中写入已发送电子邮件的信息,并记录可能发生的错误。 EmailSender 将依赖于其他三项服务:用于发送电子邮件的 SmtpClient,用于与数据库交互的 EmailRepository 以及用于记录错误的 Logger。 通常情况下我们会怎么实现呢? 1.EmailSender 的三个依赖关系被声明为属性。依赖项注入的思想是,EmailSender不应该负责创建其依赖项,它们应该从外部注入。对EmailSender来说,其配置的详细信息应该是未知的。 interface SmtpClientInterface { send(toName: string, toEmail: string, subject: string, message: string)}interface EmailRepositoryInterface { insertEmail(address: string, email: Email, status: string) updateEmailStatus(id: number, status: string)}interface LoggerInterface { error(message: string)}class EmailSender { client: SmtpClientInterface repo: EmailRepositoryInterface logger: LoggerInterface send(user: User, email: Email) { try { this.repo.insertEmail(user.email, email, "sending") this.client.send(user.email, user.name, email.subject, email.message) this.repo.updateEmailStatus(email.id, "sent") } catch(e) { this.logger.error(e.toString()) } }}2.使用 setter,您可以在EmailSender上添加setSmtpClient(),setEmailRepository()和setLogger()方法。 ...

September 30, 2019 · 8 min · jiezi

如何理解-Laravel-和-ThinkPHP-5-中的服务容器与注入

从文档说起很多人一开始看到官方的文档,无论是 Laravel 还是 ThinkPHP ,看完都是一头雾水,不求甚解。甚至都是直接跳过去,不看,反正我也不一样用得到这么高端的东西,如果在短时间内有这个念头很正常,尤其是习惯了 ThinkPHP 3 的使用者,相对引入的理念比较前沿,如果你在长时间内都不去考虑去理解,那就要看你自己的职业规划了。接下来就来一起看一下,细细追品。 从 Laravel 开始从 Laravel 的文档中看到有 bind 、 singleton 以及 instance ,这三个常用方法,接下来就一一解答。 实际应用假设我们有这样一个场景,当我们用户在进行注册时,我们需要向用户手机发送一条短信验证码,然后当用户收到验证码后在注册表单提交时还需要验证验证码是否正确。 这个需求看起来非常容易实现,对吧? 当我们拿到短信平台的开发文档后,我们只需要写出两个方法。send 和 check 分别用来发送验证码和校验验证码,下面就在不用容器的情况下来写一下伪代码。 MeiSms.php<?phpnamespace App\Tools;class MeiSms{ public function send($phone) { $code = mt_rand(1e3, 1e4 - 1); // TODO ... 通过接口发送 // 存放验证码到 Redis $cacheManager = cache(); // 设定 5 分钟失效 $cacheManager->set('sms:' . $phone, $code, 5 * 60); return true; } public function check($phone, $code) { $cacheManager = cache(); return $cacheManager->get('sms:' . $phone) === $code; }}很容易,不是吗? ...

July 3, 2019 · 4 min · jiezi

使用-TypeScript-和依赖注入实现一个聊天机器人

翻译:疯狂的技术宅原文:https://www.toptal.com/typesc...本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 类型和可测试代码是避免错误的两种最有效方法,尤其是代码随会时间而变化。我们可以分别通过利用 TypeScript 和依赖注入(DI)将这两种技术应用于JavaScript开发。 在本 TypeScript 教程中,除编译以外,我们不会直接介绍 TypeScript 的基础知识。相反,我们将会演示 TypeScript 最佳实践,因为我们将介绍如何从头开始制作 Discord bot、连接测试和 DI,以及创建示例服务。我们将会使用: Node.jsTypeScriptDiscord.js,Discord API的包装器InversifyJS,一个依赖注入框架测试库:Mocha,Chai和ts-mockitoMongoose和MongoDB,以编写集成测试设置 Node.js 项目首先,让我们创建一个名为 typescript-bot 的新目录。然后输入并通过运行以下命令创建一个新的 Node.js 项目: npm init注意:你也可以用 yarn,但为了简洁起见,我们用了 npm。 这将会打开一个交互式向导,对 package.json 文件进行配置。对于所有问题,你只需简单的按回车键(或者如果需要,可以提供一些信息)。然后,安装我们的依赖项和 dev 依赖项(这些是测试所需的)。 npm i --save typescript discord.js inversify dotenv @types/node reflect-metadatanpm i --save-dev chai mocha ts-mockito ts-node @types/chai @types/mocha然后,将package.json 中生成的 `scripts 部分替换为: "scripts": { "start": "node src/index.js", "watch": "tsc -p tsconfig.json -w", "test": "mocha -r ts-node/register \"tests/**/*.spec.ts\""},为了能够递归地查找文件,需要在tests/**/*.spec.ts周围加上双引号。 (注意:在 Windows 下的语法可能会有所不同。) ...

May 29, 2019 · 5 min · jiezi

PHP实现一个轻量级容器

什么是容器在开发过程中,经常会用到的一个概念就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。容器实现对对象的统一管理,并且确保对象实例的唯一性容器可以很轻易的找到有很多实现示例,如 PHP-DI 、 YII-DI 等各种实现,通常他们要么大而全,要么高度适配特定业务,与实际需要存在冲突。出于需要,我们自己造一个轻量级的轮子,为了保持规范,我们基于 PSR-11 来实现。 PSR-11 PSR 是 php-fig 提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11 提供了容器接口。它包含 ContainerInterface 和 两个异常接口,并提供使用建议。/** * Describes the interface of a container that exposes methods to read its entries. /interface ContainerInterface{ /* * Finds an entry of the container by its identifier and returns it. * * @param string $id Identifier of the entry to look for. * * @throws NotFoundExceptionInterface No entry was found for this identifier. * @throws ContainerExceptionInterface Error while retrieving the entry. * * @return mixed Entry. / public function get($id); /* * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * * has($id) returning true does not mean that get($id) will not throw an exception. * It does however mean that get($id) will not throw a NotFoundExceptionInterface. * * @param string $id Identifier of the entry to look for. * * @return bool / public function has($id);}实现示例我们先来实现接口中要求的两个方法abstract class AbstractContainer implements ContainerInterface{ protected $resolvedEntries = []; /* * @var array / protected $definitions = []; public function __construct($definitions = []) { foreach ($definitions as $id => $definition) { $this->injection($id, $definition); } } public function get($id) { if (!$this->has($id)) { throw new NotFoundException(“No entry or class found for {$id}”); } $instance = $this->make($id); return $instance; } public function has($id) { return isset($this->definitions[$id]); }实际我们容器中注入的对象是多种多样的,所以我们单独抽出实例化方法。 protected function make($name) { if (isset($this->resolvedEntries[$name])) { return $this->resolvedEntries[$name]; } $definition = $this->definitions[$name]; $params = []; if (is_array($definition) && isset($definition[‘class’])) { $params = $definition; $definition = $definition[‘class’]; unset($params[‘class’]); } $object = $this->reflector($definition, $params); return $this->resolvedEntries[$name] = $object; } public function reflector($concrete, array $params = []) { if ($concrete instanceof \Closure) { return $concrete($params); } elseif (is_string($concrete)) { $reflection = new \ReflectionClass($concrete); $dependencies = $this->getDependencies($reflection); foreach ($params as $index => $value) { $dependencies[$index] = $value; } return $reflection->newInstanceArgs($dependencies); } elseif (is_object($concrete)) { return $concrete; } } /* * @param \ReflectionClass $reflection * @return array / private function getDependencies($reflection) { $dependencies = []; $constructor = $reflection->getConstructor(); if ($constructor !== null) { $parameters = $constructor->getParameters(); $dependencies = $this->getParametersByDependencies($parameters); } return $dependencies; } /* * * 获取构造类相关参数的依赖 * @param array $dependencies * @return array $parameters * / private function getParametersByDependencies(array $dependencies) { $parameters = []; foreach ($dependencies as $param) { if ($param->getClass()) { $paramName = $param->getClass()->name; $paramObject = $this->reflector($paramName); $parameters[] = $paramObject; } elseif ($param->isArray()) { if ($param->isDefaultValueAvailable()) { $parameters[] = $param->getDefaultValue(); } else { $parameters[] = []; } } elseif ($param->isCallable()) { if ($param->isDefaultValueAvailable()) { $parameters[] = $param->getDefaultValue(); } else { $parameters[] = function ($arg) { }; } } else { if ($param->isDefaultValueAvailable()) { $parameters[] = $param->getDefaultValue(); } else { if ($param->allowsNull()) { $parameters[] = null; } else { $parameters[] = false; } } } } return $parameters; }如你所见,到目前为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,所以我们还需要提供一个方法. /* * @param string $id * @param string | array | callable $concrete * @throws ContainerException */ public function injection($id, $concrete) { if (!is_string($id)) { throw new \InvalidArgumentException(sprintf( ‘The id parameter must be of type string, %s given’, is_object($id) ? get_class($id) : gettype($id) )); } if (is_array($concrete) && !isset($concrete[‘class’])) { throw new ContainerException(‘数组必须包含类定义’); } $this->definitions[$id] = $concrete; }只有这样吗?对的,有了这些操作我们已经有一个完整的容器了,插箱即用。不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。class Container extends AbstractContainer implements \ArrayAccess{ public function offsetExists($offset) { return $this->has($offset); } public function offsetGet($offset) { return $this->get($offset); } public function offsetSet($offset, $value) { return $this->injection($offset, $value); } public function offsetUnset($offset) { unset($this->resolvedEntries[$offset]); unset($this->definitions[$offset]); }}这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。点击这里查看完整代码 ...

January 27, 2019 · 3 min · jiezi