PHP设计模式与PSR-0规范

设计模式概述

在软件工程中,设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由 Erich Gamma 等人在1990年代从建筑设计引入到计算机科学的。

设计模式并不直接用来完成代码的编写,而是描述在各种不同的情况下,要怎么解决问题的一种方案。面象对象设计通常以类或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增加软件设计面对并适应变化的能力。

并非所有的软件模式都是设计模式,设计模式特指软件“设计”层次上的问题。还有其它非设计模式的模式,如架构模式。同时,算法不能算是一种设计模式,因为算法主要是用来解决计算上的问题,而非设计上的问题。

作为程序员,我们所遇到的大部分问题其实都已被其他程序员一再地处理了。设计模式意味着智慧。一个模式一旦成为通用模式,就能丰富我们的语言,使我们可以轻松地分享设计思想及这些思想所带来的成果。设计模式提取了共同问题,定义了经过测试的解决方案并描述了可能的结果。

什么是设计模式

设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结 。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代 码可靠性。
设计模式有哪些特点呢?具体如下:

  1. 一个设计模式定义一个问题;
  2. 一个设计模式定义一个解决方案;
  3. 设计模式具有语言无关性;
  4. 模式定义了一组词汇,有助于交流,例如:“抽象工厂”;
  5. 模式是经过测试的;
  6. 模式是为协作而设计的;
  7. 设计模式促进良好设计;

设计模式核心

一个设计模式的核心由四个部分组成:命名、问题、解决方案和效果。

  1. 命名:命名非常重要,必须兼顾简洁性和描述性。
  2. 问题:找出问题比使用已知的设计模式更难,因为很多模式很可能比误用或者过度使用,所以问题及问题发生的环境都是一个模式的基础。
  3. 解决方案:虽然找到了问题的设计模式,但解决方案也不可能是简单的套用。模式描述了一个问题的一个解决方案,但在实现上可能会有千差万别。
  4. 效果:当部署了一个解决方案,设计了代码,紧接着就展现出效果。

PSR-0规范

使用PHP结合PSR-0规范和设计模式的设计理念可以开发出一个好的PHP框架。

PSR-0规范

  1. 命名空间必须与绝对路径一致
  2. 类名首字母必须大写
  3. 除入口文件外,其他”.php”必须只有一个类

开发符合PSR-0规范的基础框架

  1. 全部使用命名空间
  2. 所有PHP文件必须自动载入,不能有include/require
  3. 单一入口

对象与设计

在程序设计中,我们需要用一些标准来区分良好的设计和糟糕的设计。而其中的两种标准——可读性和重复性,相对较容易评估。其他标准,如灵活性和健壮性,则更难明确。我们通过面向过程和面向对象的比较,来讨论程序中的职责、内聚、耦合,最终明白为什么要这么做。

面向对象和面向过程

demo1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
header('Content-Type:text/html; charset=utf-8');

function write(){
return '正在写入txt文件';
}

function read(){
return '正在读取txt文件';
}

echo write();
echo read();

现在需求变更了,例如,我们希望可以将配置文件写成xml格式,则存在以下两种实现方法,分别如demo2.php和demo3.php所示。

demo2.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
<?php
header('Content-Type:text/html; charset=utf-8');


// 两个模块,write()函数,read()函数
// 内聚 = 模块内部的成分高度集中,就是高内聚,如果内部成分分散,那就是低内聚
// write() 方法内部成分是分散的,内部成分还不集中。
// 这里write()方法是低内聚。

// 耦合是模块与模块之间,内聚是模块内部之间
// write()和read()关联不?非常关联,紧密关联。
// write()和read()两个函数,能够分开使用吗?能独立吗?不能
// write()和read()模块之间是高耦合。


function write($file){
if($file == 'txt'){
//编写写入txt的业务代码
return '正在写入txt文件';
}else if($file == 'xml'){
return '正在写入xml文件';
}
}

function read($file){
if($file == 'txt'){
return '正在读取txt文件';
}else if($file == 'xml'){
return '正在读取xml文件';
}
}

$file = 'txt';

echo write($file);
echo read($file);

demo3.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
47
48
49
50
51
52
53
54
<?php
header('Content-Type:text/html; charset=utf-8');


abstract class Handler{ //抽象类,提供规范,让子类继承
abstract public function write(); // 提供规范,自己不实现,让子类实现
abstract public function read();
static public function getObject($file){//这里实现了类切换
if($file == 'xml'){
return new XmlHandler();
}elseif($file == 'txt'){
return new TxtHandler();
}
}
}

