聊聊php的设计模式

心法 (必须牢记)

万般招式都源于内功心法口诀,理解了心法,才能以不变应万变。万变不离其宗。

1, 针对接口编程
2, 优先使用对象组合,而不是类继承
3, 找到并封装变化的点
4,单一职责
5,开放-关闭原则

为什么要学设计模式

设计模式源于建筑学
很久以前,一名建筑师自问:‘建筑质量可以衡量吗?可以客观评价吗’?
他研究了很久发现:任何建筑物,优先的结构之前总有一些相同之处。

通过观察解决相同问题的不同设计,缩小了他的关注焦点,他可以洞悉优秀设计之间的相似之处。他把那些相似之处称为模式

所以他给模式下了一个定义: 在某一情景下的问题解决方案
每种模式,你都可以无数次使用,用来解决我们重复遇到的这些问题。

软件开发中有很多不断重复出现的问题,就可以用相同的方式去解决。

其中有4个人,他们写了一本书,书中共收录了23种设计模式,这对设计模式在软件开发中的应用产生了深远影响,大家都称他们为四人团

学好设计模式,我们有如下好处:
1,更好的理解面向对象
2,复用优秀的解决方案。因为通过复用别人优秀的设计,自己可以避免走很多弯路,针对问题能快速的提出优秀的解决方案
3,学习设计模式,可以给你一个更高层次的视角去分析、解决问题,避免过早的陷入处理细节的困局。
因为面对问题,很多人会过早的去考虑实现程序的细节,但这往往会模糊了真正的问题。
而设计模式,就能帮你走出这样的思维困局,面对问题,你会站在一个更高的层次去考虑如何设计,
更能清晰的去面对问题,也就能有更好的解决方案。

封装,继承,多态

面向对象有三大特征,分别是: 封装、继承、多态

封装

因为对象都对自己负责,所以,对象的很多东西都不需要或者不可以暴露给外部。比如很常见的private私有属性。

封装解决了数据的安全性,内在也体现了‘每个对象都对自己负责’的原则

继承

继承,最常用到的,主要目的是解决了代码的复用。

说到代码的复用,有两种方式: 组合和继承

那什么时候该用组合?什么时候该用继承呢?

多态

多态:同一个操作,作用于不同的对象,会产生不同的结果。
说白了,就是发出一个相同的指令后,不同的对象会对这个指令有不同的反应,所以称为多态。

比如:

老师说: 同学们请描述下自己对opp的理解。
每个学生对于这个相同的指令,有不同的反应

多态最大的好处就是 灵活+解藕

耦合度的意思是模块于模块之间,代码与代码之间的关联度。
紧耦合也就是他们之间的关联度大,这样的代码是很难维护的,很容易出bug。出了bug 滚雪球一样增长 不可收拾。

我们经常说:‘要面向接口编程,而不是面向实现编程’。
多态性 就是要求我们面向接口编程。
不同的对象,相同的接口,但因为多态,有了不同的实现。
这样面向对象接口编程,就降低了耦合度,很灵活。

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
<?php 
abstract class Animal {
abstract public function say();
public function eat(){
echo 'eating food...';
}
}

class Dog extends Animal {
public function say(){
echo 'Dog say wangwang\n';
}
}

class Cat extends Animal {
public function say(){
echo 'Cat say miaomiao\n';
}
}

//test
function work{Animal $obj}{
if($obj instanceof Animal){
$obj->say();
}else{
echo 'sorry,it is wrong!\n';
}
}

work(new Dog); //Cat say miaomiao
work(new Cat); // Dog say wangwang
?>

Facade模式

Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。

现在的web开发基本上都是mvc架构模式。
controller用来逻辑处理
model用来读取数据
view用来渲染html

如果逻辑很复杂的话,controller的方法里要写很长很长的逻辑控制代码,多达几百上千行,很不好管理。

比如:
用户提交订单时,会与其他模块进行交互,需要的数据也比较复杂。只见此controller,从model层各种拿数据,然后各种逻辑处理,代码写了上千行。

那如何给controller瘦身呢?

其实控制器不应该处理过多的业务逻辑。
1,控制器,就像遥控器一样。你见过遥控器关心电视怎么播放视频吗?没有。遥控器只是发送播放视频的信号,具体的播放视频的细节,遥控器不会关心。
2,控制器,就像将军一样。你见过将军亲自为每位士兵配备武器吗?细节部分,将军不必过问,将军的职责是领兵打仗,这叫各司其责,否则就乱了。

