理解和运用PHP中的多态性

在面向对象编程,多态性是一个强大的和基本的工具。它可以用来在您的应用程序建立一个更有机的流。本教程将介绍多态性的笼统的概念,以及它如何在PHP中可以很容易地部署。

什么是多态性?

Polymorphism(多态性)是一个很长的单词,但是它表示的是一个非常简单的概念。
多态性描述了在面向对象编程模式中类有不同的功能,而共享一个通用的接口。

多态性的优点是,并不需要知道它使用的是哪一个类,因为他们都用同样的方式与不同的类的代码工作。

可将多态性类比成现实世界的一个按钮。大家都知道如何使用一个按钮:您只需给它施加压力。一个按钮“确实是这样”,然而,取决于它和什么连接和使用它的上下文 – 但结果并不影响它是如何使用。如果你的老板告诉你按下一个按钮,您已经有执行任务所需的所有信息。
在编程的世界中,多态性是用来使应用程序更加模块化和可扩展的。相比于凌乱的条件语句描述的行动不同的课程,您可以创建根据您的需求选择的互换对象。这是多态性的基本目标。

Interfaces接口
接口是与类(class)类似的,除了它不能包含代码。接口可以定义方法名称和参数,但不是方法的内容。实现接口的任何类都必须实现接口中定义的所有方法。一个类可以实现多个接口。

使用的“interface”关键字声明一个接口:

1
2
3
interface MyInterface {
    // methods
}

被附加到一个类,使用“implements”关键字(多个接口,可以通过用逗号分开):

1
2
3
class MyClass implements MyInterface {
    // methods
}

在接口中可以像在类中一样定义方法,除了没有方法体(在大括号中的部分)以外。

1
2
3
4
5
interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}

在这里定义的所有方法都必须如接口中所描述地那样被包含在任何实现它的类中。(读下面的代码注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//合法的 VALID
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;
    }
}

// 非法的INVALID
class MyClass implements MyInterface {
    // missing doThis()!

    private function doThat() {
        // this should be public!
    }
    public function setName() {
        // missing the name argument!
    }
}

抽象类Abstract Class
抽象类是接口和类的混合。它可以像接口一样定义方法。继承自抽象类的类必须实现抽象类中定义的所有抽象方法。
抽象类的定义方式与类一样,不过是在前面附加了一个abstract 关键字。

1
2
3
abstract class MyAbstract {
    // methods
}

并且 是用 ‘extends‘ 关键字附加到类:

1
2
3
class MyClass extends MyAbstract {
    // class methods
}

就像在普通类中一样,普通的方法以及任何抽象方法(使用关键字“abstract”)可以在抽象类中定义。抽象方法的行为就像在接口中定义的的方法,而且继承它的扩展类中必须实现完全一样的定义。

1
2
3
4
5
6
7
8
abstract class MyAbstract {
    public $name;
    public function doThis() {
        // do this
    }
    abstract public function doThat();
    abstract public function setName($name);
}

Step 1: 确定问题所在 Identify The Problem
我们假设你有一个文章Article类负责管理你网站上的文章。它包含关于文章的信息,包括:title, author, date, and category.
就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
class poly_base_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;
    }
}

注意:在这个教程中的示例类使用了“package_component_Class”的命名约定,这是一个用来将类名分隔到虚拟的命名空间来避免命名冲突的通用方法。

现在你想添加一个方法来输出各种不同格式的信息,如XML和JSON。你也许会打算像下面这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class poly_base_Article {
    //...
    public function write($type) {
        $ret = '';
        switch($type) {
            case 'XML':
                $ret = '<article>';
                $ret .= '';
                $ret .= '<author>' . $obj->author . '</author>';
                $ret .= '<date>' . $obj->date . '</date>';
                $ret .= '';
                $ret .= '</article>';
                break;
            case 'JSON':
                $array = array('article' => $obj);
                $ret = json_encode($array);
                break;
        }
        return $ret;
    }
}

这种解决方案是丑陋的,但是它是可靠的——至少在现在。问下你自己将来会发生什么,当我们需要添加更多格式的时候?你可以继续编辑这个类,然后添加越来越多的case ,但是现在你只是在稀释(FIX ME: diluting) 你的类。

OOP的一个重要原则是一个类应该做的一件事情,而应该把它做好。

考虑到这一点,条件语句应该是一个红色的标志,表明你的类是试图做太多不同的东西。这是多态性的用武之地。
在我们的例子中,有两个任务明确的提出:管理文章和格式化其数据。在本教程中,我们将重构我们的格式化代码到一个新的类,然后我们会发现使用多态性是多么容易。

Step 2: 定义你的接口 Define Your Interface
第一件事就是我们应该定义接口,努力想好怎么定义你的接口是件重要的事情,因为对它的任何改变都将需要改动调用它的代码。
在我们这个例子中,我们将使用一个简单的接口来定义一个方法:

1
2
3
interface poly_writer_Writer {
    public function write(poly_base_Article $obj);
}

