设计模式

创建性模式

单例模式(Singleton)

  1. 要求:只有一个实例,自行创建实例并对外提供这个实例
  2. 实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Mysql {
    private static $conn;

    private function __construct() {
    self::$conn = new PDO("mysql:dbname=testdb;host=127.0.0.1", "root", "root");
    }
    public static function getInstance() {
    if(!(self::$conn instanceof self)){
    self::$conn = self;
    }
    return self::$conn;
    }
    //防止对象被复制
    public function __clone(){
    trigger_error('Clone is not allowed !');
    }
    //防止反序列化后创建对象
    public function __wakeup() {
    trigger_error('Unserialized is not allowed !');
    }
    }

    $mysql = Mysql::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
    <?php
    class SimpleFactory {
    public function create($name) {
    return new $name();
    }
    }

    abstract class Vehicle {
    abstract public function driveTo($dest);
    }

    class Bicycle extends Vehicle {
    public function driveTo($dest) {
    return '骑自行车去:'.$dest;
    }
    }

    class Car extends Vehicle {
    public function driveTo($dest) {
    return '开汽车去:'.$dest;
    }
    }

    //应用
    $factory = new SimpleFactory();
    $vehicle = $factory -> create('Bicycle');
    print($vehicle -> driveTo('北京'));

结构性模式

适配器模式

接口的应用

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
interface Database {
public function connect();
public function query();
public function close();
}

class DBMysql implements Database {
public function connect() {
var_dump(__METHOD__);
}
public function query() {
var_dump(__METHOD__);
}
public function close() {
var_dump(__METHOD__);
}
}

class DBPdo implements Database {
public function connect() {
var_dump(__METHOD__);
}
public function query() {
var_dump(__METHOD__);
}
public function close() {
var_dump(__METHOD__);
}
}

$database = new DBMysql();//切换数据库只要改这一行就行了,因为后面的都是标准接口方法,不管哪个数据库都一样
$database->connect();
$database->query();
$database->close();

问题
有的三方接口并没有按照标准接口实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
//第三方数据库类
class Oracle {
public function oracleConnect(){
//Oracle 的逻辑
};
public function oracleQuery(){
//Oracle 的逻辑
};
public function oracleClose(){
//Oracle 的逻辑
};
}

这时候适配器就派上用场了,用这个适配器将异类的方法转换为接口标准方法
适配器模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Adapter implements Database {  
private $adaptee;
function __construct($adaptee) {
$this->adaptee = $adaptee;
}
//这里把异类的方法转换成了 接口标准方法,下同
public function connect(){
$this->adaptee->oracleConnect();
};
public function query(){
$this->adaptee->oracleQuery();
};
public function close(){
$this->adaptee->oracleClose();
};
}

应用:

1
2
3
4
5
6
$adaptee = new Oracle();  
$adapter = new Adapter($adaptee);//只要改这个类就行了,后面的都可以不用改;
$database = $adapter;
$database->connect();
$database->query();
$database->close();

装饰器模式

解决问题:为某个方法装饰可以自由删除或增加的逻辑(如果遇到)

场景:比如有一个煮咖啡的程序,默认是纯咖啡,如何方便的选择是否加糖、加奶、加巧克力呢?

1
2
3
4
5
6
7
//主咖啡程序
class plainCoffee {
public function makeCoffee(){
$this->addCoffee();
}
public function addCoffee(){}
}

一般逻辑:加糖

1
2
3
4
5
6
7
class sweetCoffee extends plainCoffee {
public function makeCoffee(){
$this->addCoffee();
$this->addSugar();
}
public function addSugar(){}
}

弊端:如果这时候加奶、加巧克力就需要继续继承重写makeCoffee

装饰器方式:在addCoffee前后增加逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//调整煮咖啡程序
class plainCoffee {
private $decorators;
public function addDecorator($decorator){
$this->decorators[] = $decorator;
}
private function before(){
foreach($this->decorators as $decorator){
$decorator->before()
}
}
private function after(){
foreach($this->decorators as $decorator){
$decorator->after()
}
}
public function makeCoffee(){
$this->before();
$this->addCoffee();
$this->after();
}
public function addCoffee(){}
}