有一种模式叫外观模式,
外观模式,提供了统一的接口,用来访问子系统中的一群接口。
外观模式定义了一个高层接口,使得子系统更加易用。

也就是说,干一件很复杂的事的时候,你想团队中每个人都花一年半载去学习如何做这件事吗?
利用外观模式,我只需要指定一个人去学会这些复杂的步骤,然后我再告诉这个接口人去干就行了。

在thinkphp里,有一层叫Logic层,关于业务逻辑处理的部分,你可以写在login层里,这样,controller层就变得很轻量了,好维护了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//文件名: SendEmailFacdeLogin.class.php(发送邮件Logic)
//获取用户邮箱
private function get_user_email($uid){
return new User()->get_user_email($uid);
}

private function get_email_content($uid){
return new Email()->get_email_content($uid);
}

public function send_email($uid){
$email = $this->get_user_email();
$content = $this->get_email_content($uid);
return new Email()->send_email($email,$content);
}

1
2
3
//文件名:SubmitController.class.php(用户提交模块controller)
//.....接上。。。2000行代码。。。
D('SendEmail','Logic')->send_email($uid);

这样,加了logic层,业务逻辑都放在logic里面去处理,controller是不是瘦了很多呢?
logic层为controller提供了一个高层的接口用来发送邮件,也就是Facada模式的应用。

这样 统一成简单的接口后,有很多好处
1,降低了系统的耦合度。提交订单的Controller,再也不用与UserController,EmailController等耦合了。现在只需要关心SendEmailFacadeLogic就可以了。

2,用户使用了facade模式后,有了统一的入口,就很容易监控客户对系统的使用了。就如thinkphp的单一入口原则一样。

Adapter模式

适配器的存在,就是为了将已存在的东西(接口)转换成适合我们需要、能被我们所利用的东西。在现实生活中,适配器更多的是作为一个中间层来实现这种转换作用。比如电源适配器,它是用于电流变换(整流)的设备。

适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

Facade模式,是对接口进行了包装。
Adapter模式,是对接口进行了包装适配。

Facade模式的目的,是为了提供一个简单易用的接口给用户。
而Adapter的目的,是为了适配某个类,从而保持对象的多态行为。

最大的不同,就是他们两个目的不同。
Adapter模式最常见的场景就是用来保持对象的多态行为。

比如:

把MySQL和mysqli统一成一个接口,用户可以调用同样的方法使用MySQL和mysqli操作数据库。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php 
//MySQL待操作适配类
class MySQLAdaptee implements Target {
protected $conn; //用于存放数据库连接句柄
//实现连接方法
public function connect($host, $user, $passwd, $dbname) {
$conn = mysql_connect($host, $user, $passwd);
mysql_select_db($dbname, $conn);
$this->conn = $conn;
}
//查询方法
public function query($sql) {
$res = mysql_query($sql, $this->conn);
return $res;
}
//关闭方法
public function close() {
mysql_close($this->conn);
}
}

//MySQLi操作待适配类
class MySQLiAdaptee {
protected $conn;
public function connect($host, $user, $passwd, $dbname) {
$conn = mysqli_connect($host, $user, $passwd, $dbname);
$this->conn = $conn;
}
public function query($sql) {
return mysqli_query($this->conn, $sql);
}
public function close() {
mysqli_close($this->conn);
}
}

//用户所期待的接口
Interface Target{
public function connect($host, $user, $passwd, $dbname);
public function query($sql);
public function close();
}

//用户期待适配类
Class DataBase implements Target {
protected $db ; //存放MySQLiAdapter对象或MySQLAdapter对象
public function __construct($type){
$type = $type."Adapter" ;
$this->db = new $type ;
}
public function connect($host, $user, $passwd, $dbname){
$this->db->connect($host, $user, $passwd, $dbname);
}

public function query($sql){
return $this->db->query($sql);
}

public function close(){
$this->db->close();
}
}

//用户调用同一个接口,使用MySQL和mysqli这两套不同示例。
$db1 = new DataBase('MySQL'); $db1->connect('127.0.0.1','root','1234','myDB');die;
$db1->query('select * from test');
$db1->close();

$db2 = new DataBase('MySQLi'); $db2->connect('127.0.0.1','root','1234','myDB');
$db2->query('select * from test');
$db2->close();

