该篇属于《Laravel底层核心技术实战揭秘》这一课程《laravel底层核心概念解析》这一章的扩展阅读。考虑到学员们的基础差异,为了避免视频当中过于详细而连篇累牍,故将一些laravel底层实现相关的PHP知识点以文章形式呈现,供大家预习和随时查阅。

在面向对象中,polymorphism(多态、多极对应)是基本而强大的工具,可以用它来更好地组织你的应用开发。

何谓Polymorphism?

Polymorphism describes a pattern in object oriented programming in which classes have different functionality while sharing a common interface. 多态,作为面向对象编程中的一种设计模式,指的是通过遵循同一个interface,类可以有不同的功能实现(相当于说,有多种形态)。

多态的一大魅力就是,我们不需要知道背后具体操作的是哪一个具体的class,虽然它们各自功能不同,只要它们都是实现了同一个interface,那么使用起来就相互没什么两样。

在物理现实中,我们可以把多态想象成“按按钮”——是个人就知道怎么去触碰一个按钮,尽管不同的按钮功能不一样,但只要它是个按钮,操作起来就非常简单。

同样的,在编程世界里,多态形式可以让我们的程序更加地模块化,易于扩展,而不是到处都是基于不同状态的条件判断,比如动不动就switch,动不动就层层嵌套if判断,这种情况下,十有八九是你的代码“有问题”了。

Interfaces(广义上的接口)

多态里面很关键的一环就是接口,广义上的接口包括interfaceabstract class

interface

interface里可以定义方法名及相应参数,任何实现这个interface的类必须具体实现interface里定义的所有抽象方法,一个class可以实现多个interface

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}
// 正确的做法
class MyClass implements MyInterface {
    protected $name;
    public function doThis() {
        // code that does this
    }
    public function doThat() {
        // code that does that
    }
    public function setName($name) {
        $this->name = $name;
    }
}
 
// 无效的做法
class MyClass implements MyInterface {
    // 缺少 doThis()方法!
 
    private function doThat() {
        // 这个方法必须也是public!
    }
    public function setName() {
        // 缺少 name 参数!
    }
}

Abstract Class

Abstract Class可以说是介于interface和普通class之间,它既可以通过abstract method的形式定义统一的接口,又可以定义具体的功能实现。一个扩展了该Abstract Class的普通class,必须得具体实现该Abstract Class的所有抽象方法。

abstract class MyAbstract {
    public $name;
    public function doThis() {
        // do this
    }
    abstract public function doThat();
    abstract public function setName($name);
}

Step 1: 面临的问题

假设你有一个articleclass:

class Article {
    public $title;
    public $author;
    public $date;
    public $category;
 
    public function  __construct($title, $author, $date, $category = 0) {
        $this->title = $title;
        $this->author = $author;
        $this->date = $date;
        $this->category = $category;
    }
}

现在呢,你想添加一个方法,来以不同的形式输出article相关的信息,比如说XML格式,或者说JSON格式。

可能你一开始会想着这么来处理:

class Article {
    //...
    public function write($type) {
        $ret = '';
        switch($type) {
            case 'XML':
                $ret = '<article>';
                $ret .= '<title>' . $obj->title . '</title>';
                $ret .= '<author>' . $obj->author . '</author>';
                $ret .= '<date>' . $obj->date . '</date>';
                $ret .= '<category>' . $obj->category . '</category>';
                $ret .= '</article>';
                break;
            case 'JSON':
                $array = array('article' => $obj);
                $ret = json_encode($array);
                break;
        }
        return $ret;
    }
}

虽然功能上能实现效果,但是看上去很糟糕,不是吗?假设,将来你又想加上其他的格式,那该怎么办?再加几个case判断,这代码得多臃肿呢?

关于面向对象,有一个很重要的原则就是,一个class应该只做份内之事。每当你遇到大块的条件判断的时候,你就应该有所警醒,因为很可能这个时候你已经在同一个class或method下,硬要去做太多的事情了。那么这个时候,也就是该尝试多态实现了。

Step 2: 定义 Interface

interface Writer {
    public function write(Article $obj);
}

Step 3: 具体实现 Interface

XMLWriter可以这样来实现:

class XMLWriter implements Writer {
    public function write(Article $obj) {
        $ret = '<article>';
        $ret .= '<title>' . $obj->title . '</title>';
        $ret .= '<author>' . $obj->author . '</author>';
        $ret .= '<date>' . $obj->date . '</date>';
        $ret .= '<category>' . $obj->category . '</category>';
        $ret .= '</article>';
        return $ret;
    }
}

JSONWriter:

class JSONWriter implements Writer {
    public function write(Article $obj) {
        $array = array('article' => $obj);
        return json_encode($array);
    }
}

这样每一种个的class只负责各自的那一件事。

Step 4: 具体调用

class Article {
    //...
    public function write(Writer $writer) {
        return $writer->write($this);
    }
}

这样articlewrite方法接收的是一个实现了Writer这个interface的具体类,article不再需要关注具体该用什么样的格式,那已经不是它要负责的了,交给背后具体的Writer去处理就好了。

至于怎么传一个具体的writer进去,这个就取决于你的使用情境了,比如你可以用一个factory class来这样操作:

class Factory {
    public static function getWriter() {
        // 获取request参数
        $format = $_REQUEST['format'];
				
        // 形成相应的class name,并检查该class是否存在
        $class =  $format . 'Writer';
        if(class_exists($class)) {
            // 返回一个具体实例
            return new $class();
        }
        // 否则的话抛出异常
        throw new Exception('Unsupported format');
    }
}

Step 5: 终端调用

假设最终在controller里就可以这样来调用:

$article = new Article('Polymorphism', 'Steve', time(), 0);
 
try {
    $writer = Factory::getWriter();
}
catch (Exception $e) {
    $writer = new XMLWriter();
}
 
echo $article->write($writer);

当然取决于你最终使用的框架,往往实际上比这个要更简单一些。

这里呢只是展示了多态的一种应用案例,供大家初步了解interface的益处和必要性:interface就像一个“蓝图”,基于这个“蓝图”,你可以放心地去实现多种形态的功能,从而使你的程序更加地模块化、易于扩展。

最后,可能我们更多关注的是在laravel里面,如何更好地面向interface,如何通过interface来提高多人团队开发效率,如何使我们更轻松地应对不断改变的功能需求,等等。关于这些,我们将继续在《Laravel底层核心技术实战揭秘》这一课程里深入探索。