PHP常用的设计模式

Posted by jintang on 2014-09-22

1. 工厂模式

工厂方法或者类生成对象,而不是在代码中直接new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
class Database
{
}

class Factory
{
public static function createDatabase()
{
return new Database();
}
}

$db = Factory::createDatabase();
?>

2. 单例模式

使某个类的对象仅允许创建一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class Singleton
{
protected static $obj;

/**
* 把构造函数设置成私有,防止使用new来实例化对象
* Singleton constructor.
*/
private function __construct()
{
}

public static function getInstance()
{
if (!(self::$obj instanceof Singleton)) {
self::$obj = new self();
}
return self::$obj;
}
}

$single = Singleton::getInstance();
?>

3. 注册树模式

全局共享和交换对象

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
<?php

class Register
{
protected static $objects;

public function set($alias, $object)
{
self::$objects[$alias] = $object;
}

public function get($alias)
{
return self::$objects[$alias];
}

public function _unset($alias)
{
unset(self::$objects[$alias]);
}
}

// 初始化
$single = Singleton::getInstance();
Register::set('s', $single);

// 在其他任何地方使用single
$s = Register::get('s');
?>

4. 适配器模式

适配器模式,可以将截然不同的函数接口封装成统一的API

如:mysql,mysqli,pdo3,类似cache适配器的场景,缓存有memcache,redis,file

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
<?php

// 首先实现一个接口,约定方法
interface IDatabase
{
public function connect($host, $user, $pass, $dbname);
public function query($sql);
public function close();
}

// 若干种扩展
class MySQL implements IDatabase
{
// 实现connect方法
public function connect($host, $user, $pass, $dbname)
{
}

public function query($sql)
{
}

public function close()
{
}
}

class MySQLi implements IDatabase
{
// 实现connect方法
public function connect($host, $user, $pass, $dbname)
{
}

public function query($sql)
{
}

public function close()
{
}
}

// 开发者可以选择其中一种扩展
$db = new MySQL();
$db = new MySQLi();
$db->connect('localhost', 'root', '123456', 'test');
$db->query('show databases');
$db->close();
?>

5. 策略模式

  1. 将一组特定的行为和算法封装成类,以适应某些特定的上下文环境,这种模式就是策略模式。
  2. 场景:假如一个点上网站系统,针对男女性用户要各自跳转到不同的商品类目,并且所有广告位展示不同的广告
  3. 应用:实现Ioc、依赖倒置和控制反转

策略定义:UserStrategy.php

1
2
3
4
5
6
7
8
<?php
// 策略接口
interface UserStrategy
{
public function showAd();
public function showCategory();
}
?>

男性策略:MaleUserStrategy.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class MaleUserStrategy implements UserStrategy
{
public function showAd()
{
// TODO: Implement showAd() method.
echo "2017款男装";
}

public function showCategory()
{
// TODO: Implement showCategory() method.
echo "男装";
}
}
?>

女性策略:FemaleUserStrategy.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class FemaleUserStrategy implements UserStrategy
{
public function showAd()
{
// TODO: Implement showAd() method.
echo "2017款女装";
}

public function showCategory()
{
// TODO: Implement showCategory() method.
echo "女装";
}
}
?>

应用:index.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
<?php
// 选择策略
$is_male = $_GET['male'];
if ($is_male) {
$strategy = new MaleUserStrategy();
} else {
$strategy = new FemaleUserStrategy();
}

class Page
{
protected $strategy;

public function index()
{
echo "AD:";
echo $this->strategy->showAd();
echo "Category:";
echo $this->strategy->showCategory();
}

public function setStrategy(UserStrategy $strategy)
{
$this->strategy = $strategy;
return $this;
}
}

$page = new Page();
$page->setStrategy($strategy)->index();
?>

6. 数据对象映射模式

  1. 数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射到对数据存储的操作。
  2. 比如实现一个ORM类,将复杂的sql语句映射成对象属性的操作

数据表user:

1
2
3
4
5
6
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL COMMENT 'ID',
`name` varchar(32) NOT NULL DEFAULT '' COMMENT '姓名',
`mobile` varchar(11) NOT NULL DEFAULT '' COMMENT '电话号码',
`regtime` int(11) NOT NULL DEFAULT '0' COMMENT '注册时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

实例代码, User.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class User
{
public $id;
public $name;
public $mobile;
public $regtime;

protected $db;

public function __construct($id)
{
// 根据id查询具体的某一行,然后赋值给对应的成员对象
}

public function __destruct()
{
// 根据成员变量,更新数据库
}
}

7. 观察者模式

  1. 观察者模式,当以个对象发生改变时,依赖他的对象全部会收到通知,并自动更新
  2. 场景:一个事件发生后,要执行一连串的更新操作。传统的编程方式,就是在事件的代码之后直接加入处理逻辑。当更新的逻辑增加之后,代码会变得难以维护。这种方式是耦合的,侵入式的,增加新的逻辑需要修改事件主体代码。
  3. 观察者模式实现了低耦合,非侵入式的通知和更新机制。

观察者Observer:

1
2
3
4
5
6
<?php
interface Observer
{
public funciton update($event_info = null);
}
?>

事件生产者EventGenerator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

abstract class EventGenerator
{
private $observers = [];
public function addObserver(Observer $observer)
{
$this->observers[] = $observer;
}

public function notify()
{
if (count($this->observers) > 0) {
foreach ($this->observers as $observer) {
$observer->update();
}
}
}
}
?>

应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

class Event extends EventGenerator
{
public function trigger()
{
echo "触发逻辑 \n";
// 通知所有观察者做更新操作
$this->notify();
}
}

