php 设计模式(四)适配器模式 | Leezhiy Blog

php 设计模式(四)适配器模式

什么是适配器模式

定义:把对某些相似的类的操作转化为一个统一的“接口” – 适配器

适配器的特点

适配器统一或者屏蔽那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类很容易增减,而不用修改与之交互的代码,符合减少代码之间的耦合

什么时候要使用适配器

主要应用于“希望复用一些现成的类,但接口又与复用环境不一样”的情况。

示例

业务场景

黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的 doMouthOpen 及 doMouthClose 方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用 doMouthOpendoMouthClose 方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。

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
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}
更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的 operateMouth(type) 方法来实现嘴巴控制。如果 operateMouth(type) 方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用 operateMouth() 方法。搁谁都不淡定了。

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
70
71
72
73
74
75
76
77
abstract class Toy  
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了 开-闭 原则,一个软件实体应该对拓展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被拓展,也就是每个实体都是一个小王国,你让我参与你的事情可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那总归得有个解决方法吧。那就是引入一个新的类–我们本文的主角–适配器。 适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。

到此,解决方法已经呼之欲出了,下面贴上代码。

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
<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}

解决方案一 :对象适配器

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

//对象适配器 角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//对象适配器 角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}

// 测试
class testObjectDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testObjectDriver();
$test->run();

解决方案一 :类适配器

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

// 适配器除了对象适配器还有类适配器

// 类适配器 角色:红枣遥控公司 狗
class RedDogAdapter extends Dog implements RedTarget {
public function doMouthOpen(){
$this->openMouth();
}

public function doMouthClose(){
$this->closeMouth();
}
}

// 类适配器 角色:红枣遥控公司 猫
class RedCatAdapter extends Cat implements RedTarget {
public function doMouthOpen(){
$this->openMouth();
}

public function doMouthClose(){
$this->closeMouth();
}
}

// 类适配器 角色:绿枣遥控公司 狗
class GreenDogAdapter extends Dog implements GreenTarget {
public function operateMouth($type = 0){
if ($type) {
$this->openMouth();
} else {
$this->closeMouth();
}
}
}

// 类适配器 角色:绿枣遥控公司 猫
class GreenCatAdapter extends Cat implements GreenTarget {
public function operateMouth($type = 0){
if ($type) {
$this->openMouth();
} else {
$this->closeMouth();
}
}
}


// 测试
class testClassDriver
{
public function run()
{
$adapter_red_dog = new RedDogAdapter();
echo "给狗套上红枣适配器\n";
//张嘴
$adapter_red_dog->doMouthOpen();
//闭嘴
$adapter_red_dog->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green_dog = new GreenDogAdapter();
//张嘴
$adapter_green_dog->operateMouth(1);
//闭嘴
$adapter_green_dog->operateMouth(0);
}
}

$test = new testClassDriver();
$test->run();

最后的结果就是,Toy类及其子类在不改变自身的情况下,通过两种不同的适配器都实现了不同的接口。

小结

【适配器模式流程】:

(1).客户通过目标接口调用适配器的方法对适配器发出请求

(2). 适配器使用被适配者接口把请求转换成被适配者一个或多个调用接口

(3).客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。

【常用情境】:

(1).系统需要使用现有的类,而此类的接口不符合系统的需要

(2).想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。

(3).(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。

(4).Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。

(5).Adapter模式有对象适配器和类适配器两种形式的实现结构,但是类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神。

Buy me a cup of milkshake 🍨.
------------- 💖 🌞 本 文 结 束 😚 感 谢 您 的 阅 读 🌞 💖 -------------

欢迎关注我的其它发布渠道