PHP设计模式之适配器模式

适配器模式将某个对象的接口适配为另一个对象所期望的接口。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
举个小例子:插座问题,两个插口只能插两个刀片的插头,三个刀片的插头就无法插拔了。那么可以做成即可插三个刀片又可以插两个刀片的插口,这就可以兼容了。

实例一:写入网络错误日志

网站错误日志系统第一版:只有一种格式的日志

demo1.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
<?php
header('Content-Type:text/html; charset=utf-8');

class ErrorObject{
private $error;

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

public function getError(){
return $this->error;
}
}

class LogToTxt{
private $errorObject;

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

public function write(){
fwrite(fopen('log.txt', 'w'), $this->errorObject->getError());
}
}

$logtotxt = new LogToTxt(new ErrorObject('404:HTTP error!'));
$logtotxt->write();

// 1.扩展的话,这个系统是否已经上线,如果上线了,
// 修改类会和其他类及客户端发生连锁反应(一个地方修改,其他地方跟着改)
// 2.如果没有上线,那么直接修改要修改的地方即可。

此版本只生成 Txt 文件格式的日志。第二版将增加一个 xml 格式的日志。

网站错误日志系统第二版:两种格式日志

demo2.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
header('Content-Type:text/html; charset=utf-8');

class ErrorObject{
private $error;
private $line;
private $text;

public function __construct($error){
$this->error = $error;
$temp = explode(':', $this->error);
$this->line = $temp[0];
$this->text = $temp[1];
}

public function getError(){
return $this->error;
}

public function getLine(){
return $this->line;
}

public function getText(){
return $this->text;
}

}

class LogToTxt{
private $errorObject;

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

public function write(){
fwrite(fopen('log.txt', 'w'), $this->errorObject->getError());
}
}

class LogToXml{
private $errorObject;

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

public function write(){
$xml = "<root>\r\n";
$xml .= '<line>'.$this->errorObject->getLine()."</line>\r\n";
$xml .= '<text>'.$this->errorObject->getText()."</text>\r\n";
$xml .= '</root>';
file_put_contents('log.xml', $xml);
}
}

$logtotxt = new LogToTxt(new ErrorObject('404:Http error!'));
$logtotxt->write();

$logtoxml = new LogToXml(new ErrorObject('404:Http error!'));
$logtoxml->write();

此版本增加了生成 xml 的功能,但是,我们必须修改 ErrorObject 类才能完成这种功能 ,这么修改违反了面向对象的原则,因为系统如果在运行中,可能会破坏客户端调用的方式 ,比如构造方法传参等等。

网站错误日志系统第三版:使用适配器模式增强扩展内容

demo3.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
header('Content-Type:text/html; charset=utf-8');

class ErrorObject{
private $error;

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

public function getError(){
return $this->error;
}
}

class LogToTxt{
private $errorObject;

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

public function write(){
fwrite(fopen('log.txt', 'w'), $this->errorObject->getError());
}
}

class ErrorXmlObject extends ErrorObject{
private $line;
private $text;

public function __construct($error){
parent::__construct($error);
$temp = explode(':', $this->getError());
$this->line = $temp[0];
$this->text = $temp[1];
}

public function getLine(){
return $this->line;
}

public function getText(){
return $this->text;
}
}

class LogToXml{
private $errorObject;

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

public function write(){
$xml = "<root>\r\n";
$xml .= '<line>'.$this->errorObject->getLine()."</line>\r\n";
$xml .= '<text>'.$this->errorObject->getText()."</text>\r\n";
$xml .= '</root>';
file_put_contents('log.xml', $xml);
}
}


$logtotxt = new LogToTxt(new ErrorObject('404:HTTP error!'));
$logtotxt->write();

$logtoxml = new LogToXml(new ErrorXmlObject('404:Http error!'));
$logtoxml->write();

此版本采用是适配器模式,用于将原来比较基础的错误输出功能通过另外一个类进行了增强以适应新的需求。

实例二:PHP操作数据库

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

demo4.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
interface DB{
public function connect($host, $user, $passwd, $dbname);
public function query($sql);
public function close();
}

class Mysql implements DB{
private $conn;

public function connect($host, $user, $passwd, $dbname){
$this->conn = mysql_connect($host, $user, $passwd);
mysql_select_db($dbname, $this->conn);
}

public function query($sql){
return mysql_query($sql, $this->conn);
}

public function close(){
mysql_close($this->conn);
}
}

class Mysqli implements DB{
private $conn;

public function connect($host, $user, $passwd, $dbname){
$this->conn = mysqli_connect($host, $user, $passwd, $dbname);
}

public function query($sql){
return mysqli_query($this->conn, $sql);
}

public function close(){
mysqli_close($this->conn);
}
}

class Pdo implements DB{
private $conn;

public function connect($host, $user, $passwd, $dbname){
$this->conn = new PDO("mysql:host=$host;dbname=$dbname", $user, $passwd);
}

public function query($sql){
return $this->conn->query($sql);
}

public function close(){
unset($this->conn);
}
}

$db = new Mysql();
$db->connect('127.0.0.1', 'root', 'password', 'db');
$db->query('select * from user');
$db->close();