编写装饰器

1
2
3
4
5
6
7
8
class sweetCoffeeDecorator{
public function before(){
}
public function after(){
$this->addSugar();
}
public function addSugar(){}
}

应用:加糖加奶只需要增加装饰器即可,不需要只需注释掉。

1
2
3
4
$coffee = new plainCoffee();
$coffee->addDecorator(new sweetCoffeeDecorator());
$coffee->addDecorator(new milkCoffeeDecorator());
$coffee->makeCoffee();

总结:当你extends用过后又遇到需要再次extends的情况时,不妨考虑一下装饰器模式

依赖注入

原理:把一个类不可能更换的部分 和 可更换的部分分离开来,通过注入的方式来使用,从而达到解耦的目的。
一个数据库连接类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//数据库连接
class Mysql{
private $host;
private $port;
private $username;
private $password;
private $db_name;
public function __construct(){
$this->host = '127.0.0.1';
$this->port = 22;
$this->username = 'root';
$this->password = '';
$this->db_name = 'my_db';
}
public function connect(){
return mysqli_connect($this->host,$this->username ,$this->password,$this->db_name,$this->port);
}
}

//使用:
$db = new Mysql();
$con = $db->connect();

依赖注入:将可变部分(配置)提取出来抽象为一个类,然后在注入到原类中,这样就完成了配置文件和连接逻辑的分离。

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
//配置类
class MysqlConfiguration {
private $host;
private $port;
private $username;
private $password;
private $db_name;

public function __construct($host, $port, $username, $password, $db_name) {
$this->host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $password;
$this->db_name = $db_name;
}
public function getHost() {
return $this->host;
}
public function getPort() {
return $this->port;
}
public function getUsername(){
return $this->username;
}
public function getPassword() {
return $this->password;
}
public function getDbName() {
return $this->db_name;
}
}

//Mysql变更为:
class Mysql {
private $configuration;
public function __construct(MysqlConfiguration $config) {
$this->configuration = $config;
}
public function connect() {
return mysqli_connect($this->configuration->getHost(), $this->configuration->getUsername(), $this->configuration->getPassword, $this->configuration->getDbName(), $this->configuration->getPort());
}
}

使用:$config是注入Mysql的,这就是所谓的依赖注入。

1
2
3
$config = new MysqlConfiguration('127.0.0.1','root','','my_db',22);
$db = new Mysql($config);
$con = $db->connect();

链式操作

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
<?php
class Employee
{
public $name;
public $surName;
public $salary;
public function setName($name)
{
$this->name = $name;
return $this;
}
public function setSurname($surname)
{
$this->surName = $surname;
return $this;
}
public function setSalary($salary)
{
$this->salary = $salary;
return $this;
}
public function __toString()
{
$employeeInfo = 'Name: ' . $this->name . PHP_EOL;
$employeeInfo .= 'Surname: ' . $this->surName . PHP_EOL;
$employeeInfo .= 'Salary: ' . $this->salary . PHP_EOL;
return $employeeInfo;
}
}
//链式操作的效果
$employee = (new Employee())
->setName('Tom')
->setSurname('Smith')
->setSalary('100');
echo $employee;
# 输出结果
# Name: Tom
# Surname: Smith
# Salary: 100

行为性模式

观察者模式

需求:有一个listener和handler,当listener监听到一个事件发生,多个handler自动处理对应的逻辑
原理

  • 声明抽象事件发生者类(EventGenerator),负责添加观察者和逐一调用观察者的update方法进行通知。
  • 声明观察者接口类(Observer),提供抽象方法update,所有的观察者都实现这个接口
  • 具体的事件继承EventGenerator类,然后调用addObserver添加观察者,调用trigger进行触发。

实现

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
//声明一个抽象的事件发生者基类
abstract class EventGenerator{
private $observers = array();
//添加观察者方法
function addobserver(Observer $observer)
{
$this->observers[] = $observer;
}
//对每个添加的观察者进行事件通知
function notify()
{
//对每个观察者逐个去更新
foreach($this->observers as $observer)
{
$observer->update();
}
}
}
//声明一个观察者接口
interface observer {
function update($event_info = null);
}

