# PHP设计模式
> 在软件开发过程中,经常出现的典型场景的典型解决方案,称为设计模式
>
> 是为了程序高内聚、低耦合
>
> 更深入理解面向对象
>
> 有利于开发出扩展性强的程序
## 单例模式
> 用于场景:经典例子是数据库连接(`redis,mongodb,memcache`等),通过单例模式来实现只做一次`mysql_connect()`来节约资源。
两个对象是一个的时候,才全等
1. 创建一个普通类
~~~
class Singleton {
}
$single = new Singleton();
~~~
2. 封锁`new`操作,将构造函数私有化
~~~
class Singleton {
private function __construct(){}
}
~~~
3. 留个静态方法`getInstance()`来`new`对象
~~~
class Singleton {
private function __construct(){}
public static function getInstance()
{
return new self();
}
}
~~~
4. `getInstance`先顶一个私有静态属性来保存实例,第一次保存之后,后面进行判断这个私有静态属性是否为空,否则就直接返回这个存储实例的私有静态属性,此时还有问题,额外定义一个子类继承这个类,将构造函数改为公有属性,则还是不行。
~~~
class Singleton {
private static $instance = null;
private function __construct(){}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
~~~
5. 用`final`防止继承时,被修改权限
方法前加`final`,方法不能被覆盖,类前加`final`,则类不能被继承
此时,还是有问题,使用`clone`函数时,又产生了一个对象
~~~
class Singleton {
private static $instance = null;
final private function __construct(){}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
~~~
6. 可以私有化`__clone()`函数
~~~
class Singleton {
private static $instance = null;
final private function __construct(){}
public static function getInstance()
{
// 判断有没有实例化
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
final private function __clone(){}
}
~~~
7. 优化后的就是使用`!self::$instance instanceof self`来代替上述的判断和`final`,这里前者是用来判断保存的对象不是从本身或者其派生类产生的,这样就可以避免继承之后改变构造函数公有化代来的烦恼。也减少了代码量。
~~~
<?php
class Singleton
{
private static $instance = null;
// 私有 => 防止外部进行实例化 new Singleton
private function __construct()
{
echo __METHOD__;
}
public static function getInstance()
{
// 判断保存的对象不是从本身或者其派生类产生的
if (!self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}
private function __clone()
{
// TODO: Implement __clone() method.
}
}
$instance = Singleton::getInstance();
var_dump($instance);
~~~
运行结果
~~~
Singleton::__constructobject(Singleton)#1 (0) { }
~~~
## 观察者模式
> 定义对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖与它的对象都得到通知并被自动更新
>
> PHP中提供观察者`observer`与被观察者`subject`的接口
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/23
* Time: 21:33
* 观察登录的次数
*/
class User implements SplSubject
{
public $login_num;
public $hobby;
protected $observers = null;
public function __construct($hobby)
{
$this->login_num = rand(1, 10);
$this->hobby = $hobby;
$this->observers = new SplObjectStorage();
}
public function login()
{
// 操作session...
// 发送通知
$this->notify();
}
/**
* Attach an SplObserver
* @link https://php.net/manual/en/splsubject.attach.php
* @param SplObserver $observer <p>
* The <b>SplObserver</b> to attach.
* </p>
* @return void
* @since 5.1.0
*/
public function attach(SplObserver $observer)
{
// TODO: Implement attach() method.
$this->observers->attach($observer);
}
/**
* Detach an observer
* @link https://php.net/manual/en/splsubject.detach.php
* @param SplObserver $observer <p>
* The <b>SplObserver</b> to detach.
* </p>
* @return void
* @since 5.1.0
*/
public function detach(SplObserver $observer)
{
// TODO: Implement detach() method.
$this->observers->detach($observer);
}
/**
* Notify an observer
* @link https://php.net/manual/en/splsubject.notify.php
* @return void
* @since 5.1.0
*/
public function notify()
{
// TODO: Implement notify() method.
$this->observers->rewind();
while ($this->observers->valid()) {
$observer = $this->observers->current();
$observer->update($this);
$this->observers->next();
}
}
}
class Security implements SplObserver
{
/**
* Receive update from subject
* @link https://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject <p>
* The <b>SplSubject</b> notifying the observer of an update.
* </p>
* @return void
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
if ($subject->login_num >= 3) {
echo '这是第'.$subject->login_num.'次安全登录';
} else {
echo '这是第'.$subject->login_num.'次登录,异常';
}
}
}
class Ad implements SplObserver
{
/**
* Receive update from subject
* @link https://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject <p>
* The <b>SplSubject</b> notifying the observer of an update.
* </p>
* @return void
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
if ($subject->hobby == 'sports') {
echo '台球锦标赛预定';
} else {
echo '好好学习,天天向上';
}
}
}
$user = new User('study');
$user->attach(new Security());
$user->attach(new Ad());
$user->login();
~~~
## 简单工厂模式
> 这里拿连接多种数据库来当作案例
>
> 数据库类一般会有一个连接的接口 ,这是每个数据库连接的共同的接口`Db`
>
> 按照正常面向接口开发来说,我们不知道各种数据库类的具体内部细节,只知道,数据库类都实现了`Db`接口
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/23
* Time: 18:03
*/
// 共同接口
interface db
{
function conn();
}
// 服务端开发(不知道将会被谁调用)
class DbMysql implements db
{
function conn()
{
// TODO: Implement conn() method.
echo '连接上了mysql';
}
}
class DbSqlite implements db
{
function conn()
{
// TODO: Implement conn() method.
echo '连接上了sqlite';
}
}
$db = new DbMysql();
$db->conn(); // 连接上了mysql
~~~
> 此时我们还是知道服务端有哪些数据库类,对于面向接口编程来说,这里我们要开始认为,客户端现在不知道服务端有什么数据库类!的情况,只知道对方开放了一个`SimpleFactory::createDB()`方法,并且方法允许传递数据库名称,由此衍生出简单工厂模式!
~~~
// ...上述代码还是必须的内容,这里省略
class SimpleFactory
{
public static function createDB($type)
{
if ($type == 'mysql') {
return new DbMysql();
} else if ($type == 'sqlite') {
return new DbSqlite();
} else {
return new Exception("Error db type", 1);
}
}
}
$mysql = SimpleFactory::createDB('mysql');
$mysql->conn();
$sqlite = SimpleFactory::createDB('sqlite');
$sqlite->conn();
~~~
> 此时,如果新增`Oracle`类型咋办,一方面,可以直接在上述`if/else`里再来一个`else if`,这样在`PHP`里修改个5、6条还行,但是对于静态类语言,他们是需要重新编译的,所以会很浪费时间。在`OOP`重,重要的开闭原则,对于修改是封闭的,对于扩展时开放的。这就是要我们将工厂也进行扩展,不要轻易去修改内容。
所以我们新增了一个工厂的接口提供了一个`createDB`的方法
~~~
interface Factory
{
public function createDB();
}
class MySQLFactory implements Factory
{
public function createDB()
{
// TODO: Implement createDB() method.
return new DbMysql();
}
}
class SqliteFactory implements Factory
{
public function createDB()
{
// TODO: Implement createDB() method.
return new DbSqlite();
}
}
// 新增Oracle的方式就变了
class Oracle implements db
{
function conn()
{
// TODO: Implement conn() method.
echo '连接上了Oracle';
}
}
class OracleFactory implements Factory
{
public function createDB()
{
// TODO: Implement createDB() method.
return new Oracle();
}
}
~~~
## 职责链模式(chain of responsibility)
> 每个对象,存储着对自己上级的引用,如果自己处理不了,就交给上一级
拿举报信息提交给能处理的人的案例来说
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>责任链模式</title>
</head>
<body>
<h1>责任链模式</h1>
<form action="action.php" method="post">
<select name="jubao" id="">
<option value="1">粗口</option>
<option value="2">黄赌毒</option>
<option value="3">分裂国家</option>
</select>
<button type="submit">提交</button>
</form>
</body>
</html>
~~~
`action.php`
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/23
* Time: 22:13
*/
header('content-type: text/html;charset=utf-8');
// 版主类
class Board
{
// 权限
public $power = 1;
// 上级
protected $top = 'Admin';
public function process($level)
{
// 如果当前的处理者权限不够,就引导去它的上级去处理
if ($level <= $this->power) {
echo '版主删帖子';
} else {
$top = new $this->top;
$top->process($level);
}
}
}
class Admin
{
protected $power = 2;
protected $top = 'Police';
public function process($level)
{
if ($level <= $this->power) {
echo '管理员封锁账号';
} else {
$top = new $this->top;
$top->process($level);
}
}
}
class Police
{
protected $power;
protected $top = null;
public function process($level)
{
echo '警察抓起来';
}
}
/*
* 这里还是偏向面向过程的解决方式
if ($level == 1) {
$processer = new Board();
$processer->process();
} else if ($level == 2) {
$processer = new Admin();
$processer->process();
} else {
$processer = new Police();
$processer->process();
}
*/
// 责任链模式来处理举报问题
// 接收举报等级
$level = $_POST['jubao'] + 0;
$judge = new Board();
$judge->process($level);
~~~
## 策略模式
和工厂模式类似
区别:应用场景进行区别
> 工厂中,根据不同的情况返回过来的对象,就干它本身的方法
>
> 策略中,根据不同的情况下返回过来的对象,赋给父类,不必去管返回来的对象。
***案例:***
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>策略模式</title>
</head>
<body>
<form action="calc.php" method="post">
<input type="text" name="num1">
<select name="op" id="">
<option value="add">+</option>
<option value="sub">-</option>
<option value="mul">*</option>
<option value="div">/</option>
</select>
<input type="text" name="num2">
<p>
<input type="submit" value="计算">
</p>
</form>
</body>
</html>
~~~
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/24
* Time: 10:31
*/
interface Math
{
public function calc($num1, $num2);
}
class MathAdd implements Math
{
public function calc($num1, $num2)
{
// TODO: Implement calc() method.
return $num1 + $num2;
}
}
class MathSub implements Math
{
public function calc($num1, $num2)
{
// TODO: Implement calc() method.
return $num1 - $num2;
}
}
class MathMul implements Math
{
public function calc($num1, $num2)
{
// TODO: Implement calc() method.
return $num1 * $num2;
}
}
class MathDiv implements Math
{
public function calc($num1, $num2)
{
// TODO: Implement calc() method.
return $num1 / $num2;
}
}
// 一般思路,根据op,制造不同对象,并且调用
// 这里我们封装一个虚拟计算器
class CMath
{
protected $calc = null;
public function __construct($type)
{
$calc = 'Math'.$type;
$this->calc = new $calc();
}
public function calc($num1, $num2)
{
return $this->calc->calc($num1, $num2);
}
}
$type = $_POST['op'];
$cmath = new CMath($type);
echo $cmath->calc($_POST['num1'], $_POST['num2']);
~~~
## 装饰器模式(decorator)
> 子类先获取父类的对象,然后进行修饰
>
> 可以动态地添加修改类地功能
>
> 一个类提供了一项功能,如果要在修改并添加额外地功能,传统地编程模式,需要写一个子类继承它,并重新实现类地方法。
>
> 使用装饰器模式 ,仅需在运行时添加一个装饰器对象即可实现,可以实现最大地灵活性。
**装饰器实现文章的编辑**
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/24
* Time: 11:46
*/
class BaseArt
{
protected $content;
protected $art = null;
public function __construct($content)
{
$this->content = $content;
}
public function decorator()
{
return $this->content;
}
}
// 编辑文章摘要
class EditorArt extends BaseArt
{
public function __construct(BaseArt $art)
{
$this->art = $art;
$this->decorator();
}
public function decorator()
{
return $this->content = $this->art->decorator().'小编摘要';
}
}
class SeoArt extends BaseArt
{
public function __construct(BaseArt $art)
{
$this->art = $art;
$this->decorator();
}
public function decorator()
{
return $this->content = $this->art->decorator().'seo关键词';
}
}
$art = new SeoArt(new EditorArt(new BaseArt('好好学习')));
echo $art->decorator();
// 此时SeoArt和EditorArt属于兄弟关系
~~~
## 适配器模式
> 没搞懂有啥意义。。。
暂时给个示例代码
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/24
* Time: 15:49
*/
// 适配器模式
// 服务器端代码
class Tianqi
{
public static function show()
{
$today = [
'tep' => 28,
'wind' => 7,
'sun' => 'sunny',
];
return serialize($today);
}
}
// 增加一个适配器
class AdapterTianqi extends Tianqi
{
public static function show()
{
$today = parent::show();
$today = unserialize($today);
$today = json_encode($today);
return $today;
}
}
// 客户端调用
$tq = unserialize(Tianqi::show());
echo '温度:', $tq['tep'], '<br>';
echo '风力:', $tq['wind'], '<br>';
echo '太阳:', $tq['sun'], '<br>';
// java、python再来调用,通过适配器调用
$tq1 = AdapterTianqi::show();
$tq1 = json_decode($tq1);
echo '温度:', $tq1->tep, '<br>';
echo '风力:', $tq1->wind, '<br>';
echo '太阳:', $tq1->sun, '<br>';
~~~
## 桥接模式
消息发送案例
消息分为站内消息、email发送、手机短信发送
消息也分为普通信息、紧急信息、特急信息
传统方式:M \* N
桥接模式后:M + N可以任意两两组合
![传统模式与桥接模式的坐标轴化](https://img.kancloud.cn/ab/f0/abf01a284ce0d67647183ae42ed3da80_948x510.png)
传统方式代码:
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/24
* Time: 16:08
*/
// 桥接模式
// 论坛给用户发信息,可以实站内短信,email,手机
interface Message
{
public function send($to, $content);
}
class Zn implements Message
{
public function send($to, $content)
{
// TODO: Implement send() method.
echo '站内信给', $to, '内容:', $content;
}
}
class Email implements Message
{
public function send($to, $content)
{
// TODO: Implement send() method.
echo '邮箱给', $to, '内容:', $content;
}
}
class Sms implements Message
{
public function send($to, $content)
{
// TODO: Implement send() method.
echo '短信给', $to, '内容:', $content;
}
}
/*
// 内容也分普通,加急,特急
class ZnCommon extends Zn{}
class ZnWarn extends Zn{}
class ZnDanger extends Zn{}
// 邮箱也分以上等级 。。。
*/
// 信息发送方式是一个变化因素
// 信息的紧急程度也是变化因素
// 不修改父类,只考虑2个因素的组合,不停产生新类,肯定是不行的
~~~
桥接模式代码
~~~
<?php
/**
* Created By basic
* Author: Virus
* Date: 2020/5/24
* Time: 16:31
*/
abstract class Info
{
protected $send = null;
public function __construct($send)
{
$this->send = $send;
}
public abstract function msg($content);
public function send($to, $content)
{
$content = $this->msg($content);
$this->send->send($to, $content);
}
}
class Zn
{
public function send($to, $content)
{
echo '站内给', $to, '内容是:', $content;
}
}
class Email
{
public function send($to, $content)
{
echo 'Email给', $to, '内容是:', $content;
}
}
class Sms
{
public function send($to, $content)
{
echo '短信给', $to, '内容是:', $content;
}
}
class CommonInfo extends Info
{
public function msg($content)
{
return '普通'.$content;
}
}
class WarnInfo extends Info
{
public function msg($content)
{
return '紧急'.$content;
}
}
class DangerInfo extends Info
{
public function msg($content)
{
return '特急'.$content;
}
}
// 用站内发普通信息
$commonInfo = new CommonInfo(new Zn());
$commonInfo->send('小明', '吃饭了');
echo '<br/>';
$sms = new DangerInfo(new Sms());
$sms->send('小刚', '失火了,快回家');
~~~
- PHP获取客户端浏览器信息和版本
- PHP获取客户端操作系统信息
- 无限级分类
- git使用
- 权限检测思路
- Vue学习
- 遇到的一些问题
- PHP的编码思维和技巧
- mysql复习
- tp5
- ThinkPHP5.x 公共函数
- TP5登录注册
- TP5使用模板继承
- ThinkPHP5.1 清除缓存
- thinkphp5实现安装程序
- 安全
- tp中实现跨域代码
- ThinkPHP5.1配合pjax实现菜单栏无刷新跳转
- 获取数据库版本和数据库大小
- 模型的基本CURD操作
- 商品spu
- 全局异常处理类
- ExceptionHandler
- BaseException
- PHP函数之error_reporting(E_ALL ^ E_NOTICE)详细说明
- 微信小程序
- wx:for
- tp6
- 分离的一些模块
- session开启
- Spring
- 依赖注入
- 数据结构
- 二叉树
- js获取地址栏变量
- PHP设计模式
- 面向对象
- PHP1
- PHP性能优化
- Java学习
- static关键字
- 多态
- 接口、阶乘
- 大佬给的面试题
- 访问量为5000万的博客系统设计
- PHP可变参数
- Nginx的配置案例
- 求数组中的最大值,并返回数组索引
- PHP面试方向
- PHP数组工具类ArrUtil
- 字符串工具类StrUtil
- PHP使用curl发送请求
- mysql
- PHP上传base64图片处理函数
- webstorm小程序常用配置
- 邮箱正则表达式
- leetcode mysql记录
- 函数库