上面的代码只是一个示例,如果你运行以上的代码报了mysql函数不存在或是被废弃的错误。这是正常的,因为MySQL这套函数在PHP5.5以上的版本已经被废弃了。感兴趣的还可以去了解一下PDO的实现。
通过上面的代码,我们可以看到,使用适配器可以把不同的操作接口封装起来,对外显示成用户所期望的接口。

Bridge模式

Bridge模式,也就是桥梁模式。
四人团的说法是:‘将抽象部分与它的实现部分分离,使他们都可以独立的变化。’

比如:
要发送短信或者微信,push到redis或者mq队列里去。

这里的抽象部分: 就是message的抽象
这里的实现部分:就是sendtype的实现
在抽象部分与实现部分之间搭个桥,使抽象部分可以引用实现部分的对象,就是桥梁模式。
这样使用对象组合的方式,特别的灵活。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php 
//消息发送类
abstract class Message(){
//定义发送方式对象与消息数据
public $send_type_obj;
public $data;
//构造函数
public function __construct($send_type_obj,$data){
$this->send_type_obj = $send_type_obj;
$this->data = $data;
}
//抽象类:不同的消息来重写此方法,以得到不同的消息数据
abstract public function combine_data();
//桥接到外部对象(引用外部对象,push到相应的队列)
public function push_to_queue($data){
if($this->send_type_obj instanceof SendType){
$this->send_type_obj->push_to_queue($data);
}
}

//完成发送
public function send(){
$combined_data = $this->combine_data();
$this->push_to_queue($combined_data);
}
}

//短信消息类
class SmsMesssage extends Message(){
//发送短信消息数据
public function combine_data(){
return 'sms combined data:'.$this->data;
}

}

//微信消息类
class WechatMessage extends Message(){
//发送微信消息数据
public function combine_data(){
return 'wechat combined data:'.$this->data;
}
}

//发送方式抽象类
abstract class SendType{
abstract public function push_to_queue($data);
}

//Redis发送方式类
class RedisSendType extends SendType{
//将消息push到redis队列里,完成发送。
public function push_to_queue($data){
echo $data."has send by redis queue\n";
}
}

//Mq发送方式类
class MqSendType extends SendType{
//将消息push到mq队列里,完成发送
public function push_to_queue($data){
echo $data." has send by mq queue\n";
}
}

//test case
//实例化不同的发送方式类
$redis_send_obj = new RedisSendType();
$mq_send_obj = new MqSendType();
//通过redis发送短信
$sms_redis_obj = new SmsMessage($redis_send_obj,'123');
$sms_redis_obj->send();

//通过redis发送微信
$wechat_redis_obj = new WechatMessage($redis_send_obj,'456');
$wechat_redis_obj->send();

//通过mq发送短信
$sms_mq_obj = new SmsMessage($mq_send_obj,'123');
$sms_redis_obj->send();

//通过mq发送微信
$wechat_mq_obj = new WechatMessage($mq_send_obj,'456');
$wechat_mq_obj->send();
?>

工厂方法模式

简单工厂模式,用的人挺多的,但不属于23种GOF设计模式之一。

差劲的代码

调用端负责了太多的事情,违背了单一职责原则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
\<?php
//file Client.php
class Client{
public function main(){
switch ($mes_type){
case 'Sms':
$obj = new SmsMessage();
break;
case 'Emial':
$obj = new EmailMessage();
break;
default:
throw new Exception('No Message Type Found');
}
$obj->send();
}
}
?>

优秀的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php 
//file MessageFactory.php
class MessageFactory{
public static function get_instance($mes_type){
$mes_type = 'Sms';
switch ($mes_type){
case 'Sms':
$obj = new SmsMessage();
break;
case 'Emial':
$obj = new EmailMessage();
break;
default:
throw new Exception('No Message Type Found');
}
}
}
?>
1
2
3
4
5
6
7
8
9
<?php
//file Client.php
class Client{
public function main(){
$obj = MessageFactory:get_instance($mes_type);
$obj->send();
}
}
?>

上面的是简单工厂模式,有个缺点。
比如我要新增AppPush消息类型,就要修改上面的MessageFactory工厂类。
这样就违背了开放-封闭原则。
我们应该对扩展开放,而对修改关闭。
因为修改,可能会带来意想不到的bug。

既然简单工厂解决了单一职责的问题,那么什么模式既能解决单一职责的问题,又不违背开闭原则呢?

