接口适配器,写了一个 demo,不知道理解的对不对?

2023-08-07 原版(非适配器)

  • 适配器模式:接口适配器,写了一个 demo,不知道理解的对不对,请大家指教,感谢。
<?php
/**
 * 设计模式:接口适配器
 * 作用:手机接口适配器
 * 
 * @author Tacks 
 */

namespace App\Structural\Adapter\IphoneInterfaceAdapter;

/**
 * 接口:电子设备(不同电压的电子设备适配为统一的接口)
 */
interface ElectronicDeviceInterface {
    public function use();
}

/**
 * 抽象类:电子设备
 * 
 * 实现:电子设备接口
 */
abstract class ElectronicDeviceAbstract  implements ElectronicDeviceInterface {
    public function use() {

    }
}

/**
 * 具体的适配器:5v 普通
 */
class Power5vAdapter extends ElectronicDeviceAbstract
{
    public function use() {
        return 5;
    }
}

/**
 * 具体的适配器:10v 快充
 */
class Power10vAdapter extends ElectronicDeviceAbstract
{
    public function use() {
        return 10;
    }
}

/**
 * 具体的适配器:200v 高压,适用于大功率电器
 */
class Power220vAdapter extends ElectronicDeviceAbstract
{
    public function use() {
        return 220;
    }
}


/**
 * 业务使用:手机客户端类
 */
class ClientIphone {
    const VOLTS = [5, 10];
    const NAME = '手机';

    /**
     * 适配器
     *
     * @param ElectronicDeviceInterface $dest
     * @return void
     */
    public function charge(ElectronicDeviceInterface $dest) {
        $value = $dest->use();
        if (in_array($value, self::VOLTS)) {
            echo sprintf("[%s] %s:%s %s使用到%sV电压,准备充电", 
                date("Y-m-d H:i:s"), __CLASS__ , __FUNCTION__, self::NAME, $value). PHP_EOL ;
        } else {
            echo sprintf("[%s] %s:%s %s应该采用%sV电压,但是实际上是%sV,无法充电", 
                date("Y-m-d H:i:s"), __CLASS__ , __FUNCTION__, self::NAME, json_encode(self::VOLTS), $value). PHP_EOL ;
        }
    }
}


/**
 * 业务使用:空调客户端类
 */
class ClientAirConditioner {
    const VOLT = 220;
    const NAME = '空调';
    public function charge(ElectronicDeviceInterface $dest) {
        $value = $dest->use();
        if ($value == self::VOLT) {
            echo sprintf("[%s] %s:%s %s使用到%sV电压,准备充电", 
                date("Y-m-d H:i:s"), __CLASS__ , __FUNCTION__, self::NAME, self::VOLT). PHP_EOL ;
        } else {
            echo sprintf("[%s] %s:%s %s应该采用%sV电压,但是实际上是%sV,无法充电", 
                date("Y-m-d H:i:s"), __CLASS__ , __FUNCTION__, self::NAME, self::VOLT, $value). PHP_EOL ;
        }
    }
}

// 适配器模式的主要作用是将一个类的接口转换成客户端所期望的另一个接口
// 在这个示例中,适配器类充当了接口适配器的角色,它们通过实现目标接口 ElectronicDeviceInterface,并提供了符合要求的电压值
$myIphone = new ClientIphone();
$myIphone->charge(new Power5vAdapter());
$myIphone->charge(new Power10vAdapter());
$myIphone->charge(new Power220vAdapter());

$myAir = new ClientAirConditioner();
$myAir->charge(new Power5vAdapter());
$myAir->charge(new Power220vAdapter());
  • Output
[2023-08-07 10:56:39] App\Structural\Adapter\IphoneInterfaceAdapter\ClientIphone:charge 手机使用到5V电压,准备充电
[2023-08-07 10:56:39] App\Structural\Adapter\IphoneInterfaceAdapter\ClientIphone:charge 手机使用到10V电压,准备充电
[2023-08-07 10:56:39] App\Structural\Adapter\IphoneInterfaceAdapter\ClientIphone:charge 手机应该采用[5,10]V电压,但是实际上是220V,无法充电
[2023-08-07 10:56:39] App\Structural\Adapter\IphoneInterfaceAdapter\ClientAirConditioner:charge 空调应该采用220V电压,但是实际上是5V,无法充电
[2023-08-07 10:56:39] App\Structural\Adapter\IphoneInterfaceAdapter\ClientAirConditioner:charge 空调使用到220V电压,准备充电

2023-08-08 适配器理解-适配管理器

感谢评论区的大佬,思考了适配器后,再次实现代码。

/**
 * 设计模式:适配器模式 - 适配管理器
 * 
 * 中心思想:用于将不兼容的接口转换为客户端期望的接口
 * 
 * 目标接口:Target
 *      LineInterface
 * 
 * 不同的充电接口:
 *      LightningLineInterface
 *      TypecLineInterface
 *      TypecUsbInterface
 * 不同的手机实现:
 *      Apple
 *      Huawei
 *      Xiaomi
 *      Realme
 *      Nokia
 * 客户端:
 *      (new Human)->phoneChange($phone, $config); 永远都是这样调用充电,传入手机设备
 * 
 * 适配器主要分为:
 *      适配器接口:PhoneChargeAdapterInterface
 *      不同适配器:LightningAdapter TypecAdapter
 *      适配管理器:PhoneChargeManage
 * 适配管理器,通过手机类型创建不同的适配器对象,再调用适配器方法完成充电
 * 
 * 
 * @author Tacks 
 */
  • 不同手机充电线 Interface

// 充电线
interface LineInterface
{
    const WHAT = '手机充电线';
}

// Lightning 充电线接口
interface LightningLineInterface extends LineInterface
{
    function lightning();
}

// Typec 充电线接口
interface TypecLineInterface extends LineInterface
{
    function typec();
}

// Typec USB 充电线接口
interface TypecUsbInterface extends LineInterface
{
    function usb();
}
  • 不同手机充电方法

// Apple 手机类,实现 LightningLineInterface
class Apple implements LightningLineInterface
{
    const MAX_POWER = '29W';

    function lightning()
    {
        echo sprintf("[%s] typec %s...", __CLASS__, self::MAX_POWER). PHP_EOL ;
    }
}

// Huawei 手机类,实现 TypecLineInterface 接口
class Huawei implements TypecLineInterface
{
    const MAX_POWER = '100W';

    function typec()
    {
        echo sprintf("[%s] typec %s...", __CLASS__, self::MAX_POWER). PHP_EOL ;
    }
}

// Xiaomi 手机类,实现 TypecLineInterface 接口
class Xiaomi implements TypecLineInterface
{
    const MAX_POWER = '120W';

    function typec()
    {
        echo sprintf("[%s] typec %s...", __CLASS__, self::MAX_POWER) . PHP_EOL ;
    }
}

// Realme 手机类,实现 TypecLineInterface 接口
class Realme implements TypecLineInterface,LineInterface
{
    const MAX_POWER = '240W';

    function typec()
    {
        echo sprintf("[%s] typec %s...", __CLASS__, self::MAX_POWER). PHP_EOL ;
    }
}

// Nokia 手机类,实现 TypecUsbInterface 接口
class Nokia implements TypecUsbInterface
{
    const MAX_POWER = '2.5W';

    function usb()
    {
        echo sprintf("[%s] usb %s...", __CLASS__, self::MAX_POWER). PHP_EOL ;
    }
}
  • 不同类型的手机充电适配器
    • 类似把吃肉的动物和吃素的动物分开适配
    • 我这里就是不同充电口的,就按照不同的手机来适配


// 手机充电适配器
interface PhoneChargeAdapterInterface
{
    public function charge($phone);
}

// Lightning 适配器类,实现 PhoneChargeAdapterInterface 适配器接口
class LightningAdapter implements PhoneChargeAdapterInterface
{
    public function charge($phone)
    {
        $phone->lightning();
    }
}

// Typec 适配器类,实现 PhoneChargeAdapterInterface 适配器接口
class TypecAdapter implements PhoneChargeAdapterInterface
{
    public function charge($phone)
    {
        $phone->typec();
    }
}
  • 统一适配管理器
    • 用来管理多个适配器

// 适配管理器
class PhoneChargeManage
{
    // 适配器对象
    private $adapterMap = [];

    // 充电
    public function charge($name)
    {
        $key = strtolower($name);
        if (in_array($key, $this->adapterMap)) {
            return $this->adapterMap[$name];
        }
        return $this->createAdapter($key);
    }

    // 创建适配器对象
    public function createAdapter($key)
    {
        $driverMethod = 'create' . ucfirst($key) . 'Driver';
        if (!method_exists($this, $driverMethod)) {
            throw new \Exception("PhoneChargeManage [{$key}]'s Driver is not supported.");
        }
        $this->adapterMap[$key] = $this->$driverMethod();

        return $this->adapterMap[$key];
    }

    public function createLightningDriver()
    {
        return new LightningAdapter();
    }

    public function createTypecDriver()
    {
        return new TypecAdapter();
    }
}
  • 客户希望永远都是统一的方式调用

// 人
class Human
{
    // 手机充电方法
    function phoneChange(LineInterface $phone, array $config)
    {
        // 创建适配器管理器
        $manage = new PhoneChargeManage();

        // 获取适配器类型
        $driver  = $this->getDriver($phone, $config);

        // 根据适配器类型获取适配器对象
        $adapter = $manage->charge($driver);

        // 使用适配器对象进行手机充电
        $adapter->charge($phone);
    }

    // 获取适配器类型
    public function getDriver(LineInterface $phone, array $config)
    {
        foreach ($config as $keyDriver => $lineItem) {
            if (in_array(get_class($phone), $lineItem)) {
                return $keyDriver;
            }
        }
        return 'default';
    }
}
  • 具体调用

// 配置信息
$config  = [
    'lightning' => [
        Apple::class,
    ],
    'typec' => [
        Huawei::class,
        Xiaomi::class,
    ],
    'usb' => [
        Nokia::class,
    ],
];

try {
    $tacks = new Human();

    $tacks->phoneChange(new Apple(), $config);

    $tacks->phoneChange(new Huawei(), $config);

    $tacks->phoneChange(new Nokia(), $config);

} catch(\Exception $e) {
    echo sprintf("[Error] message:%s...", $e->getMessage()). PHP_EOL ;
}
明天我们吃什么 悲哀藏在现实中 Tacks
最佳答案
  1. 首先 use 是关键字,函数变量命名应该尽量避开关键字。
  2. 适配器的作用是:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。你要注意这个因果关系, 是我先有了不兼容的类,然后通过适配器转换成兼容的接口。 而不是我一开始就是按照某个接口实现了多个不同的子类,然后调用。 你举得例子明显是后一种, 就是简单的多态调用。

下面我举一个例子

// 定义一个狗接口
interface Dog
{
    // 狗可以吃肉
    function eatMeat();
}
// 白狗实现了狗接口
class WhiteDog implements Dog{
    function eatMeat(){
        return '吃肉'}
}
// 定义一个鸡接口
interface Chicken
{
    // 鸡可以吃米
    function eatRice();
}

// 小鸡类实现了鸡接口
class Chick implements Chicken{
    function eatRice()
    {
        return '吃米'}
}

现在有一个农场主, 他要负责照看小鸡和小狗。如果没有适配器的话, 高层次的实现依赖于底层实现,就形成了一种耦合关系。 即农场主必须知道每一种动物是怎么吃的, 他才能喂。 比如农场主的字典里没有小猫, 小猫就喂不了。

class Farmer
{
    function careAnimals($animal)
    {
        // 
        if($animal interface Dog){
            $animal->eatMeat();
        }elseif($animal interface Chicken){
            $animal->eatRice();
        }

    }
}

然后我们定义一个照看动物适配器

interface CareAnimalsAdapter{
    function care();
}

// 肉食性动物适配器,照看肉食性动物
class CardCarnivorouAdapter implements CareAnimalsAdapter{
    function care($animal){
        $animal->eatMeat();
    }
}
// 素食性动物适配器,照看素食性动物
class CardVegetarianAdapter implements CareAnimals{
    function care($animal){
        $animal->eatRice();
    }
}

// 创建适配器管理器

class CareManage{

    private $adapterMap;

    function driver($name){
        if(in_array($name, $this->adapterMap)){
            return $this->adapterMap[$name];
        }

        return $this->createAdapter($name);
    }

    function createAdapter($name){
        // 肉食性动物返回肉食性适配器
        if($name == 'carnivorou'){
            $this->adapterMap['carnivorou'] = new CardCarnivorouAdapter();
            return $this->adapterMap['carnivorou'];
        }
        // 素食性动物返回素食性适配器
        elseif($name == 'vegetarianx'){
            $this->adapterMap['vegetarianx'] = new CardVegetarianAdapter();
            return $this->adapterMap['vegetarianx'];
        }
    }
}

修改一下农场主, 以前是直接喂, 现在是通过适配器喂。农场主喂的时候不关心这个动物到底怎么吃,只要查一下吃肉或者吃素就可以喂了。 即高层次实现不依赖低层次实现,实现了解耦。

class Farmer
{   
    function careAnimals($animal, $config)
    {
        $careManage = new CareManage($animal);
        // 通过食性获取不同的适配器
        $driver = getDriver($animal);
        // 现在农场主不用考虑它是鸡还是狗,还是猫还是鱼。也不用考虑猫吃什么,狗吃什么。
        $careManage->driver($driver)->care($animal);
    }

    public function getDriver($animal, $config)
    {
        // 农场主根据实际情况, 判断这个动物是吃肉还是吃素的。
        if(in_array(get_class($animal), $config['carnivorou'])){
            return 'carnivorou';
        }elseif(in_array(get_class($animal), $config['vegetarianx'])){
            return 'vegetarianx';
        }
    }
}
8个月前 评论
Tacks (楼主) 8个月前
徵羽宫 (作者) 8个月前
Tacks (楼主) 8个月前
徵羽宫 (作者) 8个月前
讨论数量: 10

我来谈谈我的理解也算是对设计模式的复习
首先在你的示例代码中,抽象类中的方法定义: ElectronicDeviceAbstract 类中 use 方法没有任何实现,因为它是一个抽象类,所以可以将 use 方法定义为抽象方法,强制具体适配器实现这个方法。

// 抽象类中将use方法定义为抽象方法  
abstract  class ElectronicDeviceAbstract implements ElectronicDeviceInterface 
{     abstract  public  function use(); 
}

或者可以直接去掉 ElectronicDeviceAbstract 这个抽象类,感觉有点多余,我的理解是将重复功能的代码放在抽象类,在实际开发中当你需要共享实现时使用抽象类。如果几个类间有公共的方法和属性,可以使用抽象类来封装这些共通功能。
当你需要定义一组 API 时使用接口。如果你想定义一组规范或者约束,那么使用接口是合适的。它强迫实现该接口的类遵循某种约束。例如:

abstract  class ElectronicDeviceAbstract implements ElectronicDeviceInterface 
{     
    public function __construct(protected string $name){

    }
}

这样继承 ElectronicDeviceAbstract 抽象类的类都有一个 name 属性,

再来看看你的实例代码,你所有的具体的适配器都继承了 ElectronicDeviceAbstract 抽象类等于实现了 ElectronicDeviceInterface 接口,然后客户端调用不同的适配器类,这种模式感觉是 策略模式而并不是适配器模式

PHP

策略模式路线规划策略。

在商场系统中,我们可以考虑一个支付系统的例子。
假设我们的商场系统原本只支持信用卡支付

interface PaymentGateway
{
    public function pay(float $amount);
}

class CreditCardPayment implements PaymentGateway
{
    public function pay($amount): void
    {
        echo "Paying $amount using Credit Card\n";
    }
}

现在我们想要添加对 PayPal 支付的支持。
但是,信用卡支付和 PayPal 支付的接口是不同的,我们不能直接在我们的系统中添加 PayPal 支付。

class PayPal
{
    public function sendPayment($amount): void
    {
        echo "Paying $amount using PayPal\n";
    }
}

这时,我们就可以使用适配器模式,创建一个 PayPal 适配器,将 PayPal 的接口转换成我们的系统可以接受的信用卡支付接口。

class PayPalAdapter implements PaymentGateway
{

    public function __construct(protected readonly PayPal $paypal)
    {
    }

    public function pay($amount): void
    {
        $this->paypal->sendPayment($amount);
    }
}

在这个例子中,我们首先定义了一个支付网关接口(PaymentGateway)和一个实现这个接口的类(CreditCardPayment)。
然后我们定义了一个 PayPal 类,它有一个不同的接口(sendPayment)。
然后我们创建了一个 PayPal 适配器(PayPalAdapter),它实现了支付网关接口,并在内部调用 PayPal 的接口。
在客户端代码中,我们可以使用 PayPal 适配器来完成支付,就像使用信用卡支付一样。

这样,我们就可以在不修改原有代码的情况下,添加对新的支付方式的支持。这符合开闭原则,即对扩展开放,对修改关闭。

下面是完整代码:

<?php

/**
 * 适配器模式(Adapter Pattern)是一种结构型设计模式,它的主要目的是使得原本由于接口不兼容而不能一起工作的类可以一起工作。
 * 适配器模式通过提供一个转换接口,将一个类的接口转换成客户期望的另一个接口。
 */

interface PaymentGateway
{
    public function pay(float $amount);
}

class CreditCardPayment implements PaymentGateway
{
    public function pay($amount): void
    {
        echo "Paying $amount using Credit Card\n";
    }
}

class PayPal
{
    public function sendPayment($amount): void
    {
        echo "Paying $amount using PayPal\n";
    }
}

class PayPalAdapter implements PaymentGateway
{

    public function __construct(protected readonly PayPal $paypal)
    {
    }

    public function pay($amount): void
    {
        $this->paypal->sendPayment($amount);
    }
}

$payment = new CreditCardPayment();
$payment->pay(100);

$payment = new PayPalAdapter(new PayPal());
$payment->pay(1000);

谢谢你让我再次温习了一便设计模式概念

8个月前 评论
Tacks (楼主) 8个月前
  1. 首先 use 是关键字,函数变量命名应该尽量避开关键字。
  2. 适配器的作用是:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。你要注意这个因果关系, 是我先有了不兼容的类,然后通过适配器转换成兼容的接口。 而不是我一开始就是按照某个接口实现了多个不同的子类,然后调用。 你举得例子明显是后一种, 就是简单的多态调用。

下面我举一个例子

// 定义一个狗接口
interface Dog
{
    // 狗可以吃肉
    function eatMeat();
}
// 白狗实现了狗接口
class WhiteDog implements Dog{
    function eatMeat(){
        return '吃肉'}
}
// 定义一个鸡接口
interface Chicken
{
    // 鸡可以吃米
    function eatRice();
}

// 小鸡类实现了鸡接口
class Chick implements Chicken{
    function eatRice()
    {
        return '吃米'}
}

现在有一个农场主, 他要负责照看小鸡和小狗。如果没有适配器的话, 高层次的实现依赖于底层实现,就形成了一种耦合关系。 即农场主必须知道每一种动物是怎么吃的, 他才能喂。 比如农场主的字典里没有小猫, 小猫就喂不了。

class Farmer
{
    function careAnimals($animal)
    {
        // 
        if($animal interface Dog){
            $animal->eatMeat();
        }elseif($animal interface Chicken){
            $animal->eatRice();
        }

    }
}

然后我们定义一个照看动物适配器

interface CareAnimalsAdapter{
    function care();
}

// 肉食性动物适配器,照看肉食性动物
class CardCarnivorouAdapter implements CareAnimalsAdapter{
    function care($animal){
        $animal->eatMeat();
    }
}
// 素食性动物适配器,照看素食性动物
class CardVegetarianAdapter implements CareAnimals{
    function care($animal){
        $animal->eatRice();
    }
}

// 创建适配器管理器

class CareManage{

    private $adapterMap;

    function driver($name){
        if(in_array($name, $this->adapterMap)){
            return $this->adapterMap[$name];
        }

        return $this->createAdapter($name);
    }

    function createAdapter($name){
        // 肉食性动物返回肉食性适配器
        if($name == 'carnivorou'){
            $this->adapterMap['carnivorou'] = new CardCarnivorouAdapter();
            return $this->adapterMap['carnivorou'];
        }
        // 素食性动物返回素食性适配器
        elseif($name == 'vegetarianx'){
            $this->adapterMap['vegetarianx'] = new CardVegetarianAdapter();
            return $this->adapterMap['vegetarianx'];
        }
    }
}

修改一下农场主, 以前是直接喂, 现在是通过适配器喂。农场主喂的时候不关心这个动物到底怎么吃,只要查一下吃肉或者吃素就可以喂了。 即高层次实现不依赖低层次实现,实现了解耦。

class Farmer
{   
    function careAnimals($animal, $config)
    {
        $careManage = new CareManage($animal);
        // 通过食性获取不同的适配器
        $driver = getDriver($animal);
        // 现在农场主不用考虑它是鸡还是狗,还是猫还是鱼。也不用考虑猫吃什么,狗吃什么。
        $careManage->driver($driver)->care($animal);
    }

    public function getDriver($animal, $config)
    {
        // 农场主根据实际情况, 判断这个动物是吃肉还是吃素的。
        if(in_array(get_class($animal), $config['carnivorou'])){
            return 'carnivorou';
        }elseif(in_array(get_class($animal), $config['vegetarianx'])){
            return 'vegetarianx';
        }
    }
}
8个月前 评论
Tacks (楼主) 8个月前
徵羽宫 (作者) 8个月前
Tacks (楼主) 8个月前
徵羽宫 (作者) 8个月前

@Tacks 适配器一般是组合模式,因为适配器大多数使用场景是在开发过程中, 你决定在程序中整合一个第三方库,或者你可能根本没有程序库的源代码, 从而无法对其进行修改,适配器模式允许你创建一个中间类作为接口的类之间的转换器。

我也去了解了下你说的类适配器对象,但是在你的示例代码中没有体现出来,我还是以我之前的支付系统的例子。
假设现在我们需要支持 Wechat 支付,我们引入了第三方 WechatPay 包,但是这个包我们无法直接使用需要修改,WechatPayPalAdapter 类既继承了 WechatPay 类也实现了 PaymentGateway 接口。因此,它可以作为 WechatPay 类的一个适配器,以使其符合 PaymentGateway 接口。

class WechatPay {
    public function WechatPay($amount): void
    {
        echo "Paying $amount using WechatPay \n";
    }
}

class WechatPayPalAdapter extends WechatPay implements PaymentGateway
{
    public function pay($amount): void
    {
        // 调用旧系统中的方法
        return $this->WechatPay($amount);
    }
}

这种方式的优势在于它可以重用现有代码,同时也符合新的接口规范。然而,由于PHP不支持多继承,因此这种方法的灵活性可能会受到限制

8个月前 评论
Tacks (楼主) 8个月前

适配器模式就是结合现有情况下,创建一个中间类来完成流程调用。 假设,现有一个流程,给手机插上充电线充电,现有接口和对象如下:

充电线
interface ChargingCable{
      // 起点表示链接手机那头
      public function connectStartingPoint();
      // 终点表示链接电源那头
      public function connectEndPoint();
}

class TypeC implements ChargingCable{
      public function connectStartingPoint(){
           // 把手机和自己链接到一起
      }

      public function connectEndPoint(){
           // 链接电源
      }
}

abstract class Phone {
    public function charge(ChargingCable $cable){
        // 检查当前充电线是否可以充电
        if(!$this->isChargeable($cable)){
           return;
        }
          // 把手机连上充电线
         $cable->connectStartingPoint();
         // 再把充电线链接电源
         $cable->connectEndPoint();
     }

    abstract protected function isChargeable(ChargingCable $cable);
}

class HuaWei extends Phone{
        protected function isChargeable(ChargingCable $cable){
                return $cable instanceof TypeC;
         }
}

class IPhone extends Phone{
        protected function isChargeable(ChargingCable $cable){
                return $cable instanceof Lighting;
         }
}

现在我们只有一个 typeC 的充电线和两部手机:苹果、华为,如果想给苹果充电,要么就去搞一个 Lighting 的充电线,要么就是采用适配器方案,把 TypeC 转成 Lighting:

class TypeC2LightingAdapter extends Lighting{
        public funtion __construct(protected TypeC $cable){
        }
        public function connectCable(){
            // 把转接器链接充电线
        }

        public function connectPhone(){
          // 把转接器链接手机
        }

        public function connectStartingPoint(){
             $this->connectCable();
             $this->connectPhone();
         }

      public function connectEndPoint(){
           $this->cable->connectEndPoint();
      }
}

$phone = new IPhone();

$phone->charge(new TypeC2LightingAdapter(new TypeC()))
8个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!