PS:之前在和Mentor一起开发新项目时候,接触到了一些很经典的PHP设计模式和高级特性,通过项目沉淀和业余时间的学习,想对常用设计模式进行一个归纳,进行新知识的梳理,方便服务于未来更大型的后端项目,以及帮助更多的PHPer上路。
PSR
FIG制定的PHP规范,简称PSR,是PHP开发的事实标准,现在主流的PHP框架:symfony、laravel、yii、thinkphp、百度odp等都严格遵守这个规范。
PSR-0 自动加载
- 命名空间必须与绝对路径一致。
- 类名首字母必须大写。
- 除入口文件外,其他”.php”必须只有一个类。
开发符合PSR-0规范的基础框架
- 严格全部使用命名空间。
- 所有PHP文件必须自动载入,不能有include/require。
- 单一入口。
PSR-1 基本代码规范
PSR-1规范详细介绍
PSR-2 代码样式
PSR-2代码样式详细介绍
PSR-3 日志接口
PSR-3日志接口详细介绍
PSR-4 自动加载升级版
PSR-4 PHP规范
SPL
SPL(PHP标准库)
- SqlStack,SqlQueue,SplHeap,SqlFixedArray等数据结构类。
- ArrayIterator、AppendIterator、Countable、ArrayObject。
- SPL提供的函数。
Eg