class Observer1 implement Observer
{
public function update($event_info = null)
{
echo "我是观察者1,我收到更新动作通知,我要做些操作";
}
}

$event = new Event;
$event->addObserver(new Observer1);
$event->trigger();
?>

8. 原型模式

  1. 与工厂模式作用类似,都是用来创建对象
  2. 与工厂模式的实现不用,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象,这样就免去了类创建时重复的初始化操作
  3. 原型模式适合于大对象的创建,创建一个大对象要很大的开销,如果每次new就会消耗很大,原型模式仅需要内存拷贝即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 

# 假设有这么一个大类 Prototype

$prototype = new Prototype; // 实例化一个对象
$prototype->init(); // 初始化

$obj1 = clone $prototype;
$obj1->dosomething();

$obj2 = clone $prototype;
$obj2->dosomething();
?>

9. 装饰器模式

  1. 装饰器模式(Decorator),可以动态地添加修改类的功能
  2. 一个类提供一项功能,如果要在修改并添加额外的功能,传统的编程模式,需要写一个子类继承它,并重新实现类的方法
  3. 使用装饰器模式,仅需要在运行时添加一个装饰器对象即可实现,可以实现最大的灵活性。

某一个功能类,如画一条线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class DrawLine
{
private $char = '';
private $long = 0;

public function init($char = '_', $long = 10)
{
$this->char = $char;
$this->long = $long;
}

public function do()
{
echo str_repeat($this->char, $this->long);
}
}
?>

以上类,想要修改线的样式,必须修改代码,首先必须给他添加具备使用装饰器的能力.

声明一个装饰器接口:

1
2
3
4
5
6
7
8
9
<?php

interface DrawDecorator
{
public funciton beforeDraw();
public function afterDraw();
}

?>

具备使用装饰器能力的画图类

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
<?php

abstract draw
{
protected $decorators = [];

public function addDecorator(DrawDecorator $decorator)
{
$this->decorators[] = $decorator;
}

public function beforeDraw()
{
if (count($this->decorators) > 0) {
foreach ($this->decorators as $decorator) {
$decorator->beforeDraw();
}
}
}

public function afterDraw()
{
$decorators = array_reverse($this->decorators);
if (count($decorators) > 0) {
foreach ($decorators as $dec) {
$dec->afterDraw();
}
}
}
}

# 刚刚的画线类,继承draw
class DrawLine extends draw
{
private $char = '';
private $long = 0;

public function init($char = '_', $long = 10)
{
$this->char = $char;
$this->long = $long;
}

public function do()
{
$this->beforeDraw(); // 执行画线前动作
echo str_repeat($this->char, $this->long);
$this->afterDraw(); // 执行画线后动作
}
}
?>

应用:

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
<?php
# 实现一个装饰器
class ColorDrawDecorator implements DrawDecorator
{
private $color;
public function __constructor($color)
{
$this->color = $color;
}

public function beforeDraw()
{
echo "<div style='color:{$this->color}'>";
}

public function afterDraw()
{
echo "</div>";
}
}

$line = new DrawLine;
$line->init('-', 20);

// 实例化颜色装饰器,并且添加装饰器到line
$line->addDecorator(new ColorDrawDecoration('red'));

$line->do();

10. 迭代器模式

  1. 迭代器模式,在不需要了解内部实现的前提下,遍历一个聚合对象的内部元素
  2. 相对于传统的编程模式,迭代器可以隐藏遍历元素的所需的操作。
  3. PHP中使用迭代器,只要实现接口Iterator

查询所有用户:

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
<?php
class AllUser implements \Iterator
{
protected $ids;
protected $index;
protected $data;

public function __construct()
{
$db = Factory::getDatabase();
$result = $db->query("SELECT id FROM user", MYSQLI_ASSOC);
$this->ids = $result->fetch_all(MYSQLI_ASSOC);
}

public function next()
{
$this->index++;
}

public function valid()
{
return $this->index < count($this->ids);
}

public function rewind()
{
$this->index = 0;
}

public function key()
{
return $this->index;
}

public function current()
{
$id = $this->ids[$this->index]['id'];
return Factory::getUser($id);
}
}

11. 代理模式

  1. 在客户端与实体之间建立一个代理对象(proxy),客户端对实体进行操作全部委派给代理对象,隐藏实体的具体实现细节。
  2. Proxy还可以与业务代码分离,部署到另一个的服务器。业务代码中通过RPC来委派任务。

12. 配置化

PHP中使用ArrayAccess实现配置文件的加载

Config.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
<?php
class Config implements \ArrayAccess
{
protected $path;
protected $configs = [];

public function __construct($path)
{
$this->path = $path;
}

public function offsetExists($offset)
{
// TODO: Implement offsetExists() method.
}

public function offsetGet($offset)
{
if (empty($this->configs[$offset])) {
$file_path = $this->path . '/' . $offset . '.php';
$config = require $file_path;
$this->configs[$offset] = $config;
}
return $this->configs[$offset];
}

public function offsetSet($offset, $value)
{
// TODO: Implement offsetSet() method.
}

public function offsetUnset($offset)
{
// TODO: Implement offsetUnset() method.
}
}

有配置文件controller.php:

1
2
3
4
5
6
7
8
9
10
<?php
# Config/controller.php
return [
'home' => [
'decorator' => [
'Imooc\Decorator\Template'
]
],
'default' => 'hello world'
];

应用:
1
2
3
<?php
$config = new Config(__DIR__ . '/Config/controller.php');
var_dump($config['controller']);