PHP 适配器模式 (`Adapter Pattern`):将不兼容接口转换为兼容接口

各位观众老爷,大家好!今天咱们聊聊PHP里的“百变星君”——适配器模式 (Adapter Pattern)。这玩意儿能把两个原本水火不容的接口,硬生生地撮合成一对,让它们愉快地合作。是不是听起来像个媒婆?

别急,咱们先从一个故事开始,更容易理解。

故事:老式插座和新式充电器

话说,你出国旅游,带了个国内的电器,结果发现酒店的插座跟你的插头完全不兼容。咋办?难道要放弃使用心爱的电吹风?当然不是!这时候,就需要一个“转换插头”了,它能把国内的插头转换成国外插座能识别的样式。

适配器模式就像这个“转换插头”,它负责把一个类的接口转换成客户希望的另一个接口。

什么是适配器模式?(官方解释版)

适配器模式属于结构型模式,它允许将一个类的接口转换成客户希望的另一个接口。适配器让原本接口不兼容的类可以一起工作。它主要解决的是接口不兼容的问题。

什么是适配器模式?(通俗解释版)

简单来说,适配器模式就是“搭桥”。当两个类因为接口不兼容而无法直接合作时,我们创建一个适配器类,让它充当中间人,负责把一个类的接口转换成另一个类可以接受的接口。

适配器模式的组成部分

  • 目标接口 (Target Interface): 客户期望使用的接口。就像国外酒店的插座。
  • 适配器 (Adapter): 负责将源接口转换成目标接口的类。就像转换插头。
  • 被适配者 (Adaptee): 需要适配的类,拥有待转换的接口。就像国内的插头。
  • 客户端 (Client): 使用目标接口的类。就像你带着电吹风的用户。

适配器模式的两种实现方式

适配器模式主要有两种实现方式:类适配器和对象适配器。

  1. 类适配器 (Class Adapter): 通过多重继承来实现适配。适配器类同时继承目标接口和被适配者类。

    • 优点: 实现简单,直接继承被适配者的功能。
    • 缺点: 要求使用多重继承,在某些语言中可能不支持(PHP支持),且耦合度较高。

    PHP 类适配器代码示例

    <?php
    
    // 目标接口: 鸟
    interface Bird
    {
        public function fly(): string;
        public function makeSound(): string;
    }
    
    // 被适配者: 火鸡
    class Turkey
    {
        public function gobble(): string
        {
            return "Gobble gobble";
        }
    
        public function flyShortDistance(): string
        {
            return "Flying a short distance";
        }
    }
    
    // 适配器: 火鸡适配器,让火鸡也能像鸟一样飞和叫
    class TurkeyAdapter extends Turkey implements Bird
    {
        public function fly(): string
        {
            // 火鸡飞不远,所以模拟多飞几次
            return "Trying to fly like a bird: " . $this->flyShortDistance() . ", " . $this->flyShortDistance() . ", " . $this->flyShortDistance();
        }
    
        public function makeSound(): string
        {
            return $this->gobble(); // 火鸡的叫声
        }
    }
    
    // 客户端代码
    $turkey = new Turkey();
    $turkeyAdapter = new TurkeyAdapter();
    
    echo "火鸡:n";
    echo "  " . $turkey->gobble() . "n";
    echo "  " . $turkey->flyShortDistance() . "nn";
    
    echo "火鸡适配器 (假装是鸟):n";
    echo "  " . $turkeyAdapter->makeSound() . "n";
    echo "  " . $turkeyAdapter->fly() . "n";
    
    ?>

    代码解释:

    • Bird 是目标接口,定义了 fly()makeSound() 方法,代表鸟应该有的行为。
    • Turkey 是被适配者,它有 gobble() (火鸡叫) 和 flyShortDistance() (短距离飞行) 方法。
    • TurkeyAdapter 是适配器,它继承了 Turkey 类,并实现了 Bird 接口。 在 fly() 方法中,我们模拟了火鸡努力像鸟一样飞,调用了 flyShortDistance() 方法多次。 在 makeSound() 方法中,直接返回了火鸡的叫声 gobble()
    • 客户端代码创建了一个 Turkey 实例和一个 TurkeyAdapter 实例,并通过适配器让火鸡“假装”成鸟。
  2. 对象适配器 (Object Adapter): 通过组合(持有被适配者的实例)来实现适配。适配器类持有被适配者的实例,并在方法中调用被适配者的方法来实现目标接口。

    • 优点: 耦合度较低,更加灵活。可以适配多个被适配者。
    • 缺点: 需要持有被适配者的实例,略微增加了复杂度。

    PHP 对象适配器代码示例

    <?php
    
    // 目标接口: 鸟
    interface Bird
    {
        public function fly(): string;
        public function makeSound(): string;
    }
    
    // 被适配者: 火鸡
    class Turkey
    {
        public function gobble(): string
        {
            return "Gobble gobble";
        }
    
        public function flyShortDistance(): string
        {
            return "Flying a short distance";
        }
    }
    
    // 适配器: 火鸡适配器 (对象适配器),让火鸡也能像鸟一样飞和叫
    class TurkeyAdapter implements Bird
    {
        private $turkey; // 持有火鸡的实例
    
        public function __construct(Turkey $turkey)
        {
            $this->turkey = $turkey;
        }
    
        public function fly(): string
        {
            // 火鸡飞不远,所以模拟多飞几次
            return "Trying to fly like a bird: " . $this->turkey->flyShortDistance() . ", " . $this->turkey->flyShortDistance() . ", " . $this->turkey->flyShortDistance();
        }
    
        public function makeSound(): string
        {
            return $this->turkey->gobble(); // 火鸡的叫声
        }
    }
    
    // 客户端代码
    $turkey = new Turkey();
    $turkeyAdapter = new TurkeyAdapter($turkey); // 将火鸡实例传递给适配器
    
    echo "火鸡:n";
    echo "  " . $turkey->gobble() . "n";
    echo "  " . $turkey->flyShortDistance() . "nn";
    
    echo "火鸡适配器 (假装是鸟):n";
    echo "  " . $turkeyAdapter->makeSound() . "n";
    echo "  " . $turkeyAdapter->fly() . "n";
    
    ?>

    代码解释:

    • BirdTurkey 的定义与类适配器示例相同。
    • TurkeyAdapter 实现了 Bird 接口,但它没有继承 Turkey 类,而是持有一个 Turkey 类的实例 ($turkey)。
    • TurkeyAdapter 的构造函数中,我们接收一个 Turkey 实例,并将其赋值给 $this->turkey
    • fly()makeSound() 方法通过调用 $this->turkey 的方法来实现目标接口。
    • 客户端代码创建了一个 Turkey 实例,并将其传递给 TurkeyAdapter 的构造函数。