YII
之所以能成为最快的PHP框架,源于使用了SPL
提供的LazyLoading
技术,即:vendor/yiisoft/yii2/Yii.php中sql_autoload_register
的使用,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php function my_loader($class){ require('class\\'.$class.'.php'); }
spl_autoload_register('my_loader');
$is_girl = $_GET['sex'] == 0 ? true : false;
if($is_girl){ echo 'this is a girl'; $class1 = new Class1; }else{ echo 'not a girl'; $class2 = new Class2; } ?>
|
PS:想深入了解SPL标准库的话,这里有一篇一峰前辈的博文SPL笔记,国内应该找不到比这更好的中文参考文献了,同时我自己也整理了一篇关于SPL的博文,有兴趣的同学也可以看一看:传送门。
PHP链式操作
eg: $db ->where()->limit()->order();
不过多解释了,天天用的。
PHP魔术方法
__get/__set
常用于类的构造方法,当对象的属性不存在时,将自动调用__set
初始化属性,__get
返回对象初始化属性。
__call/__callStatic
常用于类的构造方法,当对象的方法不存在时,将自动回调__call
初始化方法,__callStatic
针对静态方法。
__toString
将对象转换成字符串类型。
__invoke
把对象当成函数去使用,则自动回调__invoke
方法,返回对象传入的参数。
工厂模式
工厂模式的最大优点在于创建对象上面,就是把创建对象的过程封装起来,这样随时可以产生一个新的对象。
减少代码进行复制粘帖,耦合关系重,牵一发动其他部分代码。
通俗的说,以前创建一个对象要使用new,现在把这个过程封装起来了。
假设不使用工厂模式:那么很多地方调用类a,代码就会这样子创建一个实例:new a(),假设某天需要把a类的名称修改,意味着很多调用的代码都要修改。
工厂模式的优点就在创建对象上。
工厂模式的优点就在创建对象上。建立一个工厂(一个函数或一个类方法)来制造新的对象,它的任务就是把对象的创建过程都封装起来,
创建对象不是使用new的形式了。而是定义一个方法,用于创建对象实例。
每个类可能会需要连接数据库。那么就将连接数据库封装在一个类中。以后在其他类中通过类名:
为什么引入抽象的概念?
想一想,在现实生活中,当我们无法确定某个具体的东西的时候,往往把一类东西归于抽象类别。
工厂方法:
比如你的工厂叫做“香烟工厂”,那么可以有“七匹狼工厂”“中华工厂”等,但是,这个工厂只生厂一种商品:香烟;
抽象工厂:无法描述它到底生产什么产品,它生产很多类型的产品(所以抽象工厂就会生成子工厂)。
你的工厂是综合型的,是生产“一系列”产品,而不是“一个”,比如:生产“香烟”,还有“啤酒”等。然后它也可以有派生出来的具体的工厂,但这些工厂都是生产这一系列产品,只是可能因为地域不一样,为了适应当地人口味,味道也不太一样。
工厂模式:理解成只生成一种产品的工厂。比如生产香烟的。
工厂方法:工厂的一种产品生产线 。比如键盘的生成过程。
别人会反驳:吃饱了没事干,一定要修改类名称呢?这个说不定。一般都不会去修改类名称。
其实工厂模式有很多变体,抓住精髓才是关键:只要是可以根据不同的参数生成不同的类实例,那么就符合工厂模式的设计思想。
单例模式
单例模式的要点有三个:
一是某个类只能有一个实例;
二是它必须自行创建这个实例;
三是它必须自行向整个系统提供这个实例。
下面我们讨论下为什么要使用PHP单例模式?
多数人都是从单例模式的字面上的意思来理解它的用途, 认为这是对系统资源的节省, 可以避免重复实例化, 是一种”计划生育”. 而PHP每次执行完页面都是会从内存中清理掉所有的资源. 因而PHP中的单例实际每次运行都是需要重新实例化的, 这样就失去了单例重复实例化的意义了. 单单从这个方面来说, PHP的单例的确有点让各位失望. 但是单例仅仅只有这个功能和应用吗? 答案是否定的,我们一起来看看。
php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时(废话), 如果使用单例模式, 则可以避免大量的new 操作消耗的资源。
如果系统中需要有一个类来全局控制某些配置信息, 那么使用单例模式可以很方便的实现. 这个可以参看zend Framework的FrontController部分。
在一次页面请求中, 便于进行调试, 因为所有的代码(例如数据库操作类db)都集中在一个类中, 我们可以在类中设置钩子, 输出日志,从而避免到处var_dump, echo。
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
| <?php class EasyFramework_Easy_Mysql{ protected static $_instance = null; private function __construct(){
} public static function getInstance(){ if (self::$_instance === null){ self::$_instance = new self(); } return self::$_instance; }
protected function __clone(){
}
} $x = EasyFramework_Easy_Mysql::getInstance(); var_dump($x); ?>
* 1.第一步: * 既然是单例,也就是只能实例化一次,也就代表在实例化时 * 不可能使用new关键字!!!! * 在使用new关键字时,类中的构造函数将自动调用。 * 但是,如果我们将构造函数的访问控制符设置为protected或private * 那么就不可能直接使用new关键字了!!! * 第二步: * 无论protected/private修饰的属性或方法,请问在当前类的 * 内部是否可以访问?---> 可以 * 第三步: * 现在我们根本没有办法得到对象(因为你不能使用new关键字了), * 第四步:静态成员(包括属性或方法)在访问时,只能通过 * 类名称::属性() * 类名称::方法() * 第五步:如果我现在存在一个静态方法--> getInstance() * 那么在调用时就应写成 * $object = EasyFramework_Easy_Mysql::getInstance() * 如果,getInstance()方法可以得到唯一的一个对象 * 也就代表是所谓的单例模式了!!! * 第六步,怎么让getInstace()只得到一个对象呢? * 既然要得到对象,那么肯定是: * $variabl = new ????(); * 我们又知道静态属性的值是可以所有的对象来继承的!!! * 静态成员是属于类的,而非对象的! * 所以 * 第七步:声明一个静态的属性,用其存储实例化的对象 * protectd static $_instance * * 并且初始值为null * 那么我在调用getInstance()方法时,只需要判断其值是否为空即可\ * * public static function getInstance(){ * if(self::_instance === null){ * self::_instance = new self(); * } * return self::_instance; * } * 在实例时,一定是这样写: * $x = EasyFramework_Easy_Mysql::getInstance(); * 在第一时调用时,类的$_instance这个静态属性值为null, * 那么也就代表,getInstance()方法的判断条件为真了, * 也就意味着 * self::$_instance这个成员有了值了!!! * 并且还返回这个值 * $y = EasyFramework_Easy_Mysql::getInstance(); * 在第二次或第N次调用时,self::$_instance已经有了值了 * 也就代表getInstance()方法的条件为假了!!! * 也就代表其中的程序代表不可能执行了!!! * 也就代表将直接返回以前的值了!!! */
|
注册器模式
单例模式解决的是如何在整个项目中创建唯一对象实例的问题,工厂模式解决的是如何不通过new建立实例对象的方法。 那么注册树模式想解决什么问题呢? 在考虑这个问题前,我们还是有必要考虑下前两种模式目前面临的局限。 首先,单例模式创建唯一对象的过程本身还有一种判断,即判断对象是否存在。存在则返回对象,不存在则创建对象并返回。 每次创建实例对象都要存在这么一层判断。 工厂模式更多考虑的是扩展维护的问题。 总的来说,单例模式和工厂模式可以产生更加合理的对象。怎么方便调用这些对象呢?而且在项目内如此建立的对象好像散兵游勇一样,不便统筹管理安排埃因而,注册树模式应运而生。不管你是通过单例模式还是工厂模式还是二者结合生成的对象,都统统给我“插到”注册树上。我用某个对象的时候,直接从注册树上取一下就好。这和我们使用全局变量一样的方便实用。 而且注册树模式还为其他模式提供了一种非常好的想法。
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
| class Register{
protected static $objects;
public static function set($alias,$object){
self::$objects[$alias]=$object;
}
public static function get($alias){
return self::$objects[$alias];
}
public static function _unset($alias){
unset(self::$objects[$alias]);
}
}
Register::set('rand',RandFactory::factory());
$object=Register::get('rand');
|
适配器模式
策略模式
- 将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式就是策略模式。
- 实际应用举例,假如一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有广告位展示不同的广告。
- 实现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 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 TravelStrategy{ public function travelAlgorithm(); }
* 具体策略类(ConcreteStrategy)1:乘坐飞机 */ class AirPlanelStrategy implements TravelStrategy { public function travelAlgorithm(){ echo "travel by AirPlain", "<BR>\r\n"; } }
* 具体策略类(ConcreteStrategy)2:乘坐火车 */ class TrainStrategy implements TravelStrategy { public function travelAlgorithm(){ echo "travel by Train", "<BR>\r\n"; } }
* 具体策略类(ConcreteStrategy)3:骑自行车 */ class BicycleStrategy implements TravelStrategy { public function travelAlgorithm(){ echo "travel by Bicycle", "<BR>\r\n"; } }
* * 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。 * 算法解决类,以提供客户选择使用何种解决方案: */ class PersonContext{ private $_strategy = null; public function __construct(TravelStrategy $travel){ $this->_strategy = $travel; } * 旅行 */ public function setTravelStrategy(TravelStrategy $travel){ $this->_strategy = $travel; } * 旅行 */ public function travel(){ return $this->_strategy ->travelAlgorithm(); } }
$person = new PersonContext(new TrainStrategy()); $person->travel();
$person->setTravelStrategy(new BicycleStrategy()); $person->travel(); ?>
|
控制反转(IOC),依赖注入(DI)
eg:谈谈PHP实现依赖注入(控制反转)
数据对象映射模式(ORM)
- 数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作。
- 在代码中实现数据对象映射模式,我们将实现一个ORM类,将复杂的SQL语句映射成对象属性的操作。
- 结合使用数据对象映射模式,工厂模式,注册模式。
观察者模式
- 观察者模式(Observer),当一个对象状态发生改变时,依赖它的对象全部会收到通知,并自动更新。
- 场景:一个事件发生后,要执行一连串更新操作。传统的编程方法,就是在事件的代码之后直接加入处理逻辑。当更新的逻辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主体的代码。
- 观察者模式实现了低耦合,非侵入式的通知与更新机制。
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
| <?php
* 观察者模式 * @author: Mac * @date: 2012/02/22 */ class Paper{ private $_observers = array(); public function register($sub){ $this->_observers[] = $sub; } public function trigger(){ if(!empty($this->_observers)){ foreach($this->_observers as $observer){ $observer->update(); } } } }
* 观察者要实现的接口 */ interface Observerable{ public function update(); } class Subscriber implements Observerable{ public function update(){ echo "Callback\n"; } }
下面是测试代码
$paper = new Paper(); $paper->register(new Subscriber());
$paper->trigger();
|
原型模式
- 与工厂模式作用类似,都是用来创建对象的。
- 与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样就免去了类创建时重复的初始化操作。
- 原型模式适用于大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需内存拷贝即可。
装饰器模式
- 装饰器模式(Decorator),可以动态地添加修改类的功能。
- 一个类提供了一项功能,如果要在修改并增加额外的功能,传统的编程模式,需要写一个子类继承它,并重新实现类的方法。
- 使用装饰器模式,仅仅需在运行时添加一个装饰器对象即可实现,可以实现最大的灵活性。
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
| <?php abstract class Beverage{ public $_name; abstract public function Cost(); }
class Coffee extends Beverage{ public function __construct(){ $this->_name = 'Coffee'; } public function Cost(){ return 1.00; } }
class CondimentDecorator extends Beverage{ public function __construct(){ $this->_name = 'Condiment'; } public function Cost(){ return 0.1; } } class Milk extends CondimentDecorator{ public $_beverage; public function __construct($beverage){ $this->_name = 'Milk'; if($beverage instanceof Beverage){ $this->_beverage = $beverage; }else exit('Failure'); } public function Cost(){ return $this->_beverage->Cost() + 0.2; } } class Sugar extends CondimentDecorator{ public $_beverage; public function __construct($beverage){ $this->_name = 'Sugar'; if($beverage instanceof Beverage){ $this->_beverage = $beverage; }else{ exit('Failure'); } } public function Cost(){ return $this->_beverage->Cost() + 0.2; } }
$coffee = new Coffee();
$coffee = new Milk($coffee);
$coffee = new Sugar($coffee); printf("Coffee Total:%0.2f元\n",$coffee->Cost());
|
迭代器模式
- 迭代器模式,在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素。
- 相比于传统的编程模式,迭代器模式可以隐藏遍历元素的所需的操作。
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
| <?php
* Created by PhpStorm. * User: Jiang * Date: 2015/6/8 * Time: 21:31 */
abstract class IIterator { public abstract function First(); public abstract function Next(); public abstract function IsDone(); public abstract function CurrentItem(); }
class ConcreteIterator extends IIterator { private $aggre; private $current = 0; public function __construct(array $_aggre) { $this->aggre = $_aggre; } public function First() { return $this->aggre[0]; } public function Next() { $this->current++; if($this->current<count($this->aggre)) { return $this->aggre[$this->current]; } return false; } public function IsDone() { return $this->current>=count($this->aggre)?true:false; } public function CurrentItem() { return $this->aggre[$this->current]; } }
header("Content-Type:text/html;charset=utf-8");
require_once "./Iterator/Iterator.php"; $iterator= new ConcreteIterator(array('周杰伦','王菲','周润发')); $item = $iterator->First(); echo $item."<br/>"; while(!$iterator->IsDone()) { echo "{$iterator->CurrentItem()}:请买票!<br/>"; $iterator->Next(); }
|
代理模式
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
| <?php class Printer { public function printSth() { echo 'I can print <br>'; } } class TextShop { private $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function sellPaper() { echo 'give you some paper <br>'; } public function __call($method, $args) { if(method_exists($this->printer, $method)) { $this->printer->$method($args); } } } class PhotoShop { private $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function takePhotos() { echo 'take photos for you <br>'; } public function __call($method, $args) { if(method_exists($this->printer, $method)) { $this->printer->$method($args); } } } $printer = new Printer(); $textShop = new TextShop($printer); $photoShop = new PhotoShop($printer); $textShop->printSth(); $photoShop->printSth(); ?>
|
面向对象编程的基本原则
- 1.单一职责:一个类,只需要做好一件事情。
- 2.开放封闭:一个类,应该是可扩展的,而不可修改的。
- 3.依赖倒置:一个类,不应该强依赖另一个类,每个类对于另一个类都是可替换的。
- 4.配置化:尽可能地使用配置,而不是硬编码。
- 5.面向接口编程:只需要关心接口,不需要关心实现。