设计模式
设计模式是开发人员在面向对象编程实践在总结的经验和方案.
PHP中常用到的11种设计模式
- 工厂模式
- 单例模式
- 注册模式
- 适配器模式
- 策略模式
- 数据对象映射模式
- 观察者模式
- 原型模式
- 装饰器模式
- 迭代器模式
- 代理模式
设计模式参考 :Php设计模式
设计原则
1.单一职责 原则
一个类,只专心做好一件事情
2.开/闭 原则(OCP)
对扩展开放,对修改关闭.使程序易于升级与维护,对程序扩展时尽量不去修改原有代码.
3.里氏代换 原则(LSP)
任何基类可以出现的地方,子类一定可以出现.但不能相反.
4.依赖倒转 原则
开闭原则的基础,针对接口编程,依赖抽象而不依赖具体.
5.接口隔离 原则(ISP)
降低类之间的耦合度,最小单一职责接口,不使用不需要的接口.(这里的接口指抽象类,抽象层)
6.迪米特 法则(LOD)
最小知道原则,
被依赖者:只暴露应该暴露的方法,
依赖者:只依赖应该依赖的对象
7. 配置化
尽量使用配置化,而不是硬编码
8.合成复用 原则
尽量使用合成/聚合方式,而不是使用继承.
php设计模式的基础
了解的设计模式的基础,先了解PHP的基础语法和面向对象的特性,以及每个PHP版本的更新变化.
面向对象的高级特性.
命名空间
php5.3以后新增了命名空间. 命名空间允许同名的类名,避免命名冲突.
命名空间和文件系统中的目录结构原理类似.
可以于类,函数,常量一起使用.方便软件工程管理.
参考: php手册-命名空间
定义命名空间
必须在php文件前申明,以”\”分割多个单词.
注意点:
- 命名空间名字为变量名,如不能以数字或特殊字符开头;
- 不能使用保留字,如php,PHP,PHP\Classes
- 名称本身不能与其它命名重复冲突
要符合PSR-4规范,命名空间要与文件目录和文件名一一关联,注意文件名大小写,从根目录往下层级一一对应.
参考: psr-4自动加载规范>>
[app.php]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace App;
//常量 5.3以后可以使用const 定义常量
const Author="arkin";
// 函数
function index(){
echo "index";
}
// 类
class testClass{
const NAME="testClass";
// 静态方法
static function index(){
echo "testClass.index";
}
}
也可以在同一个文件中定义多个命名空间.
命名空间,之后的代码.
1 | // 第一种 |
使用命名空间
引用文件后,可以使用.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require ('app.php');
// 使用常量
echo App\Author;
// 使用函数
echo App\index();
// 使用类
$AClass= new App\testClass();
// 使用类的常量
echo App\testClass::Name;
// 调用类的静态方法
echo App\testClass::index();
// 全局 空间,包含系统自带类和函数
throw new \Exception("有错误");
use 别名
除了以上通过全路径命名空间外,可use别名.
(>PHP5.6)
如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 类别名,简写 use App\Controller;
use App\Controller as Controller;
// 函数别名
use function App\Controller\out as msg;
// 常量别名
use const App\Controller\Module as MdName;
// 多个连写
use App\Model\A,App\Model\B;
// 子命名空间
use App\Model\{
User,
function bindsql,
const Version as MySQLVer
};
匿名函数
参考: php手册-匿名函数
在php5.3后引用.
又叫闭包函数(closures),其它语言使用lambda关键字.经常用于回调函数.临时创建的一个没名称的函数.和普通函数一样.
PHP匿名函数通过Closure类来实现.
定义
1 | // 示例1 |
匿名函数 变量传递
通过use来传递变量.变量类型.支持所有变量类型.
注:在PHP7.1以后,不允许传递\$this和内置的超全局变量(如\$_GET,\$_COOKIE),在匿名函数中可以获取到.
也不能与参数名重复.
例子:1
2
3
4
5
6
7
8
9
10$userName="arkin";
$local="北京";
// 这里使用的强类型,use ()传递变量.
$show=function (string $msg) use($userName,&$local) :string{
$local="上海";
return $userName.':'.$msg;
};
echo $show('走你!');// 输出,'arkin:走你'
echo $local;// 输出: 上海
绑定对象
可以绑定新的对象到匿名函数.其实就Closure对象.1
2
3
4
5
6
7
8// 此时$this为空
$func=function (){
var_dump($this);
};
// 把新的对象 stdClass绑定到 匿名函数
$fun2=$func->bindTo(new stdClass());
// 此时的$this 为 stdClass()
$fun2();
匿名函数在php5.3以后会自动绑定$this,除非申明函数为静态匿名函数.1
2
3
4
5
6
7
8
9
10
11
12class A {
function __construct(){
//
$fun=function(){
var_dump($this);// 正常
};
// 静态匿名函数
return static function (){
var_dump($this);// 报错
}
}
}
回调函数
内核自带很多地方需要用到回调函数.
回调函数定义的6种方法.不同的函数可能实现不一样,大部分通用.
下面以array_map()为例;创建回调函数的方法.
1. 普通函数
回调函数字符串的函数名称.1
2
3
4
5
6
function callback($v){
echo $v;
}
array_map('callback',[1,2,3]);
2. 匿名函数
使用匿名函数,最常用的方法.1
2
3
array_map(function($v){echo $v;},[1,2,3]);
3. 类的方法
支持两种字符类名+方法名与数组类名+方法名;1
2
3
4
5
6
7
8
9class MyClass {
static function callback($v){}
}
// 第1种, 静态类字符串
array_map('MyClass::callback',[1,2,3]);
// 第2种, 静态类数组
array_map(['MyClass','callback'],[1,2,3]);
// 第3种, 实例对象方法
array_map([new MyClass(),'callback'],[1,2,3]);
4. 命名空间
和静态方法类似.1
2
3namespace My;
static function callback($v){};
array_map('My\callback',[1,2,3]);
5. 函数生成器
使用create_function 函数生成器,有时在代码生成器时需要到.1
array_map(create_function('$v','echo $v;'),[1,2,3]);
6. 内置函数
1 | $arr=array_map('htmlentities',['<a>','<script>','xxxp']); |
匿名类
php7.0开始支持匿名类,anonymous class;
定义
和普通类一样定义,也可以继承 ,使用接口1
2
3
4
5
6
7
8$class=new class{
function __construct()
{
echo "haha";
}
function lala(){}
}
$class->lala();
stdClass对象
stdClass是一个特殊的基类,不需要定义就可以使用.但是stdClass没有方法,只能使用属性.
由于php没有定义空对象的方法,一般使用stdClass来创建一个空对象.
1 | $obj= new stdClass(); |
类的自动加载
php使用require或include手动引用,在php5.2以后提供php自动引用功能.
自动引用类机制是,当发现未加载的类时会自动执行载入函数.
__autoload()
__authload()函数只可以定义一次.
1 |
|
spl_autoload_register()
spl是PHP的标准库的一类.
参考 :
spl_autoload_register好处:
- 可以定义多个autoload函数
- 支持异常处理
- 可以注销已注册的函数
示例简单自动加载,使用了命名空间.
目录结构如下:
Loader.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
30namespace Bootstrap;
class Loader
{
private $classMap=[];
public function __construct()
{
// 注册加载函数
spl_autoload_register([$this,'register'],true);
}
function __destruct()
{
spl_autoload_unregister([$this,'register']);
}
// 加载函数
private function register($class){
// 目录分隔符 windows为 '\',linux为 '/'
$sep=DIRECTORY_SEPARATOR;
$file=__ROOT.$sep.str_replace('\\',$sep,$class).'.php';
if(file_exists($file)){
$loaded=include($file);
if($loaded){
$this->classMap[$class]=$file;
}
}
}
public function getClassMap(){
return $this->classMap;
}
}
index.php,入口文件引用1
2
3
4
5
6
7
8
9// 根目录
const __ROOT=__DIR__;
require_once __ROOT.'/Bootstrap/Loader.php';
$autoload=new \Bootstrap\Loader();
// 使用类
(new App\Controller\Home())->index();
var_dump($autoload->getClassMap());
PHP标准库(SPL)
参考 : SPL文档
常用到的是:SPL函数,数据结构和迭代器
PHP常用的数据结构
PHP常见的数据结构,数组,队列,堆,栈
1.数组
数组: 连续方式存储数据的结构,可以通过索引访问.注意不是指PHP的数组.
特点: 固定长度,索引只能为数字,性能快
使用SplFixedArray类1
2
3
4$arr= new SplFixedArray(3);//0,1,2
$arr[2]="abc";
$arr[3]="ccdd";// 这里会报错,超时索引最大值
$arr->getSize(4);// 可以修改数组长度
PHP内置数组底层使用的是HashTable实现.
2.队列
一种特殊的线性表,特性是先进先出.
主要支持:入队(enqueue),出队(dequeue)1
2
3
4
5$que = new SplQueue();
$que->enqueue("1");
$que->enqueue("2");
echo $que->dequeue();//1
echo $que->dequeue();//2
3.栈
特性: 先进后出
主要方法: 入栈(push)/出栈(pop)1
2
3
4
5que = new SplStack();
$que->push("1");
$que->push("2");
echo $que->pop();//2
echo $que->pop();//1
4.堆
不会.
序列化/反序列化
序列化的目的,是将变量转成可存储和传输的字符串的过程.
PHP常用的序列化方式,
- json_encode/json_decode
- serialize/unserialize
serialize 对象
对象在序列化时会先尝试调用sleep(),unserialize时尝试调用wakeup();
serialize不可以序列化匿名类.
// 第一种,使用sleep和wakup
__sleep用来做返回属性名称的数组,不能返回父类属性.
注意:反序列化时,类必须存在
1 | class myClass{ |
应用场景
在反序列化对象时,需要加载对应的类.
序列化,常用于缓存对象,异构系统之间传输对象.
常见的异步处理消息队列,在消息体中会存储依赖类的路径和序列化的数据;在消费时会加载依赖的类,在反序列化.
反顺序化时会调用自动加载类函数.__autoload或spl_autoload_register;
PSR规范
无规矩没方圆, PHP规范是PHP软件工程的基础.
- PSR-0 和 PSR-4: 自动加载标准,命名规范,现在使用PSR-4代替.
- PSR-1 和 PSR-2: 编码风格规范
- PSR-3 : 日志规范
PSR-0 ~ PSR-4
- 全部使用命名空间,命名空间与目录结构一致;
- 所有PHP文件必须使用自动载入,不能有Include/require;
- 只有一个单一入口;
- 类名要与文件名一致,一个文件只有一个类,不能含有其他运行代码;
大部分主流的PHP的框架都遵守PSR规范.
其它规范参考官方文档,与设计模式无关.
PHP链式操作
在一些框架,如Laravel中经常使用链式操作.
实现原理:通过调用方法中 返加 \$this;
1 | class BaseModel |
魔术方法
get/set
对象属性不存在时;
set: 当外部设置赋值一个不存在的属性时,会触set方法;该方法有两个参数:\$name:属性名称,\$value:属性值;1
2
3
4
5
6
7
8class MyClass{
function __set($name, $value){
$this->$name=$value;
}
}
$obj= new MyClass();
$obj->name="haha";// 不存在name属性
get: 当调用一个不存在的属性时,会触发 \get方法;1
2
3
4
5
6
7
8class MyClass{
function __get($name){
return $name."不存在";
}
}
$obj= new MyClass();
$obj->name="haha";// 不存在name属性
call/callStatic
对象和类方法不存在时.
当调用不存在的方法时,会触发 call方法
call有两个参数: \$func:方法名,\$params:参数
返回: 当作不存在方法的返回值.
__callStatic: 为静态方法
1 | class MyClass{ |
__toString
将对象转字符串.
在 echo 一个对象时.
可以使用 __toString(){ return “classsxx”;} 返回字符串.
__invoke
将对象当成函数来执行;
1 | class MyClass{ |
浅拷贝与深拷贝
这个在JS中也是一个注意点,拷贝本身是一个耗资源的过程.
为了利用内存空间,程序语言设计时都在拷贝做优化,在文件系统中快捷方式类似.
浅拷贝:指共享内存空间地址,起了个别名,修改一个很影响到另一个.
深拷贝:指把内存空间复制一份出来,另起存储,从拷贝的那一刻起,大家一刀两断,互不影响.
普通变量
“=”赋值为深拷贝,使用”=&”赋值为浅拷贝.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18$a=100;
// 深拷贝
$b=$a;
// 浅拷贝
$c=&$a;
$a=200;
var_export([
'$a'=>$a,//200
'$b'=>$b,//100
'$c'=>$c,//200
]);
$c=300;
var_export([
'$a'=>$a,//300
'$b'=>$b,//还是100
'$c'=>$c,//300
]);
对象
普通”=”赋值为浅拷贝,使用”clone”为深拷贝1
2
3
4
5
6
7
8
9
10
11
12
13
14
15$obj=new stdClass();
$obj->name="阿金";
// 浅拷贝,相当于起了别名
$boy=$obj;
// 深拷贝,clone了一模一样的我
$girl=clone $obj;
$obj->name="Arkin";
echo $obj->name.'--'.$boy->name,PHP_EOL;//Arkin - Arkin
$boy->name="Kim";
echo $obj->name.'--'.$boy->name,PHP_EOL;//Kim - Kim
echo $obj->name.'--'.$girl->name,PHP_EOL;//Kim - Arkin
$girl->name="丽娜";
echo $obj->name.'--'.$girl->name,PHP_EOL;//Kim - 丽娜
函数参数
与变量赋值一样,普通参数为深拷贝,带”&”为浅拷贝1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16$ren="红";
// 出淤泥不染
function lianhua($color){
$color="黑";
echo $color;
}
// 变了
function shehui(&$color){
$color="黑";
echo $color;
}
lianhua($ren);
echo $ren,PHP_EOL;// 保持本心,"红"
shehui($ren);
echo $ren,PHP_EOL;// 被传染,"黑"
PHP设计模式
常用的工厂模工,单例模式,注册机模式;
工厂模式
使用统一的类或方法去new 对象,而不是手工new 对象;
优点:
单一创建对象入口;当项目中有很多地方需要new 一个对象时,假如对象的参数,或对象名变化,就需要批量修改 new 的代码.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class DataBase(){
function __construct($host,$database,$user,$password){
}
}
class Factory{
static function createDatabase(){
$db=new DataBase("127.0.0.1",'test','root','123456');
return $db;
}
}
$db1=Factory::createDatabase();
$db2=Factory::createDatabase();
$db3=Factory::createDatabase();
单例模式
避免资源浪费,减少连接数.如数据库连接,共用一个即可.
如上,已经new 过的对象,后面就不需要再次new了.
主要实现步骤:
- 把 __construct 析构函数变成私有private.就不可以直接new 实例化.
- 申明一个获取实例的静态方法, 如getInstance()方法
- 申明一个私有的属性来存储实例.
- 当发现未实例化过,实例化 new self();,已实例化则直接返回该实例.
1 | class DataBase(){ |
注册机模式
全局统一注册,其它地方获取而不是用实例化.
主要两个方法: set(‘别名’,’实例化对象’),remove(‘别名’),get(‘别名’);
统一set和remove,其它统一get.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//
class Register
{
// 对象库
protected static $objects=[];
static function set($alias,$object){
self::$objects[$alias]=$object;
}
static function remove($alias){
unset(self::$objects[$alias]);
}
static function get($alias){
return self::$objects[$alias];
}
}
适配器模式
将截然不同的函数接口封装成统一的API
比如,数据库将mysql,mysqli,pdo封装成成统一操作接口,比如缓存:统一redis,memecache,mongodb
实现步骤:
- 约定统一接口
- 分别创建实例,实现约定的方法
1 | // 统一接口 |
其它实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 实现接口
class Mysqli implements IDatabase
{
protected $conn;
function connect($host,$user,$password,$dbName){
$conn=mysqli_connect($host,$user,$password,$dbName);
$this->conn=$conn;
}
function query($sql){
return mysqli_query($this->conn,$sql);
}
function close(){
mysqli_close($this->conn);
}
}
策略模式
将一组特定的行为和算法封装成类,以适应某些特定的上下文环境.
比如, 不同的用户角色类型,展示不同的广告.不同的角色使用不同的策略.如果很多页面,则需要很多次硬编码.
使用策略模式,可以减少工作量和解耦.
实现方式:
- 定义接口类
- 每种策略实现一种接口
- 在调用的提供一个设置策略接口
- 全局统一策略,把策略传递给需要实现策略的地方
广告策略类:
1定义接口1
2
3
4
5interface UserStrategy
{
function showAd();
function showCategory();
}
2.实现多种策略接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Male implements UserStrategy
{
function showAd(){
echo "男装";
}
function showCategory(){}
}
class Female implements UserStrategy
{
function showAd(){
echo "女装";
}
function showCategory(){}
}
3页面1
2
3
4
5
6
7
8
9
10class Home {
protected $strategy;
// 设置
function setStrategy(\App\Strategy\UserStrategy $strategy){
$this->strategy=$strategy;
}
function index{
echo "广告",$this->strategy->show();
}
}
4入口,根据不同条件,传递不同接口实现1
2
3
4
5
6
7if(isset($_GET['female'])){
$strategy= new \App\Strategy\Female();
}else{
$strategy= new \App\Strategy\Male();
}
$page->setStrategy($strategy);
$page->index();
数据对象映射模式
将对象和数据存储映射,对一个对象的操作会映射为对数据的操作;
场景: 比如Model的属性和表的字段映射起来.常用在ORM上.
简单实现方式:
- 创建一个类,属性与数据库表字段一致.
- construct时连接数据库表
- destruct量把model的属性更新到数据表
示例
1 | class User { |
观察者模式
当一个对象状态发生变化时,依赖它的对象全部收到通知,并自动更新.
场景: 一个事件发生后,要执行一连串更新操作.
传统方法是,直接在事件中加入处理逻辑,当需要加入或更新更多的处理逻辑时就需要修改事件主体本身.
观察者模式使用:通知 与 更新机制
即是: 订阅 –> 通知 模型
使用之前:
1 | namespace App\Events; |
使用观察者:
分 事件部分 和 观察者部分.
事件部分:
1步:事件发生 抽象类
1 | namespace App\Events; |
2步,观察者接口
1 | namespace App\Observers; |
(PS,由于markdown编辑器代码太多,出现卡顿,下面使用图片代替代码)
3步,实现具体事件
4步,创建观察者,一个或多个
5步,事件中,添加观察者
运行结果.
事件观察者和事件监听者一样.稍微区别.
事件观察: 被观察者 –> 观察者(多个)
事件监听: 事件源(多个)–> 事件 –> 监听器(多个)
盗用图:
原型模式
与工厂模式类似,都是统一创建对象.
原型类是JS中的原型,先创建好一个原型对象,然后clone 原型对象来创建新的对象.
这样免去了类实例化操作,减少内存开销.
先理解对象的深拷贝和浅拷贝.
使用场景: 初始实例化类消耗大量资源时
步骤:1. 创建一个原型对象 2. clone 原型对象
装饰器模式
实际上,可以动态和修改 类的功能.
场景: 需要灵活动态添加和修改类的功能时.
也可以理解为中间件.钩子
如: 控制器 增加调用前和调用后两个装饰器,就可以动态添加改变内容了.
1步,绑定勾子,寻增加装饰函数,执行函数
2步,调用时可以动态添加装饰函数
除了装饰函数,也可以使用装饰类.
类似CI框架通过大量的勾子实现中间件的功能.
Laravel框架中间件.
注意点,勾子和中间件函数,都提供一个类似 next()方法,来传递是否进行.
迭代器模式
在不需要了解内部的前提下,遍历一个聚合对象.
php 中已经有很多迭代器.其它可以简单理解为可以循环的对象.
通过实现 Iterator类.
主要实现5个方法:current返回当前数据,key返回当前索引,next下一个索引,rewind重置索引,valid验证数据
创建迭代器类.
然后就可以循环了.1
2
3
4$users= new Users();
foreach ($users as $key=>$user){
echo $key,'=>',$user,PHP_EOL;
}
代理模式
和我们理解的代理一样,网络代理,代购,代理人等一样.
分为: 主题–>代理人–>实际对象
和适配器模式一样,但代理人实现一样的方法.
场景: 大数据对象,按需要加载,延迟加载,读写分离,权责分离,控制等
大数据场景:
一个大的对象,先实例化一个轻量的对象,当需要某部分数据才实例化.
读写分离:orm类读数据库从从库实例获取数据,写数据时写入到master实例.
常用设计结构
MVC 结构
mvc是一种常用的 C/S或B/S软件工程的组织方式.
M: 模型(Model),数据和存储的封装
V: 视图(View),展现层的封装
C: 控制器(Controller),逻辑层的封装
本文作者:阿金
本文链接:http://www.hi-arkin.com/2019/04/14/PHP/design_parttern/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
扫描二维码,分享此文章