// 这里有两个模块Xml类,Txt类
// Xml类里面有两个成分,write xml文件, read xml文件
// Xml类里面的两个成分功能是相当的,高度集中,不分散。
// 高内聚 = 成分高度集中,还与外面不关联。

// xml类和txt类,关联吗?不关联
// 如果xml类做了大量修改,txt类不会受到任何影响,那么也就是说,两个类之间都非常独立。
// 低耦合 = 两个模块之间没有太太的关联性。



//专门处理xml文件的类
class XmlHandler extends Handler{
public function write(){
//编写写入txt的业务代码
return '正在写入txt文件';
}

public function read(){
return '正在读取txt文件';
}
}

//专门处理txt文件的类
class TxtHandler extends Handler{
public function write(){
return '正在写入xml文件';
}

public function read(){
return '正在读取xml文件';
}
}

$file = 'txt';
$obj = Handler::getObject($file);
echo $obj->write();
echo $obj->read();

可以发现,当项目较小时,demo2.php的方法可以更快的适应项目需求,但一旦项目较大时,例如还希望将配置文件存成yaml和json格式,还需要对配置文件进行删除和修改时,demo3.php的设计方式会比demo2.php更好。

面象对象的规范

职责

过程化的编码,控制代码的职责是判断文件格式,它判断两次不是一次。
面向对象的编码,父类的职责是提供规范,判断格式也只有一次,并且不考虑细节。

内聚

内聚是一个模块内部各成分之间相关程度的度量。如果组件职责清晰、分工明确,那么代码较为好维护;如果代码之间关联太广,则维护性较差。(因为你想修改某部分代码的同时,要修改相关代码)。
过程化的编码,xml 文件格式判断是分开在两个函数里判断,导致修改时要同时修改 ,这是低内聚。
面向对象的编码,xml 文件格式判断是只在父类的静态方法里,修改一个地方即可,这是高内聚。

耦合

当系统各部分代码紧密绑在一起时,就会产生紧密耦合。高耦合使代码扩展和维护异常艰难,而低耦合基本都是分开的功能模块,维护和扩展都非常方便。
过程化的编码,xml 处理分别包含在读和写两个函数中,也就是说,两个函数是紧密相连的,修改的时候,要同时修改,增加新文件判断,要同时新增,那么它就是高耦合。
面向对象的编码,xml 处理只放在 xml 类中,没有和其他类相紧密结合,那么它就是低耦合。

设计的四个方向标

没有人能在设计的时候绝对正确。大部分人都要不停地修改代码,因为需求可能发生变化,或者我们加深了对问题的认识。
修改代码的时候很容易失去控制。这里加个方法,那里加个类。导致代码和项目结构越发的混乱。

  1. 代码重复:如果有,请放到父类或者公共类,合并到一起。
  2. 类处理的太多:每个类最好保持独立性,能够尽量单独抽取出来还能使用。
  3. 万能类:如果一个类把所有工作都做了,那么请分离,分出父类和子类,父类做规范,子类做实现。
  4. 条件语句:如果发现一个类中,条件语句使用太过频繁,特别是同一种条件判断在多个方法中同时出现,那么就说明这个类需要拆分成两个或者更多。

常用设计模式大全

设计模式可以按照结构被分成三种不同的类型:

创建型

在软件工程中,创建型设计模式用于处理对象的实例化:

  • 单例模式(Singleton)
  • 简单工厂模式(Simple Factory)
  • 静态工厂模式(Static Factory)
  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 原型模式(Prototype)
  • 建造者模式(Builder)
  • 多例模式(Multiton)
  • 对象池模式(Pool)

结构型

结构型设计模式用于处理类和对象的组合:

  • 组合模式(Composite)
  • 装饰器模式(Decorator)
  • 外观模式(Facade)
  • 适配器模式(Adapter)
  • 桥梁模式(Bridge)
  • 数据映射模式(Data Mapper)
  • 依赖注入模式(Dependency Injection)
  • 流接口模式(Fluent Interface)
  • 代理模式(Proxy)
  • 注册模式(Registry)

行为型

行为型设计模式用于处理类的对象间通信:

  • 策略模式(Strategy)
  • 观察者模式(Observer)
  • 访问者模式(Visitor)
  • 命令行模式(Command)
  • 责任链模式(Chain Of Responsibilities)
  • 迭代器模式(Iterator)
  • 中介者模式(Mediator)
  • 备忘录模式(Memento)
  • 空对象模式(Null Object)
  • 规格模式(Specification)
  • 状态模式(State)
  • 模板方法模式(Template Method)

其它

  • 委托模式(Delegation)
  • 服务定位器模式(Service Locator)
  • 资源库模式(Repository)

参考: https://github.com/domnikl/DesignPatternsPHP