1,概述
适配器模式, Adapter Pattern, 指的是将一个接口转换为另一个期望的接口. 目的是使得原本不匹配的接口变得兼容.
例如, 我现在需要插入网线上网, 但是我的笔记本没有网线插口, 只有usb插口. 此时就需要一个转换器, 将usb接口转换为网线插口. 这个转换器这就是一个适配器.
在上面的例子中, 有三个角色:
- 适配器, 就是接口转换器.
- 待适配实例, 就是我的笔记本电脑, 需要适配一个网线插口.
- 适配目标, 就是我的网线插口. 就是待适配实例需要适配的目标.
以此为例, 编码演示.
2,先看UML结构
图中所示, 适配器使用一个属性保存待适配的实例, 同时实现了适配目标中定义的方法.
就是我的适配器, 需要能够找到我的笔记本电脑, 同时需要满足插网线的操作.
3,再看代码演示
代码演示中, 我们会定义一个适配目标接口, 就是网线操作接口. 以及一个适配器类, 和一个笔记本电脑类. 用来演示, 适配器使得笔记本可以使用网线上网的案例.
3.1,目标接口, 网线操作接口
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 网线接口
*/
interface
INetInterface
{
/**
* RJ45(网线口)传输方法
* @return [type] [description]
*/
public
function
transferRJ45
(
)
;
}
|
一个定义了RJ45(网线口)标准的网线操作接口. 我们的目标就是要适配该接口.
3.2,笔记本类,我们的待适配目标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 笔记本类
*/
class
PC
{
/**
* 笔记本中仅仅存在USB数据传输
* @return [type] [description]
*/
public
function
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
NetAdapter
implements
INetInterface
{
// 适配器引用待适配示例对象笔记本
private
$
pc
;
public
function
__construct
(
PC
$
pc
)
{
$
this
-
&
gt
;
pc
=
$
pc
;
}
/**
* RJ45(网线口)传输方法
* @return [type] [description]
*/
public
function
transferRJ45
(
)
{
// 方法实现的主要内容就是
// 由待适配对象的相应方法, 完成RJ45操作方法
$
this
-
&
gt
;
pc
-
&
gt
;
transferUSB
(
)
;
}
}
|
适配类, 实现了目标接口, 因此在使用目标时, 直接使用该适配器即可.
语法上, 实现了接口, 重写了接口的方法, 由待适配示例完成适配目标方法即可.
3.4,使用适配器
|
// 测试演示
// 获取适配器, 将PC传递
$
adapter
=
new
NetAdapter
(
new
PC
(
)
)
;
// 此时要使用网线传输, 则直接由适配器完成即可
$
adapter
-
&
gt
;
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
|
class
Dao
{
// 存储当前类实例的静态属性
private
static
$
instance
;
/**
* 私有构造方法, 目的不能在类外部实例化
*/
private
function
__construct
(
)
{
// 代码略...
}
/**
* 私有化克隆方法, 目的不能在类外克隆对象
* @return [type] [description]
*/
private
function
__clone
(
)
{
// 代码略...
}
/**
* 获取单例对象
*/
public
static
function
instance
(
)
{
// 如果没有实例化
if
(
!
self
::
$
instance
instanceof
self
)
{
// 实例化类对象, 存储在静态属性$instance中
self
::
$
instance
=
new
self
(
)
;
}
return
self
::
$
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
|
class
Singleton
{
// 实例化好的对象列表
private
static
$
object_list
=
[
]
;
/**
* 单例对象生产
* @param string $class 类名
* @return object
*/
public
static
function
instance
(
$
class
)
{
// 判断某个类是否实例化了对象
if
(
!
isset
(
self
::
$
object_list
[
$
class
]
)
||
!
self
::
$
object_list
[
$
class
]
instanceof
$
class
)
{
// 没有, 实例化, 存储
self
::
$
object_list
[
$
class
]
=
new
$
class
(
)
;
}
return
self
::
$
object_list
[
$
class
]
;
}
}
|
可以视为一个工厂类, 专门用来生产单例对象. 生产完毕, 存储起来, 下次再用, 直接返回.
3.2,测试
1
2
3
4
5
6
7
8
9
10
11
12
|
class
Dao
{
// 代码略...
}
$
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
*/
public
static
function
log
(
)
;
}
/**
* MySQLLog子工厂
*/
class
MySQLLogFactory
implements
ILogFactory
{
public
static
function
log
(
)
{
return
new
MySQLLog
(
)
;
}
}
/**
* FileLog子工厂
*/
class
FileLogFactory
implements
ILogFactory
{
public
static
function
log
(
)
{
return
new
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
{
// 写
public
function
write
(
)
;
// 读
public
function
read
(
)
;
}
/**
* MySQL日志操作类
*/
class
MySQLLog
implements
ILog
{
public
function
write
(
)
{
// 实现略...
}
public
function
read
(
)
{
// 实现略...
}
}
/**
* File日志操作类
*/
class
FileLog
implements
ILog
{
public
function
write
(
)
{
// 实现略...
}
public
function
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
MongoLogFactory
implements
ILogFactory
{
public
static
function
log
(
)
{
return
new
MongoLog
(
)
;
}
}
/**
* MongoDB日志操作类
*/
class
MongoDBLog
implements
ILog
{
public
function
write
(
)
{
// 实现略...
}
public
function
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
;
public
function
__construct
(
$
statement
)
{
// 初始化SELECT语句属性
$
this
->
statement
=
$
statement
;
}
// 执行查询, 获取全部记录
public
function
all
(
)
{
// 代码略...
}
// 执行查询, 获取一条记录
public
function
one
(
)
{
// 代码略...
}
// 执行查询获取标量数据(第一条记录的第一个字段)
public
function
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]
*/
public
static
function
instance
(
)
{
return
new
static
(
)
;
}
// 简化的构建 字段列表和from子句部分
/**
* 构建字段列表部分
* @param string $field
*/
public
function
field
(
$
field
=
'*'
)
{
$
this
->
field
=
$
field
;
// 返回当前对象, 链式调用
return
$
this
;
}
/**
* 构建from子句
*/
public
function
table
(
$
table
)
{
$
this
->
table
=
"`$table`"
;
return
$
this
;
}
// 其他子句(join, where, group, haveing, order, limit)的构建略..
/**
* 构建Query对象
* @return [type] [description]
*/
public
function
build
(
)
{
// 初始化SELECT子句
$
statement
=
'SELECT '
;
// 连接上字段列表部分
$
statement
.
=
$
this
->
field
;
// 连接上表名部分
if
(
!
is_null
(
$
this
->
table
)
)
$
statement
.
=
' FROM '
.
$
this
->
table
;
// 返回构建好的Query对象
return
new
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.
使用克隆获取新对象的方式的优势通常是, 在实例化对象相对复杂时, 通常指的是需要做大量的构造初始化计算时, 可以采用克隆(复制)的方式得到新对象, 提升获取对象的效率.
代码演示
定义可以添加原型和利用原型克隆对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class
Factory
{
private
static
$
prototypeList
=
[
]
;
public
static
function
addPrototype
(
$
obj
)
{
static
::
$
prototypeList
[
get_class
(
$
obj
)
]
=
$
obj
;
}
public
static
function
getObject
(
$
class
)
{
return
clone
static
::
$
prototypeList
[
$
class
]
;
}
}
|
将需要克隆的对象原型加入工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
Hero
{
private
$
objectType
;
public
function
__clone
(
)
{
$
this
->
objectType
=
clone
$
this
->
objectType
;
}
}
class
Beauty
{
}
Factory
::
addPrototype
(
new
Hero
(
)
)
;
Factory
::
addPrototype
(
new
Beauty
(
)
)
;
|
需要对象时, 通常工厂克隆生成
|
$
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
|
class
Hero
{
private
$
name
;
private
$
gender
;
private
$
country
;
public
function
__construct
(
)
{
$
this
->
name
=
'someName'
;
$
this
->
gender
=
'someGender'
;
$
this
->
country
=
'someCountry'
;
}
}
$
s
=
microtime
(
true
)
;
for
(
$
i
=
0
;
$
i
<
1000
;
++
$
i
)
{
$
new
[
]
=
new
Hero
;
}
echo
microtime
(
true
)
-
$
s
,
'<br>'
;
//0.0015060901641846
$
hero
=
new
Hero
(
)
;
$
s
=
microtime
(
true
)
;
for
(
$
i
=
0
;
$
i
<
1000
;
++
$
i
)
{
$
clone
[
]
=
clone
$
hero
;
}
echo
microtime
(
true
)
-
$
s
,
'<br>'
;
// 0.00049400329589844
|
时间对比, clone的效率会高些.
深浅克隆
在执行clone时, PHP默认采用浅克隆, 就是如果是对象类型的属性, 则仅仅是引用复制, 而不是复制对象本身.
如果需要将对象本身也同时克隆. 则需要定义对象的__clone()方法, 对对象类型的属性做进一步的clone操作.
|
class
Hero
{
private
$
objectType
;
public
function
__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
class
CacheServiceProvider
extends
ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public
function
register
(
)
{
$
this
->
app
->
singleton
(
'cache'
,
function
(
$
app
)
{
return
new
CacheManager
(
$
app
)
;
}
)
;
$
this
->
app
->
singleton
(
'cache.store'
,
function
(
$
app
)
{
return
$
app
[
'cache'
]
->
driver
(
)
;
}
)
;
$
this
->
app
->
singleton
(
'memcached.connector'
,
function
(
)
{
return
new
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
(
new
Sword
(
'倚天'
)
)
;
// 用枪的英雄
new
Hero
(
new
Gun
(
'沙漠之鹰'
)
)
;
|
可见, 在注入时, 需要实例化好所依赖的对象, 再传递到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
{
public
function
__construct
(
$
title
)
;
/**
* 进攻
*/
public
function
attack
(
)
;
}
/**
* 宝剑类
*/
class
Sword
implements
I_Weapon
{
private
$
title
;
public
function
__construct
(
$
title
)
{
$
this
->
title
=
$
title
;
}
public
function
attack
(
)
{
return
'唰唰唰'
;
}
}
/**
* 枪类
*/
class
Gun
implements
I_Weapon
{
private
$
title
;
public
function
__construct
(
$
title
)
{
$
this
->
title
=
$
title
;
}
public
function
attack
(
)
{
return
'砰砰砰'
;
}
}
/**
* 英雄类
*/
class
Hero
{
private
$
weapon
;
public
function
__construct
(
I
_Weapon
$
weapon
)
{
// Hero依赖的武器, 来自于构造时的参数输入,
// 这就将依赖的对象通过注入的方式, 注入到Hero中
$
this
->
weapon
=
$
weapon
;
}
}
|
再看容器类:本节重点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
IoC容器示范类
class
IoC_Container
{
private
static
$
generator_list
=
[
]
;
// 绑定类的生成器
public
static
function
bind
(
$
class_name
,
$
generator
)
{
if
(
is_callable
(
$
generator
)
)
{
self
::
$
generator_list
[
$
class_name
]
=
$
generator
;
}
else
{
throw
new
Exception
(
'对象生成器不是可调结构'
)
;
}
}
}
|
注意其中的bind方法, 就是本小节所说的绑定(注册)对象生成器的实现.
bind方法需要两个参数:
- 第一个就是类的标志, 通常就是带有命名空间的类名称即可. 例如 path\to\Sword, path\to\Hero.
- 第二个参数就是该类对应的生成器. 所谓生成器, 其实就是一段代码, 可以生成类对象的代码而已, 说白了就是执行new的代码. 可以是匿名函数, 函数, 类方法等可执行结构都是可以的.
这样其实就是, 要用户提供类 和 该类对象的生成代码, 将其对应, 需要该类对象时执行. 但是注册时, 并不调用生成类的代码, 而仅仅是, 先存储起来. 本示例实现中,就存储到了::$generator_list数组中.
绑定示例代码:
|
// 利用容器绑定对象生成器
IoC_Container
::
bind
(
'Sword'
,
function
(
$
title
=
''
)
{
return
new
Sword
(
$
title
)
;
}
)
;
IoC_Container
::
bind
(
'Gun'
,
function
(
$
title
=
''
)
{
return
new
Gun
(
$
title
)
;
}
)
;
IoC_Container
::
bind
(
'Hero'
,
function
(
$
module
,
$
params
=
[
]
)
{
return
new
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
|
class
IoC_Container
{
private
static
$
generator_list
=
[
]
;
// 绑定类的生成器
public
static
function
bind
(
$
class_name
,
$
generator
)
{
if
(
is_callable
(
$
generator
)
)
{
self
::
$
generator_list
[
$
class_name
]
=
$
generator
;
}
else
{
throw
new
Exception
(
'对象生成器不是可调结构'
)
;
}
}
// 生成类对象
public
static
function
make
(
$
class_name
,
$
params
=
[
]
)
{
if
(
!
isset
(
self
::
$
generator_list
[
$
class_name
]
)
)
{
throw
new
Exception
(
'类没有被绑定注册'
)
;
}
return
call_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类对象被注入.结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* 英雄类
*/
class
Hero
{
private
$
weapon
;
public
function
__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
{
private
static
$
generator_list
=
[
]
;
// 绑定类的生成器
public
static
function
bind
(
$
class_name
,
$
generator
)
{
// 代码省略
}
// 生成类对象
public
static
function
make
(
$
class_name
,
$
params
=
[
]
)
{
// 代码省略
}
// 自动注入方法
public
static
function
autoInject
(
$
class_name
)
{
$
rf
=
new
ReflectionClass
(
$
class_name
)
;
// 获取构造函数
$
constructor
=
$
rf
->
getConstructor
(
)
;
// 获取构造函数参数
$
paramters
=
$
constructor
->
getParameters
(
)
;
foreach
(
$
paramters
as
$
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基础语法视频系列, 有相关的介绍.