架构设计模式适配器模式

4次阅读

共计 3862 个字符,预计需要花费 10 分钟才能阅读完成。

在软件开发中,有时存在结构之间不兼容的情况,我们也可以像引入一个电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。

概述

与电源适配器相似,在适配器模式中引入了一个被称为适配器 (Adapter) 的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。

适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。适配器模式定义如下:

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

注:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。在实际开发中,对象适配器的使用频率更高,对象适配器模式结构如图所示:

在对象适配器模式结构图中包含如下几个角色:

  • Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  • Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承或实现 Target 并关联一个 Adaptee 对象使二者产生联系。
  • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

在对象适配器中,客户端需要调用 request()方法,而适配者类 Adaptee 没有该方法,但是它所提供的 specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类 Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的 request()方法中调用适配者的 specificRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下所示:

<?php

class Adapter extends Target
{
    // 维持一个对适配者对象的引用
    private $adaptee;

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

    public function request()
    {
        // 转发调用
        $this->adaptee->specificRequest();}
}

案例

Sunny 软件公司在很久以前曾开发了一个算法库,里面包含了一些常用的算法,例如排序算法和查找算法,在进行各类软件开发时经常需要重用该算法库中的算法。在为某学校开发教务管理系统时,开发人员发现需要对学生成绩进行排序和查找,该系统的设计人员已经开发了一个成绩操作接口 ScoreOperation,在该接口中声明了排序方法 sort()和查找方法 search()。为了提高排序和查找的效率,开发人员决定重用算法库中的快速排序算法类 QuickSort 和二分查找算法类 BinarySearch。
由于某些原因,现在 Sunny 公司开发人员已经找不到该算法库的源代码,无法直接通过复制和粘贴操作来重用其中的代码;部分开发人员已经针对 ScoreOperation 接口编程,如果再要求对该接口进行修改或要求大家直接使用 QuickSort 类和 BinarySearch 类将导致大量代码需要修改。
Sunny 软件公司开发人员面对这个没有源码的算法库,遇到一个幸福而又烦恼的问题:如何在既不修改现有接口又不需要任何算法库代码的基础上能够实现算法库的重用?

Sunny 软件公司开发人员决定使用适配器模式来重用算法库中的算法,其基本结构如图所示:

ScoreOperation 接口充当抽象目标,QuickSort 和 BinarySearch 类充当适配者,OperationAdapter 充当适配器。完整代码如下所示:

<?php

// 抽象成绩操作类:目标接口
interface ScoreOperation
{
    // 成绩排序
    public function sort(array $array): array;

    // 成绩查找
    public function search(array $array, int $key): int;
}

// 快速排序类:适配者
class QuickSort
{public function _quickSort(array $array): array
    {$this->sort($array, 0, count($array) - 1);

        return $array;
    }

    public function sort(array $array, int $p, int $r)
    {
        $q = 0;

        if ($p < $r) {$q = $this->partition($array, $p, $r);
            $this->sort($array, $p, $q - 1);
            $this->sort($array, $q + 1, $r);
        }
    }

    public function partition(array $a, int $p, int $r): int
    {$x = $a[$r];
        $j = $p - 1;

        for ($i = $p; $i <= $r - 1; $i++) {if ($a[$i] <= $x) {
                $j++;
                $this->swap($a, $j, $i);
            }
        }

        $this->swap($a, $j + 1, $r);

        return $j + 1;
    }

    public function swap(array $a, int $i, int $j)
    {$t = $a[$i];
        $a[$i] = $a[$j];
        $a[$j] = $t;
    }
}

// 二分查找类:适配者
class BinarySearch
{public function _binarySearch(array $array, int $key): int
    {
        $low = 0;
        $high = count($array) - 1;

        while ($low <= $high) {$mid = ($low + $high) / 2;
            $midVal = $array[$mid];

            if ($midVal < $key) {$low = $mid + 1;} elseif ($midVal > $key) {$high = $mid - 1;} else {return 1;}
        }

        return -1;
    }
}

// 操作适配器:适配器
class OperationAdapter implements ScoreOperation
{
    // 定义适配者 QuickSort 对象
    private $sortObj;

    // 定义适配者 BinarySearch 对象
    private $searchObj; 

    public function __construct()
    {$this->sortObj = new QuickSort();
        $this->searchObj = new BinarySearch();}

    public function sort(array $array): array
    {return $this->sortObj->_quickSort($array);
    }

    public function search(array $array, int $key): int
    {return $this->searchObj->_binarySearch($array, $key);
    }
}

为了让系统具备良好的灵活性和可扩展性,我们引入了工具类 ConfigUtil 和配置文件,将适配器类的类名存储在配置文件中。如果需要使用其他排序算法类和查找算法类,可以增加一个新的适配器类,使用新的适配器来适配新的算法,原有代码无须修改。通过引入配置文件和反射机制,可以在不修改客户端代码的情况下使用新的适配器,无须修改源代码,符合“开闭原则”。

总结

适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用。

主要优点
  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  2. 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
主要缺点
  1. 对于不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
  2. 类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
适用场景
  1. 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  2. 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
正文完
 0