适配器模式

目的

将一个类的接口转换成一个兼容的接口。当使用类的原接口的时候,适配器通过向客户端提供一个接口,可以让原本不兼容的类一起工作。

使用场景

  • 数据库客户端适配器
  • 使用多个不同的 WebService 和适配器对数据进行统一处理,以保证所有的结果都一样

UML 类图

适配器模式类图

代码

Book.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Adapter;

interface Book
{
public function turnPage();

public function open();

public function getPage(): int;
}

PaperBook.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Adapter;

class PaperBook implements Book
{
private int $page;

public function open()
{
$this->page = 1;
}

public function turnPage()
{
$this->page++;
}

public function getPage(): int
{
return $this->page;
}
}

EBook.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Adapter;

interface EBook
{
public function unlock();

public function pressNext();

/**
* 返回由当前页和总页码组成的数组
* 如 [10, 100] 表示当前页是第 10 页,总共 100 页
* @return int[]
*/
public function getPage(): array;
}

EBookAdapter.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
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Adapter;

/**
* 这里是一个适配器类,注意,EBookAdapter 实现的是 `Book` 接口
* 因此,你无法改变正在使用 `Book` 接口的客户端代码
*/
class EBookAdapter implements Book
{
protected EBook $eBook;

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

/**
* 该类可以将一个接口正确地转换成另一个接口
*/
public function open()
{
$this->eBook->unlock();
}

public function turnPage()
{
$this->eBook->pressNext();
}

/**
* 注意这里的适配行为:EBook::getPage() 方法返回的是由 2 个整数组成的数组
* 但是 Book::getPage() 仅返回当前页码,因此,这里就是需要适配的地方
*/
public function getPage(): int
{
return $this->eBook->getPage()[0];
}
}

Kindle.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 declare(strict_types = 1);

namespace DesignPatterns\Structural\Adapter;

/**
* 这是适配器类。在实际生产环境中,这个类可能来自第三方,例如 vendor 文件夹下的代码
* 请注意,适配器类使用了另一种命名方案,它的具体实现以另一种方式做了类似的事情
*/
class Kindle implements EBook
{
private int $page = 1;
private int $totalPages = 100;

public function pressNext()
{
$this->page++;
}

public function unlock()
{
}

/**
* 返回当前页和总页码,如 [10, 100]
* @return int[]
*/
public function getPage(): array
{
return [$this->page, $this->totalPages];
}
}

测试

Tests/AdapterTest.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 declare(strict_types = 1);

namespace DesignPatterns\Structural\Adapter\Tests;

use DesignPatterns\Structural\Adapter\PaperBook;
use DesignPatterns\Structural\Adapter\EBookAdapter;
use DesignPatterns\Structural\Adapter\Kindle;
use PHPUnit\Framework\TestCase;

class AdapterTest extends TestCase
{
public function testCanTurnPageOnBook()
{
$book = new PaperBook();
$book->open();
$book->turnPage();

$this->assertSame(2, $book->getPage());
}

public function testCanTurnPageOnKindleLikeInANormalBook()
{
$kindle = new Kindle();
$book = new EBookAdapter($kindle);

$book->open();
$book->turnPage();

$this->assertSame(2, $book->getPage());
}
}