接口和抽象类的区别

  接口和抽象类在各个方面都很相似,而且无论是使用接口还是使用抽象类都可以实现相同的功能,这就导致了很多开发者无法区分两者,甚至在使用时根据当时心情随便选择一种。
 
  其实要理解两者的区别并不难,正如我们常说的实现接口&继承类,既然类是用来继承的,那么抽象类里的方法都应该是通用方法(出现猴子继承了兔子打洞技能肯定是错误的设计),与之相对接口里的方法则应该是特有方法。
 
  举例详细说明,动物都需要进食和睡眠,那么eat()方法和sleep()方法都应该在动物抽象类(abstract class Animal)里定义。
 
  基于动物抽象类我们派生出兔子类(class Rabbit extends Animal)和猴子类(class Monkey extends Animal),兔子特有技能是打洞,猴子特有技能是爬树,那么dig()方法和climb()方法则应该分别在两个接口(暂且称为interface Ability1和interface Ability2)里定义。
 
  实现代码如下:
 
<?php

/**
 * 抽象类Animal
 */
abstract class Animal
{
    abstract public function eat($food);

    abstract public function sleep();
}

/**
 * 接口Ability1
 */
interface Ability1
{
    public function dig();
}

/**
 * 接口Ability2
 */
interface Ability2
{
    public function climb();
}

/**
 * 兔子类(继承Animal抽象类并实现Ability1接口)
 */
class Rabbit extends Animal implements Ability1
{
    public function eat($food)
    {
        echo "兔子吃{$food}" . PHP_EOL;
    }

    public function sleep()
    {
        echo '兔子睡觉' . PHP_EOL;
    }

    public function dig()
    {
        echo '兔子打洞' . PHP_EOL;
    }
}

/**
 * 猴子类(继承Animal抽象类并实现Ability2接口)
 */
class Monkey extends Animal implements Ability2
{
    public function eat($food = '香蕉')
    {
        echo "猴子吃{$food}" . PHP_EOL;
    }

    public function sleep()
    {
        echo '猴子睡觉' . PHP_EOL;
    }

    public function climb()
    {
        echo '猴子爬树' . PHP_EOL;
    }
}

$rabbit = new Rabbit();
$rabbit->eat('胡萝卜'); // 兔子吃胡萝卜
$rabbit->sleep(); // 兔子睡觉
$rabbit->dig(); // 兔子打洞

$monkey = new Monkey();
$monkey->eat(); // 猴子吃香蕉
$monkey->sleep(); // 猴子睡觉
$monkey->climb(); // 猴子爬树
 
  这时假如我们需要增加一个老鼠类,只要继承Animal类并实现Ability1接口即可:class Mouse extends Animal implements Ability1,这样当其他开发者看到Mouse类继承了Animal类并实现了Ability1接口就知道该类肯定实现了eat()、sleep()、dig()这三个方法,可以放心大胆地调用。
 
  我们也可以从另一个角度来理解接口和抽象类:接口用于表达有没有,抽象类用于表达是不是。
 
  问:猴子是不是动物?
  答:是,因为继承了abstract class Animal。
 
  问:猴子有没有打洞技能?
  答:没有,因为没有实现interface Ability1。
 
  问:猴子有没有爬树技能?
  答:有,因为实现了interface Ability2。
 
  在实际开发中,无论是接口还是抽象类,更多的作用是对实现类代码的约束。
 
  举个例子,我们需要实现一个缓存功能,目前暂定可以使用Memcached、Redis、File这三种缓存类型里的任意一种,且可以通过修改配置文件随时切换缓存类型,后期还可能增加APC、Xcache等缓存类型。为了约束各种缓存类型的实现类代码以及方便后期扩展,我们可以先定义一个缓存抽象类,然后各种缓存类型的实现类都继承这个缓存抽象类,实现代码如下:
 
<?php

/**
 * 缓存抽象类
 */
abstract class CacheAbstract
{
    abstract public function set($key, $value);

    abstract public function get($key);

    abstract public function del($key);
}

/**
 * Memcached缓存类
 */
class MCache extends CacheAbstract
{
    public function set($key, $value)
    {
        // TODO: Implement set() method.
    }

    public function get($key)
    {
        // TODO: Implement get() method.
    }

    public function del($key)
    {
        // TODO: Implement del() method.
    }
}

/**
 * Redis缓存类
 */
class RCache extends CacheAbstract
{
    public function set($key, $value)
    {
        // TODO: Implement set() method.
    }

    public function get($key)
    {
        // TODO: Implement get() method.
    }

    public function del($key)
    {
        // TODO: Implement del() method.
    }
}

/**
 * File缓存类
 */
class FCache extends CacheAbstract
{
    public function set($key, $value)
    {
        // TODO: Implement set() method.
    }

    public function get($key)
    {
        // TODO: Implement get() method.
    }

    public function del($key)
    {
        // TODO: Implement del() method.
    }
}

// 根据配置决定使用哪种缓存
$conf = 'redis'; // 这里假设从配置文件获取了缓存类型
if ($conf === 'memcached') {
    $class = 'MCache'; // 使用Memcached缓存
} else if ($conf === 'redis') {
    $class = 'RCache'; // 使用Redis缓存
} else {
    $class = 'FCache'; // 使用File缓存
}

$cache = new $class();
$cache->set('PHP', 'PHP是世界上最好の语言');
$cache->get('PHP');
$cache->del('PHP');
 
  在实际开发中,需要使用接口和抽象类的场景是非常多的,例如各种第三方支付(支付宝、微信支付、PayPal、银联等)的接入,各种SMS短信服务(阿里云、腾讯云、华为云等)的接入。合理使用接口和抽象类能让代码更简单、规范、易维护、易拓展,尤其是项目比较庞大时。

Copyright © 2024 码农人生. All Rights Reserved