它就是那样简单,我们已经定义了一个公用方法write() ,它接受一个文章对象作为参数。任何实现Writer接口的类都将会确保有这个方法。
小提示:如果你想严格限制传递给你的方法和函数的参数类型,你可以使用类型提示,就像我们已经在write()方法中做的一样,它只能接受poly_base_Article 对象类型的数据。不幸的是,在目前版本的PHP中,返回类型提示是不被支持的,所以,你要小心返回值的类型了。
Step 3: 创建实现类 Create Your Implementation

定义接口后,该是时候来创建类来真正干活的啦。在我们的例子中,我们需要输出两种格式。这样,我们就要两个Writer 类:XMLWriter 和 JSONWriter 。从传递过来的Article 对象提取数据然后格式化这些信息完全取决于这些类了。
下面是 XMLWriter 类的一个示例:

1
2
3
4
5
6
7
8
9
10
11
class poly_writer_XMLWriter implements poly_writer_Writer {
    public function write(poly_base_Article $obj) {
        $ret = '<article>';
        $ret .= '';
        $ret .= '<author>' . $obj->author . '</author>';
        $ret .= '<date>' . $obj->date . '</date>';
        $ret .= '';
        $ret .= '</article>';
        return $ret;
    }
}

就如你可以从类定义中看到的一样,我们使用implements关键字来实现我们的接口。write() 方法包含格式化为XML的功能。

现在我们来看下JSONWriter 类:

1
2
3
4
5
6
class poly_writer_JSONWriter implements poly_writer_Writer {
    public function write(poly_base_Article $obj) {
        $array = array('article' => $obj);
        return json_encode($array);
    }
}

现在,我们的代码中的特定每种格式都包含在单独的类。每个类有全权负责处理特定的格式,而不是其他。您的应用程序中没有其他部分需要关心这些是如何工作的才能使用它,感谢我们的接口。

Step 4: 使用你的接口Use Your Implementation

在我们的新类定义后,该是时候来重温一下我们的Article类了,所有原write() 方法中的代码已经被分离出来,进入到我们的新类中了。我们的所有方法现在需要做的就是使用这些新的类,像这样:

1
2
3
4
5
6
class poly_base_Article {
    //...
    public function write(poly_writer_Writer $writer) {
        return $writer->write($this);
    }
}

获取一个 Writer对象 Obtaining A Writer

你也许会疑惑你该从哪里获取一个 Writer对象开始,因为你需要传递一个 Writer对象到这个方法。
这完全取决于你,并且,有很多策略。如,你可能会使用工厂类来获取请求数据然后创建一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class poly_base_Factory {
    public static function getWriter() {
        // grab request variable
        $format = $_REQUEST['format'];
        // construct our class name and check its existence
        $class = 'poly_writer_' . $format . 'Writer';
        if(class_exists($class)) {
            // return a new Writer object
            return new $class();
        }
        // otherwise we fail
        throw new Exception('Unsupported format');
    }
}

就像我说的,根据你的需求,有好多其它策略可用。在这个例子中,通过一个请求变量选择哪种格式是要使用的。它基于request请求变量来构造一个类名,检测它是否存在,然后返回一个新的Writer对象。如果没有那个名字的类存在,抛出一个异常,让客户端代码决定接下来要干什么。

Step 5: 把它们放一起 Put It All Together

当所有东东都到位了,下面是我们的客户端代码如何放在一起:

1
2
3
4
5
6
7
8
9
10
$article = new poly_base_Article('Polymorphism', 'Steve', time(), 0);

try {
    $writer = poly_base_Factory::getWriter();
}
catch (Exception $e) {
    $writer = new poly_writer_XMLWriter();
}

echo $article->write($writer);

首先,我们创建了一个示例 Article 对象来配合工作。然后,我们试图从工厂Factory获取一个Factory对象,如果异常发生的话回滚到默认(XMLWriter) 。最后,我们传递Writer对象给我们的Article的 write() 方法,输出结果。

结论 Conclusion
在本教程中,我提供了一个多态性的简介而且解释了PHP中的接口。我希望你意识到,我只向您展示一个潜在的使用多态性的案例。多态性是以一个优雅的方式来避免您的OOP代码中丑陋的条件语句。它遵循的原则是使您的组件分离,而且它是许多设计模式的组成部分。如果您有任何问题,不要犹豫,在评论中提问!

译自:http://net.tutsplus.com/tutorials/php/understanding-and-applying-polymorphism-in-php/

更多
4 Responses Post a comment
  1. aron

    要不要换个友链 ? http://www.dahouduan.com

  2. aron

    Factory 那段最好不要把 Request 硬编码进去

  3. 荒野无灯

    理解和运用PHP中的多态性[译]...

    在面向对象编程,多态性是一个强大的和基本的工具。它可以用来在您的应用程序建立一个更有机的流。本教程将介绍多态性的笼统的概念,以及它如何在PHP中可以很容易地部署。 什么是多态性? Poly......

Leave a Reply

Note: You may use basic HTML in your comments. Your email address will not be published.

Subscribe to this comment feed via RSS