//声明具体事件类,继承了主事件
class Event extends EventGenerator
{
function trigger()
{
echo "Event<br/>";
$this->notify();
}
}

//声明多个观察者
class Observer1 implements observer
{
function update($event_info = null)
{
echo "逻辑1<br/>";
}
}
class Observer2 implements observer
{
function update($event_info = null)
{
echo "逻辑2<br/>";
}
}

应用

1
2
3
4
5
//Event基类里的foreach,可以实现一个事件对应多个观察者
$event = new Event;
$event->addObserver(new Observer1);
$event->addObserver(new Observer2);
$event->trigger();

策略模式

需求 :针对大量ifelse判断上下文的环境所作出的策略。
原理:提供一个策略接口(Strategy),其中定义待实现方法,其他策略实现这个接口,业务逻辑中只调用接口中定义的方法。
举例:商城的首页,男的进来看男性商品,女的进来看女性商品,不男不女…以此类推,各种条件下用不同策略展示不同商品
实现:

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
//showStrategy.php 展示策略接口
interface showStrategy{
public function showCategory();
}

//maleShowStrategy.php 男性用户展示策略
class maleShowStrategy implements showStrategy { // 具体策略A
public function showCategory(){
echo '展示男性商品目录';
}
}
//femaleShowStrategy.php 女性用户展示策略
class femaleShowStrategy implements showStrategy { // 具体策略B
public function showCategory(){
echo '展示女性商品目录';
}
}
//page.php 展示页面
class Page{
private $_strategy;
public function __construct(Strategy $strategy) {
$this->_strategy = $strategy;
}
public function showPage() {
$this->_strategy->showCategory();
}
}

使用

1
2
3
4
5
6
7
8
if(isset($_GET['male'])){
$strategy = new maleShowStrategy();
}elseif(isset($_GET['female'])){
$strategy = new femaleShowStrategy();
}
//注意看这里上下,Page类不再依赖一种具体的策略,而是只需要绑定一个抽象的接口,这就是传说中的控制反转(IOC)。
$question = new Page($strategy);
$question->showPage();

总结
仔细看上面的例子,不复杂,我们发现有2个好处:

它把if else 抽离出来了,不需要在每个类里都写if else;
它成功的实现了控制反转,Page类里没有具体的依赖策略,这样我们就可以随时添加和删除 不同的策略。

模板方法

这是最常见的设计模式之一,其实质就是父类提供一系列模板方法,有的实现了逻辑,有的只是一个接口。而子类继承大部分共有方法,同时对接口方法进行不同的实现,从而完成对父类模板的个性化改造,起到一对多的解耦目的。

可以说PHP的抽象类就是为了实现这个设计模式而推出的功能,在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
//Journey.php 模板类
abstract class Journey
{
private $thingsToDo = [];
//final关键字的作用是不让这个方法被子类覆盖
final public function takeATrip()
{
$this->thingsToDo[] = $this->buyAFlight();
$this->thingsToDo[] = $this->takePlane();
$this->thingsToDo[] = $this->enjoyVacation();
$buyGift = $this->buyGift();
if ($buyGift !== null) {
$this->thingsToDo[] = $buyGift;
}
$this->thingsToDo[] = $this->takePlane();
}
//子类必须实现的抽象方法
abstract protected function enjoyVacation(): string;
protected function buyGift()
{
return null;
}
private function buyAFlight(): string
{
return 'Buy a flight ticket';
}
private function takePlane(): string
{
return 'Taking the plane';
}
//把所有旅行中干过的事情列出来
public function getThingsToDo(): array
{
return $this->thingsToDo;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//BeachJourney.php子类一
class BeachJourney extends Journey
{
protected function enjoyVacation(): string
{
return "Swimming and sun-bathing";
}
}
//BeachJourney.php子类二
class CityJourney extends Journey
{
protected function enjoyVacation(): string
{
return "Eat, drink, take photos and sleep";
}
//覆盖父类已有方法
protected function buyGift(): string
{
return "Buy a gift";
}
}

参考: