【PHP】高级特性以及设计模式归纳


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的使用,例如:


<?php function my_loader($class){ require('class\\'.$class.'.php');//对象new时候发现未定义,会自动到指定目录下加载对应对象 } spl_autoload_register('my_loader'); $is_girl = $_GET['sex'] == 0 ? true : false; if($is_girl){ echo 'this is a girl'; $class1 = new Class1;//未定义对象,不会报错,会反向调用my_loader去加载对象 }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的单例的确有点让各位失望. 但是单例仅仅只有这个功能和应用吗? 答案是否定的,我们一起来看看。

  1. php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时(废话), 如果使用单例模式, 则可以避免大量的new 操作消耗的资源。

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

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


<?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建立实例对象的方法。 那么注册树模式想解决什么问题呢? 在考虑这个问题前,我们还是有必要考虑下前两种模式目前面临的局限。 首先,单例模式创建唯一对象的过程本身还有一种判断,即判断对象是否存在。存在则返回对象,不存在则创建对象并返回。 每次创建实例对象都要存在这么一层判断。 工厂模式更多考虑的是扩展维护的问题。 总的来说,单例模式和工厂模式可以产生更加合理的对象。怎么方便调用这些对象呢?而且在项目内如此建立的对象好像散兵游勇一样,不便统筹管理安排埃因而,注册树模式应运而生。不管你是通过单例模式还是工厂模式还是二者结合生成的对象,都统统给我“插到”注册树上。我用某个对象的时候,直接从注册树上取一下就好。这和我们使用全局变量一样的方便实用。 而且注册树模式还为其他模式提供了一种非常好的想法。


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');

适配器模式

  • 可以将截然不同的函数接口封装成统一的API。
  • 实际应用举例,PHP的数据库操作有mysql、mysqli、pdo3种、可以用适配器模式统一成一致。类似的场景还有cache适配器,将memcache、redis、file、apc等不同的缓存函数,统一成一致。

//目标角色 interface Target { public function simpleMethod1(); public function simpleMethod2(); } //源角色 class Adaptee { public function simpleMethod1(){ echo 'Adapter simpleMethod1'."<br>"; } } //类适配器角色 class Adapter implements Target { private $adaptee; function __construct(Adaptee $adaptee) { $this->adaptee = $adaptee; } //委派调用Adaptee的sampleMethod1方法 public function simpleMethod1(){ echo $this->adaptee->simpleMethod1(); } public function simpleMethod2(){ echo 'Adapter simpleMethod2'."<br>"; } } //客户端 class Client { public static function main() { $adaptee = new Adaptee(); $adapter = new Adapter($adaptee); $adapter->simpleMethod1(); $adapter->simpleMethod2(); } } Client::main();

策略模式

  • 将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式就是策略模式。
  • 实际应用举例,假如一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有广告位展示不同的广告。
  • 实现Ioc,依赖倒置,控制反转。
<?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),当一个对象状态发生改变时,依赖它的对象全部会收到通知,并自动更新。
  • 场景:一个事件发生后,要执行一连串更新操作。传统的编程方法,就是在事件的代码之后直接加入处理逻辑。当更新的逻辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主体的代码。
  • 观察者模式实现了低耦合,非侵入式的通知与更新机制。
<?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->register(new Subscriber1());
//$paper->register(new Subscriber2());
$paper->trigger();

原型模式

  • 与工厂模式作用类似,都是用来创建对象的。
  • 与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象。这样就免去了类创建时重复的初始化操作。
  • 原型模式适用于大对象的创建,创建一个大对象需要很大的开销,如果每次new就会消耗很大,原型模式仅需内存拷贝即可。

装饰器模式

  • 装饰器模式(Decorator),可以动态地添加修改类的功能。
  • 一个类提供了一项功能,如果要在修改并增加额外的功能,传统的编程模式,需要写一个子类继承它,并重新实现类的方法。
  • 使用装饰器模式,仅仅需在运行时添加一个装饰器对象即可实现,可以实现最大的灵活性。
<?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;
}
}
// Test Case
//1.拿杯咖啡
$coffee = new Coffee();
//2.加点牛奶
$coffee = new Milk($coffee);
//3.加点糖
$coffee = new Sugar($coffee);
printf("Coffee Total:%0.2f元\n",$coffee->Cost());

迭代器模式

  • 迭代器模式,在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素。
  • 相比于传统的编程模式,迭代器模式可以隐藏遍历元素的所需的操作。
<?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;  
}  
//返回是否IsDone  
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();  
}  

代理模式

  • 在客户端与实体之间建立一个代理对象(proxy),客户端对实体进行操作全部委派给代理对象,隐藏实体的具体实现细节。

  • Proxy还可以与业务代码分离,部署到另外的服务器,业务代码中通过RPC来委派任务。

<?php
class Printer {   //代理对象,一台打印机
public function printSth() {
echo 'I can print <br>';
}
// some more function below
// ...
}
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.面向接口编程:只需要关心接口,不需要关心实现。

声明:枫言枫语 | 版权所有,违者必究 | 如未注明,均为原创 |

本网站采用CC BY-NC-SA 3.0国际化协议进行授权

转载:转载请注明原文链接 - 【PHP】高级特性以及设计模式归纳


只有汗水不会欺骗你