1,概述
适配器模式, Adapter Pattern, 指的是将一个接口转换为另一个期望的接口. 目的是使得原本不匹配的接口变得兼容.
例如, 我现在需要插入网线上网, 但是我的笔记本没有网线插口, 只有usb插口. 此时就需要一个转换器, 将usb接口转换为网线插口. 这个转换器这就是一个适配器.
在上面的例子中, 有三个角色:
- 适配器, 就是接口转换器.
- 待适配实例, 就是我的笔记本电脑, 需要适配一个网线插口.
- 适配目标, 就是我的网线插口. 就是待适配实例需要适配的目标.
以此为例, 编码演示.
2,先看UML结构
图中所示, 适配器使用一个属性保存待适配的实例, 同时实现了适配目标中定义的方法.
就是我的适配器, 需要能够找到我的笔记本电脑, 同时需要满足插网线的操作.
3,再看代码演示
代码演示中, 我们会定义一个适配目标接口, 就是网线操作接口. 以及一个适配器类, 和一个笔记本电脑类. 用来演示, 适配器使得笔记本可以使用网线上网的案例.
3.1,目标接口, 网线操作接口
/**
* 网线接口
*/
interface INetInterface
{
/**
* RJ45(网线口)传输方法
* @return [type] [description]
*/
publicfunction transferRJ45();
}
一个定义了RJ45(网线口)标准的网线操作接口. 我们的目标就是要适配该接口.
3.2,笔记本类,我们的待适配目标
/**
* 笔记本类
*/
class PC
{
/**
* 笔记本中仅仅存在USB数据传输
* @return [type] [description]
*/
publicfunction transferUSB()
{
// 代码略 ...
}
}
一个待适配实例类, 我们的笔记本目前仅仅支持USB传输, 不支持RJ45传输.
因为不能更改网线和笔记本, 因此需要中间的适配器.
3.3,适配器类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 适配器
*/
class NetAdapterimplements INetInterface
{
// 适配器引用待适配示例对象笔记本
private$pc;
publicfunction __construct(PC$pc)
{
$this->pc= $pc;
}
/**
* RJ45(网线口)传输方法
* @return [type] [description]
*/
publicfunction transferRJ45(){
// 方法实现的主要内容就是
// 由待适配对象的相应方法, 完成RJ45操作方法
$this->pc->transferUSB();
}
}
适配类, 实现了目标接口, 因此在使用目标时, 直接使用该适配器即可.
语法上, 实现了接口, 重写了接口的方法, 由待适配示例完成适配目标方法即可.
3.4,使用适配器
// 测试演示
// 获取适配器, 将PC传递
$adapter= newNetAdapter(newPC());
// 此时要使用网线传输, 则直接由适配器完成即可
$adapter->transferRJ45();
// 网线插到适配器上了, 此时适配器就转换了操作, 让PC完成处理了. 适配工作完成
当我们需要在笔记本上使用RJ45网线时, 直接使用适配器,就是将网线插在适配器上. 由于是笔记本的适配器,因此 适配器是可以找到电脑笔记本实例的. 在适配器内部完成了转换工作.
4,分析
由于待适配实例中方法与适配目标接口方法不一致问题, 由适配器完成转换. 此时适配器是专门为电脑设计的, 因此适配器需要获取电脑笔记本对象. 也就意味着, 如果待适配实例发生改变, 则我们的适配器也要随之更新, 构建新的适配器出现.
上面代码演示的实现方式, 通常叫做对象适配模式 , 体现就是适配器的属性引用了待适配的示例.
除了对象适配模式, 还有一种典型的类适配模式来实现适配模式, 但通常需要使用多继承机制, 由于语言不支持多继承特性, 暂且不与演示.
5,结语
类似的模式有, 代理模式, 装饰者模式 等. 请持续关注呢.
一家之言, 欢迎补充, 讨论, 拍砖.
如有帮助, 谢谢转发哈!
1,概述
单例模式, singleton pattern, 顾名思义, 就是一个类仅仅可以存在一个实例. 故称单例模式, 也是一个规范创建对象的一种设计模式.
使用场景, 是程序周期中, 使用某个类的一个对象, 即可完全完成全部任务时, 可以选择使用单例模式设计类. 进而保证尽可以有一个实例.
实操时, 通常有懒惰式和登记式单例实现(这个受限与应用语言语法).
2,懒惰式实现
实现思路是:
通过私有化构造方法和克隆方法, 使得类不能在类外实例化, 实例化好的对象也不能被克隆. 就意味着不能任意生成新对象.
同时提供一个静态方法, 生产该类的对象. 该静态方法中实现了如果对象已经实例化过, 则直接返回其对象的引用. 反之, 如对象没有被实例化过, 执行实例化, 存储起来. 再返回.
2.1,单例实现的Dao
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
classDao
{
// 存储当前类实例的静态属性
privatestatic $instance;
/**
* 私有构造方法, 目的不能在类外部实例化
*/
privatefunction __construct()
{
// 代码略...
}
/**
* 私有化克隆方法, 目的不能在类外克隆对象
* @return [type] [description]
*/
privatefunction __clone()
{
// 代码略...
}
/**
* 获取单例对象
*/
publicstatic functioninstance()
{
// 如果没有实例化
if(!self::$instanceinstanceof self){
// 实例化类对象, 存储在静态属性$instance中
self::$instance= newself();
}
returnself::$instance;
}
}
以上代码可以做个标签辅助记忆: 三私一公
2.2,多次获取对象演示
$dao1= Dao::instance();
$dao2= Dao::instance();
$dao3= Dao::instance();
var_dump($dao1,$dao2,$dao3);
// 结果
// object(Dao)#1 (0) { } object(Dao)#1 (0) { } object(Dao)#1 (0) { }
由结果的输出#1, 可以看出, 三个变量引用的是同一个对象. 单例目的达到!
该实现之所以被称之为是懒惰式, 是因为, 对象的生成是在第一次使用时才实例化的. 如果不使用, 则不实例化. 因此成为懒惰方式. (可想的勤劳方式就是在类初始化时, 同时实例化该类的对象, 用的时候直接返回即可. 但PHP语法不支持private static $intance=new self()这类的语法, 因此就只有这个懒惰式了)
懒惰式的优势, 在于如果没有实例化, 则节省资源.
3,登记式实现
所谓的登记式, 指的是除了需要单例效果的类之外, 提供一个其他代码完成对某些类的单例等级. 通常使用类名与对象映射的方式存储. 如果类登记过对象, 则使用时直接返回, 否者实例化,存储再返回.
说白了, 就是将懒惰式中instance()方法的逻辑, 搬到类外, 其他代码负责完成和记录.
3.1 登记类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
classSingleton
{
// 实例化好的对象列表
privatestatic $object_list= [];
/**
* 单例对象生产
* @param string $class 类名
* @return object
*/
publicstatic functioninstance($class)
{
// 判断某个类是否实例化了对象
if(!isset(self::$object_list[$class])|| !self::$object_list[$class]instanceof $class){
// 没有, 实例化, 存储
self::$object_list[$class]= new$class();
}
returnself::$object_list[$class];
}
}
可以视为一个工厂类, 专门用来生产单例对象. 生产完毕, 存储起来, 下次再用, 直接返回.
3.2,测试
classDao
{
// 代码略...
}
$dao1= Singleton::instance('Dao');
$dao2= Singleton::instance('Dao');
$dao3= Singleton::instance('Dao');
var_dump($dao1,$dao2,$dao3);
// 结果
// object(Dao)#1 (0) { } object(Dao)#1 (0) { } object(Dao)#1 (0) { }
由结果可知, 是同一个对象.
代理式的由来, 就是原本是Dao的功能, 被Singleton这个代理完成了.
其优势是将功能与单例在Dao中解耦了. 同时Dao也更加灵活, 可以适用于单例和非单例的情况了.
4,结语
单例模式这种, 适应的情况非常典型. 因此不要可以去使用, 只有确实一个对象就能搞定的情况下, 才可以使用. 实际操作中, 数据库连接往往使用该设计模式. 因为一个连接可以执行所有的SQL了. 情况吻合, 隐藏可以使用单例模式.
一家之言, 欢迎讨论, 拍砖.
PS: 至此, 创建型设计模式就完毕了.
但是结构型, 行为型, 架构型还会继续.
1 概述
通常有三种工厂模式可用: 简单工厂, 工厂方法, 抽象工厂。他们都是在解决动态创建对象的问题。针对于不同的情况和场景,选择合适的工厂模式。
上文中介绍简单工厂模式(输入”简单工厂模式”可以查看)。
本文讨论工厂方法模式(Factory Method Pattern)。
首先, 无论是简单工厂还是工厂方法模式,解决的是同一类问题,动态创建对象的问题。
但简单工厂模式存在一个问题, 如果需要生产的对象增加了, 就需要更改工厂类的方法, 不满足开放-封闭的原则.也使得维护难度增大.
如何使得工厂的维护变得容易, 可以满足开放-封闭原则呢?
so easy, 将具体的生产工作下放到工厂的子类中。就是一个工厂存在多个子工厂,具体的产品的生产工作,由对应的子工厂完成,而上级工厂仅仅负责抽象具体功能接口。此时, 一旦增减了生产的产品,我们的工厂只需要提供对应的子厂即可。这种设计就是工厂方法模式。
由此可见,如果生产任务不复杂的工厂,简单工厂就ok了。但是生产任务复杂的工厂,就需要我们的工厂变得健壮,就需要由简单工厂升级为工厂方法模式。
请看演示。 演示内容有:上级工厂,子工厂(具体产品工厂),产品接口,产品,生产演示。
2 UML
先通过UML了解,上级工厂,子工厂,产品之间的关系:
图上可以看到,每种产品都有对应的工厂来生产。而所有的子工厂都实现了工厂接口,完成工厂规范。
绿色的表示,当新增了产品时,同时增加对应工厂即可。解耦,满足开放-封闭原则。
3 代码演示
3.1 工厂接口及工厂子类
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
/**
* 工厂接口
*/
interface ILogFactory
{
/**
* 创建日志对象的工厂方法
* @param string $type 类型
* @return object
*/
publicstatic functionlog();
}
/**
* MySQLLog子工厂
*/
class MySQLLogFactoryimplements ILogFactory
{
publicstatic functionlog()
{
returnnew MySQLLog();
}
}
/**
* FileLog子工厂
*/
class FileLogFactoryimplements ILogFactory
{
publicstatic functionlog()
{
returnnew FileLog();
}
}
代码演示了2个子工厂和一个工厂接口(实操中也可以由抽象类实现)。工厂接口仅仅负责规范工厂应该具有的操作,而工厂子类完成具体产品的生产。
3.2 日志接口及日志类
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
/**
* 日志操作接口
*/
interface ILog
{
// 写
publicfunction write();
// 读
publicfunction read();
}
/**
* MySQL日志操作类
*/
class MySQLLogimplements ILog
{
publicfunction write(){
// 实现略...
}
publicfunction read(){
// 实现略...
}
}
/**
* File日志操作类
*/
class FileLogimplements ILog
{
publicfunction write(){
// 实现略...
}
publicfunction read(){
// 实现略...
}
}
3.3 生产演示
// 测试使用
// 不同的日志类型
$type= ['MySQL','File'][mt_rand(0,1)];
// 利用工厂获取日志操作对象
$factory= $type. 'LogFactory';
$log= $factory::log();
var_dump($log);
3.4 增加新产品及工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* MongoLog子工厂
*/
class MongoLogFactoryimplements ILogFactory
{
publicstatic functionlog()
{
returnnew MongoLog();
}
}
/**
* MongoDB日志操作类
*/
classMongoDBLog implementsILog
{
publicfunction write(){
// 实现略...
}
publicfunction read(){
// 实现略...
}
}
代码演示了,一旦需要增加额外的产品,我们同时增加对应的工厂来生产。由于工厂都实现的接口,因此新的产品的生产也满足原有规范,可以直接融入到之前的业务逻辑中。
4 分析
和简单工厂一样,都是解决动态生产对象的问题。只是工厂方法模式,使得工厂的规模变大了,由原来的一个车间升级为由多个子车间构成的大规模工厂了。而且允许扩展子工厂。因此,在生产相对复杂,繁多的产品时,工厂方法模式要更加适合些。而且扩展之后,不需要修改之前的代码,满足开放-封闭和原则。
最后吐槽一句,工厂方法其实就是将简单工厂的case语句变由一个个的类方法来实现了。
该模式被GOF(Gang Of Four,四人组)写到了《设计模式》一书中。被发扬光大了。
5 结语
一家之言,欢迎讨论,拍砖!
如有帮助, 请帮忙转发!
1 概述
构建者模式(Builder Pattern), 是一种获取对象的设计模式.
其设计思路是将对象本身与构建对象操作分开处理. 通过对象的构建操作来获取需要的对象. 实操中使用很广泛, 例如, SELECT语句对象的构建, SELECT查询条件where的构建, url对象的构建等.
就以SELECT语句的构建为例, SELECT语句具有结构复杂, 拼凑多样性的问题. 思考, SELECT语句由多个子句, From, where, …等构成. 同时, 在使用时, 还允许某些子句存在, 某些子句不存在. 可见, 构建SELECT语句的过程, 就是一个相对复杂操作. 此时就可以将语句对象和语句对象的构建分开处理, 这种设计,就称之为: 构建者模式. 而实际中, 多数的SELECT语句的获取, 都会采用该设计模式实现. 非常实用.
需要采用构建者模式来构建的对象. 通常是对象的内部结构会有不同的复杂变化. 因此, 将处理这些复杂部分代码, 独立出来, 不加入到对象的内部结构中. 这样, 对象本身就和对象的创建过程解耦了. 对象本身, 不再去关注构建的过程, 仅仅需要处理对象本身的功能即可.
以上的设计, 就满足面向对象的设计模式原则中: 合成复用的原则.
2 代码演示
就以构建SELECT语句为例.
我们的最终目标是得到对象Query, 一个包含了SELECT语句的对象, 可以执行该SELECT语句, 返回匹配的记录 的查询对象.
而SELECT的拼凑, 复杂, 我们将拼凑的代码抽取, 有QueryBuilder实现.
2.1 Query类
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
/**
* Query类
*/
class Query
{
// 存储SELECT语句的属性
private$statement;
publicfunction __construct($statement)
{
// 初始化SELECT语句属性
$this->statement= $statement;
}
// 执行查询, 获取全部记录
publicfunction all()
{
// 代码略...
}
// 执行查询, 获取一条记录
publicfunction one()
{
// 代码略...
}
// 执行查询获取标量数据(第一条记录的第一个字段)
publicfunction scalar()
{
// 代码略...
}
}
Query类对象, 有一个SQL语句属性, 保存拼凑好的SELECT语句. 在实例化时, 获取.
Query类对象, 在实际工作中, 完成执行SQL依据, 获取结果的工作, all(), one()方法就是用来执行和获取结果的. 功能性代码省略.
1.2.2 QueryBuild类, Query对象构建类
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
/**
* QueryBuilder类
*/
class QueryBuilder
{
// 字段列表
private$field= '*';
// 表名
private$table;
/**
* 返回本类实例
* @return [type] [description]
*/
publicstatic functioninstance()
{
returnnew static();
}
// 简化的构建 字段列表和from子句部分
/**
* 构建字段列表部分
* @param string $field
*/
publicfunction field($field= '*')
{
$this->field= $field;
// 返回当前对象, 链式调用
return$this;
}
/**
* 构建from子句
*/
publicfunction table($table)
{
$this->table= "`$table`";
return$this;
}
// 其他子句(join, where, group, haveing, order, limit)的构建略..
/**
* 构建Query对象
* @return [type] [description]
*/
publicfunction build()
{
// 初始化SELECT子句
$statement= 'SELECT ';
// 连接上字段列表部分
$statement.=$this->field;
// 连接上表名部分
if(!is_null($this->table))$statement.=' FROM ' .$this->table;
// 返回构建好的Query对象
returnnew Query($statement);
}
}
QueryBuild的主要作用,就是拼凑SELECT语句的各个部分. 有具体的每个方法负责拼凑语句的各个部分. 示例中演示了简单的查询字段和from表名的拼凑. 用户需要调用QueryBuild对象的field(), table()方法,完成设置字段和表名.
同时提供build()方法, 用来基于设置好的子句部分, 拼凑完整的SELECT语句, 并实例化Query对象.
2.3 构建演示
// 构建过程演示
$query= QueryBuilder::instance()// QueryBuilder对象
->field('user, host')// 字段
->table('user')// 表名
->build();// 构建
var_dump($query);// Object(Query)
$query->all();// 查询全部
$query->one();// 查询第一条
后续就是Query对象的具体操作.
3 分析
通过以上的代码展示, 大家注意Query对象的获取方式.
是通过QueryBuilder对象的build方法获取的.
这么设计的原因, 就是SELECT语句的结构较为复杂, 将拼凑和最终的使用分离实现.
这就是采用构建者模式获取对象的方式.
额外的, 具体功能的实现, 不是本文的内容, 故大部分功能的代码略. 仅仅展示了构建者模式需要的代码.
请关注 微信公众号: 小韩说理
概述
原型模式(prototype pattern), 是获取对象的一种设计模式. 设计思想是通过复制(克隆)已有对象, 得到新的对象. 而这个已有对象, 就是复制(克隆)而形成才新对象的原型. 故称之原型模式.
核心语法, 就是clone.
使用克隆获取新对象的方式的优势通常是, 在实例化对象相对复杂时, 通常指的是需要做大量的构造初始化计算时, 可以采用克隆(复制)的方式得到新对象, 提升获取对象的效率.
代码演示
定义可以添加原型和利用原型克隆对象的方法
classFactory
{
privatestatic $prototypeList= [];
publicstatic functionaddPrototype($obj)
{
static::$prototypeList[get_class($obj)]= $obj;
}
publicstatic functiongetObject($class)
{
returnclone static::$prototypeList[$class];
}
}
将需要克隆的对象原型加入工厂
classHero
{
private$objectType;
publicfunction __clone()
{
$this->objectType= clone$this->objectType;
}
}
classBeauty
{
}
Factory::addPrototype(newHero());
Factory::addPrototype(newBeauty());
需要对象时, 通常工厂克隆生成
$h1= Factory::getObject('Hero');
$h2= Factory::getObject('Hero');
$h3= Factory::getObject('Hero');
$b1= Factory::getObject('Beauty');
代码分析
工厂类的静态属性存储加入工厂的原型对象, 等待被克隆.
提供生成对象的接口getObject(), 通过传递参数类名, 获取对应的对象.
对象是通过clone而形成的.
采用该方法, 在new操作复杂(构造方法复杂)时, clone获取对象的效率会高些, 做了一个小测试:
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
classHero
{
private$name;
private$gender;
private$country;
publicfunction __construct()
{
$this->name= 'someName';
$this->gender= 'someGender';
$this->country= 'someCountry';
}
}
$s= microtime(true);
for($i=0;$i<1000;++$i){
$new[]= newHero;
}
echo microtime(true)- $s,'<br>';
//0.0015060901641846
$hero= newHero();
$s= microtime(true);
for($i=0;$i<1000;++$i){
$clone[]= clone$hero;
}
echomicrotime(true)- $s,'<br>';
// 0.00049400329589844
时间对比, clone的效率会高些.
深浅克隆
在执行clone时, PHP默认采用浅克隆, 就是如果是对象类型的属性, 则仅仅是引用复制, 而不是复制对象本身.
如果需要将对象本身也同时克隆. 则需要定义对象的__clone()方法, 对对象类型的属性做进一步的clone操作.
classHero
{
private$objectType;
publicfunction __clone()
{
$this->objectType= clone$this->objectType;
}
}
摘要
上两篇中, 说了IoC服务容器 和 DI依赖注入.
我们知道laravel和核心就是一个IoC容器, 被称之为服务容器. 那么作为IoC容器, 就必须要有绑定对象生成器的工作. 在laravel中是服务提供者来项服务容器中绑定对象生成器的.
下面就继续说说, laravel的核心架构中的 服务提供者.provider
服务提供者, 就是负责在laravel的IoC容器中绑定对象生成器的代码. 在laravel中称之为服务提供者.
注册服务提供者
laravel要知道, 哪些有服务提供者来负责绑定IoC容器, 采用的办法是, 在配置文件中, 将所有的服务提供者配置好, 所有的服务提供者, 都在 config/app.php中配置, 示例代码如下 : config/app.php
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
config/web.app
'providers'=>[
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
//
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
上面代码的每一行, 都注册一个一个服务提供者.
服务提供者绑定对象生成器到IoC服务容器
那么服务器提供者, 是如何绑定IoC容器中的对生成器呢?
每个服务器提供者对象, 都要实现一个register()方法, 该方法会被laravel自动调用. register()方法, 就是用来向IoC服务容器绑定对象生成器的.
以lluminate\Cache\CacheServiceProvider这个提供者为例, 查看其register方法, 位于:vendor/laravel/framework/src/Illuminate/Cache/CacheServerProvider.php文件中, 代码如下
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
vendor/laravel/framework/src/Illuminate/Cache/CacheServerProvider.php
classCacheServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
publicfunction register()
{
$this->app->singleton('cache',function ($app){
returnnew CacheManager($app);
});
$this->app->singleton('cache.store',function ($app){
return$app['cache']->driver();
});
$this->app->singleton('memcached.connector',function (){
returnnew MemcachedConnector;
});
$this->registerCommands();
}
}
在register()方法中, 通过$this->app访问到IoC服务容器. 然后调用该容器的singleton()方法, 绑定单例对象生成器.
可见, 当register()被调用时, 该服务提供者就会向IoC服务容器绑定内容. 绑定完毕后, laravel中就可以使用该类的对象了. make()也好, 自动注入依赖也罢都可以了.
结语
这样, 服务提供者的核心任务, 绑定对象的生成代码到IoC服务容器这个任务就完成了. 这就是laravel中的, 服务提供者provider的概念.
一家之言, 欢迎讨论拍砖.
最新的内容, 可以通过关注我的微信公众号: 小韩说理 获取
概述
在上一篇中, 讲解了依赖注入(DI)( http://www.hellokang.net/2016/11/08/ioc-di/).
先留意下面的代码:
// 用宝剑的英雄
new Hero(newSword('倚天'));
// 用枪的英雄
new Hero(newGun('沙漠之鹰'));
可见, 在注入时, 需要实例化好所依赖的对象, 再传递到Hero类中. 虽然通过依赖注入解决了解耦的问题, 但在实际使用时, 比较麻烦, 因为每次都需要手动实例化依赖, 再传递. 这对于复杂大量的依赖关系, 手动解决明显力不从心. 因此, 实际项目中需要一个自动化的依赖注入管理机制, 这就是IoC容器.
IoC容器, 一个封装了依赖注入DI的框架, 实现了动态创建注入依赖对象, 管理依赖关系, 管理对象生命周期等功能.
核心实现, 一般分为绑定(注册)对象生成器 和 创建对象注入依赖, 两个核心步骤 我们逐一去看.
绑定(注册)对象生成器
绑定: 指的是将类与生成类对象的代码记录, 对应绑定起来. 这样, 在需要某个类的对象时, 直接执行类对应的生成代码, 就可以得到该类的对象了.
上代码: 同样需要讲解依赖注入是的示例类: Hero, Sword, Gun
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
/**
* 武器接口
*/
interface I_Weapon
{
publicfunction __construct($title);
/**
* 进攻
*/
publicfunction attack();
}
/**
* 宝剑类
*/
class Swordimplements I_Weapon
{
private$title;
publicfunction __construct($title)
{
$this->title= $title;
}
publicfunction attack()
{
return'唰唰唰';
}
}
/**
* 枪类
*/
class Gunimplements I_Weapon
{
private$title;
publicfunction __construct($title)
{
$this->title= $title;
}
publicfunction attack()
{
return'砰砰砰';
}
}
/**
* 英雄类
*/
class Hero
{
private$weapon;
publicfunction __construct(I_Weapon$weapon)
{
// Hero依赖的武器, 来自于构造时的参数输入,
// 这就将依赖的对象通过注入的方式, 注入到Hero中
$this->weapon= $weapon;
}
}
再看容器类:本节重点
IoC容器示范类
class IoC_Container
{
privatestatic $generator_list= [];
// 绑定类的生成器
publicstatic functionbind($class_name,$generator)
{
if(is_callable($generator)){
self::$generator_list[$class_name]= $generator;
}else {
thrownew Exception('对象生成器不是可调结构');
}
}
}
注意其中的bind方法, 就是本小节所说的绑定(注册)对象生成器的实现.
bind方法需要两个参数:
- 第一个就是类的标志, 通常就是带有命名空间的类名称即可. 例如 path\to\Sword, path\to\Hero.
- 第二个参数就是该类对应的生成器. 所谓生成器, 其实就是一段代码, 可以生成类对象的代码而已, 说白了就是执行new的代码. 可以是匿名函数, 函数, 类方法等可执行结构都是可以的.
这样其实就是, 要用户提供类 和 该类对象的生成代码, 将其对应, 需要该类对象时执行. 但是注册时, 并不调用生成类的代码, 而仅仅是, 先存储起来. 本示例实现中,就存储到了::$generator_list数组中.
绑定示例代码:
// 利用容器绑定对象生成器
IoC_Container::bind('Sword',function($title=''){
returnnew Sword($title);
});
IoC_Container::bind('Gun',function($title=''){
returnnew Gun($title);
});
IoC_Container::bind('Hero',function($module,$params=[]){
returnnew Hero(IoC_Container::make($module,$params));
});
调用bind()方法, 提供类名和实例化类对象的匿名函数. 我们的容器就会将类与生成器记录下来, 等着需要时生成.
再看如何创建对象
完善IoC_Container类的代码, 增加创建对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
classIoC_Container
{
privatestatic $generator_list= [];
// 绑定类的生成器
publicstatic functionbind($class_name,$generator)
{
if(is_callable($generator)){
self::$generator_list[$class_name]= $generator;
}else {
thrownew Exception('对象生成器不是可调结构');
}
}
// 生成类对象
publicstatic functionmake($class_name,$params= [])
{
if(!isset(self::$generator_list[$class_name])){
thrownew Exception('类没有被绑定注册');
}
returncall_user_func_array(self::$generator_list[$class_name],$params);
}
}
注意其中的make方法, 就是用来生成对象的方法. 该方法要获取需要的类, 然后调用该类之前注册绑定的生成器函数, 来获取对象.
通过make生成类对象:
IoC容器生成对象
$hero1= IoC_Container::make('Hero',['Sword',['倚天']]);
var_dump($hero1);
$hero2= IoC_Container::make('Hero',['Gun',['沙漠之鹰']]);
var_dump($hero2);
这样, 就可以生成两个Hero对象, 一个Sword类对象被注入, 另一个Gun类对象被注入.结果:
object(Hero)#4 (1) {
["weapon":"Hero":private]=>
object(Sword)#5 (1) {
["title":"Sword":private]=>
string(6)"倚天"
}
}
object(Hero)#6 (1) {
["weapon":"Hero":private]=>
object(Gun)#7 (1) {
["title":"Gun":private]=>
string(12)"沙漠之鹰"
}
}
这就是IoC容器的两个基本步骤, 1绑定, 2创建.
小结
可见, IoC其实就是一个功能高级点的工厂类而已, 用于生产类的对象. 目前为止, 稍微高级的地方,就是预先将生产类对象的代码, 先绑定了而已. 需要的时候, 在执行生成对象.
而真正解决了依赖问题的, 还是上篇说的DI依赖注入. 而不是这个容器. 容器仅仅是让生成代码更加规范, 方便了而已.
IoC容器, 到此, 基本功能有了, 但是实操时, 往往还需要再升级. 主要升级的方面, 就是自动判断所需要的依赖, 然后生产创建对象时 进行依赖注入. 下面接着聊.
自动依赖注入
上面的例子的make()实现中, 需要手动将Hero需要的参数传递到Make中. 实际的IoC容器, 比这要高级些.
思考我们定义的Hero类的构造方法, 我们使用了类型约束的语法, 限制参数类型:
public function__construct(I_Weapon$weapon)
{
// Hero依赖的武器, 来自于构造时的参数输入,
// 这就将依赖的对象通过注入的方式, 注入到Hero中
$this->weapon= $weapon;
}
那就意味着, 我们是知道Hero类所依赖的对象类型的, 因此我们可以自动根据这个类型, 从已注册的类中, 将Hero需要的Weapon注入(传递)到Hero的构造方法中.
语法上, 我们需要 反射Reflectoin机制, 来了解我们构造方法的参数类型, 去做逻辑判断, 示例代码如下:
为了方便演示自动依赖注入, 将上面的英雄类的依赖改为对 Sword类的依赖, 这样示例代码会直观简单些, 方便学习, 新的英雄Hero类如下:
/**
* 英雄类
*/
class Hero
{
private$weapon;
publicfunction __construct(Sword$weapon)
{
// Hero依赖的武器, 来自于构造时的参数输入,
// 这就将依赖的对象通过注入的方式, 注入到Hero中
$this->weapon= $weapon;
}
}
在IoC容器类中, 增加自动注入的方法:
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
// 增加自动注入的容器
class IoC_Container
{
privatestatic $generator_list= [];
// 绑定类的生成器
publicstatic functionbind($class_name,$generator)
{
// 代码省略
}
// 生成类对象
publicstatic functionmake($class_name,$params= [])
{
// 代码省略
}
// 自动注入方法
publicstatic functionautoInject($class_name)
{
$rf= newReflectionClass($class_name);
// 获取构造函数
$constructor= $rf->getConstructor();
// 获取构造函数参数
$paramters= $constructor->getParameters();
foreach($paramtersas $paramter){
// 得到参数类型
$type= $paramter->getClass()->getName();
// 生成参数对象
$params[]= self::make($type);
}
return$rf->newInstanceArgs($params);
}
}
上面的代码, 先利用反射类对象, 获取类的构造方法; 再利用方法反射对象,获取构造方法的参数列表; 再利用参数的反射对象, 获取参数的类型, 这样,就可以知道参数要求是Sword类. 那么就可以获Sword类对象, 传递给hero类, 将依赖的Sword类注入到Hero类构造方法中去了.
该方法仅仅是一个语法的演示 , 演示如何使用反射完成自动类型判断. 实操中方法的完整性要增加很多操作, 不是上面几行代码就可以完成的.
实际操作中, 会将通用的规范的类, 使用这种IoC容器, 自动依赖注入. 而一些相对特殊规则的类, 可以使用上面的make方法, 解决问题.
那到此, 关于依赖注入的实操中最常见的实现IoC容器, 就说到这里了.
结语
理解了什么是IoC容器, 本文的目的就达到了. 实际使用中(例如laravel)IoC容器的方法会有很多, 例如绑定构造器, 绑定对象实例, 绑定单例, 绑定接口实现等. 具体的使用就要到具体的框架或者产品中看了.
有了IoC容器, 框架的底层, 仅仅提供容器相关的结构, 功能上的模块, 都是容器中注册和生成的.
代码中提到的反射机制语法, 如果大家感兴趣, 可以关注我的PHP基础语法视频系列, 有相关的介绍.