类适配器 vs 对象适配器

特性 类适配器 对象适配器
实现方式 多重继承 组合(持有被适配者的实例)
耦合度 较高 较低
灵活性 较低 较高
适用场景 被适配者是类,且可以使用多重继承 被适配者是对象,或者需要适配多个被适配者
依赖关系 依赖于被适配者的实现细节 不依赖于被适配者的实现细节,只依赖于其接口

适配器模式的应用场景

  • 兼容旧代码: 当你需要使用一个旧的类,但它的接口与你的系统不兼容时,可以使用适配器模式来解决。
  • 集成第三方库: 当你使用第三方库,但它的接口与你的系统不一致时,可以使用适配器模式来进行适配。
  • 重构: 在重构过程中,如果需要修改类的接口,但又不想影响现有代码,可以使用适配器模式来保持兼容性。
  • 统一接口: 当你有多个类,它们的功能相似,但接口不同时,可以使用适配器模式来统一它们的接口。

实际案例:数据库适配器

PHP的PDO(PHP Data Objects)就是一个很好的适配器模式的应用。PDO提供了一个统一的接口来访问不同的数据库系统(MySQL, PostgreSQL, SQLite等)。

<?php

// 假设我们有一个旧的数据库类
class OldDatabase
{
    public function query(string $sql): array
    {
        // 模拟查询数据库
        echo "Executing old database query: " . $sql . "n";
        return ['old_data' => 'result'];
    }
}

// 目标接口:统一的数据库接口
interface DatabaseInterface
{
    public function execute(string $sql): array;
}

// 适配器:将旧的数据库类适配到新的接口
class OldDatabaseAdapter implements DatabaseInterface
{
    private $oldDatabase;

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

    public function execute(string $sql): array
    {
        return $this->oldDatabase->query($sql);
    }
}

// 新的代码中使用统一的数据库接口
class NewService
{
    private $database;

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

    public function getData(string $query): array
    {
        return $this->database->execute($query);
    }
}

// 使用旧的数据库类
$oldDatabase = new OldDatabase();
$adapter = new OldDatabaseAdapter($oldDatabase);

// 新的服务可以使用适配后的数据库
$newService = new NewService($adapter);
$data = $newService->getData("SELECT * FROM users");
print_r($data);

?>

代码解释:

  • OldDatabase 是一个旧的数据库类,它有一个 query() 方法,用于执行SQL查询。
  • DatabaseInterface 是目标接口,定义了一个 execute() 方法,用于执行SQL查询。
  • OldDatabaseAdapter 是适配器,它实现了 DatabaseInterface 接口,并将 OldDatabase 适配到新的接口。 execute() 方法调用了 OldDatabasequery() 方法。
  • NewService 是新的服务类,它依赖于 DatabaseInterface 接口。 通过使用适配器,NewService 可以使用旧的数据库类,而无需修改自身的代码。

适配器模式的优点

  • 提高了类的复用性: 可以将不兼容的类组合在一起工作。
  • 增加了系统的灵活性: 可以方便地替换被适配者,而无需修改客户端代码。
  • 符合开闭原则: 可以在不修改现有代码的情况下,添加新的适配器。

适配器模式的缺点

  • 增加了系统的复杂性: 需要创建额外的适配器类。
  • 可能会降低性能: 适配器可能会引入额外的开销。

注意事项

  • 适配器模式应该谨慎使用。 如果接口不兼容的问题可以通过修改其中一个类的接口来解决,那么最好不要使用适配器模式。
  • 适配器的设计应该尽量简单。 适配器只应该负责接口的转换,而不应该包含过多的业务逻辑。

总结

适配器模式是一个非常有用的设计模式,它可以帮助我们解决接口不兼容的问题,提高代码的复用性和灵活性。 但是,它也会增加系统的复杂性,因此应该谨慎使用。记住,设计模式是工具,用得好能事半功倍,用不好反而会适得其反。

希望今天的讲解对大家有所帮助! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注