工厂方法模式,可以。

主要利用反射来决定使用哪个工厂。

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
//抽象类 MessageFactory.php
<?php
abstract class MessageFactory{
abstract public function get_instance();
}
?>

//EmailFactory.php
<?php
class EmailFactory extends MessageFactory{
public function get_instance{
return new EmailMessage();
}
}
?>

//SmsFactory.php
<?php
class SmsFactory extends MessageFactory{
public function get_instance{
return new SmsMessage();
}
}
?>

//调用端 Client.php
<?php
class Client{
public function main($mes_type){
//利用反射,消除调用端的逻辑判断
$reflection = new ReflectionClass($mes_type.'Factory');
$factory = $reflection->newInstance();
$mes_obj = $factory->get_instance();
$mes_obj->send();
}
}
?>

抽象工厂模式

工厂模式针对一种产品提供一个工厂类,
而抽象工厂模式是针对一组相关或相互依赖的产品提供一个工厂类。
抽象工厂模式就是工厂模式的升级版。

在这里,Client端负责向Factory发出请求,Factory返回相关对象,Client端再根据Factory返回的对象,制造相关的产品。

也就是Client端负责使用对象,Factory负责创建对象。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<php 
//灯泡产品接口
interface Light{
public function produce_light();
}
//奥迪A4灯泡产品
class AudiA4Light implements Light{
public function produce_light(){
echo 'AudiA4 Light produced\n';
}

}

//奥迪A6灯泡产品
class AudiA6Light implements Light{
public function produce_light(){
echo 'AudiA6 Light produced\n';
}
}

//轮子产品接口
interface Wheel{
public function produce_wheel();
}
//奥迪A4的轮子
class AudiA4Wheel implements Wheel{
public function produce_wheel(){
echo 'AudiA4 Wheel produced\n';
}
}
//奥迪A6的轮子
class AudiA6Wheel implements Wheel{
public function produce_wheel(){
echo 'AudiA6 Wheel produced\n';
}
}

//工厂接口
interface Factory{
public function CreateWheel();
public function CreateLight();
}

//奥迪A4工厂
class AudiA4Factory implements Factory{
public function CreateWheel(){
return new AudiA4Wheel();
}

public function CreateLight(){
return new AudiA4Light();
}
}

//奥迪A6工厂
class AudiA6Factory implements Factory{
public function CreateWheel(){
return new AudiA6Wheel();
}

public function CreateLight(){
return new AudiA6Light();
}
}

//客户端调用类
class Client{
//运行主函数
public static function main($type){
$reflection = new ReflectionClass($type.'Factory');
$factory = $reflection->newInstance();
self:run($factory);
}

//生成产品
public static function run(Factory $factory){
$wheel = $factory->CreateWheel();
$wheel->product_wheel();
$light = $factory->CreateLight();
$light->product_light();
}
}
Client::main('A6');
?>

单例模式

单例模式(Singleton Pattern):顾名思义,就是只有一个实例。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

1,主要用于数据库。
一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时, 如果使用单例模式,
则可以避免大量的new 操作消耗的资源,还可以减少数据库连接这样就不容易出现 too many connections情况。

2,如果系统中需要有一个类来全局控制某些配置信息, 那么使用单例模式可以很方便的实现. 这个可以参看zend Framework的FrontController部分。

3,在一次页面请求中, 便于进行调试, 因为所有的代码(例如数据库操作类db)都集中在一个类中, 我们可以在类中设置钩子, 输出日志,从而避免到处var_dump, echo

单例模式的实现

1,私有化一个属性用于存放唯一的一个实例

2,私有化构造方法,私有化克隆方法,用来创建并只允许创建一个实例

3,公有化静态方法,用于向系统提供这个实例

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Singleton{
//存放实例
private static $_instance = null;

//私有化构造方法、
private function __construct(){
echo "单例模式的实例被构造了";
}
//私有化克隆方法
private function __clone(){

}

//公有化获取实例方法
public static function getInstance(){
if (!(self::$_instance instanceof Singleton)){
self::$_instance = new Singleton();
}
return self::$_instance;
}
}

$singleton=Singleton::getInstance();

因为静态方法可以在全局范围内被访问,当我们需要一个单例模式的对象时,只需调用getInstance方法,获取先前实例化的对象,无需重新实例化。

坚持原创技术分享,您的支持将鼓励我继续创作!