Fork me on GitHub

在PHP下理解和应用多态


后来我终于知道 , 它并不是我的花 ,我只是恰好途径了它的盛放。

说明

封装是类的构建过程,php具有;php也具有继承的特性。唯独这个多态,php体现的十分模糊。原因是php是弱类型语言。
java的多态体现的十分清晰,大体分两类:父类引用指向子类对象;接口引用指向实现接口的类对象。java声明变量时都要给变量设定类型,所以存在什么父类引用和接口引用。而php则没有这点体现,php声明变量不需要给变量设定类型,一个变量可以指向不同的数据类型。所以,php不具有像java一样的多态。

多态,作为面向对象编程中的一种设计模式,指的是通过遵循同一个interface,类可以有不同的功能实现(相当于说,有多种形态)。
在编程世界里,多态形式可以让我们的程序更加地模块化,易于扩展,而不是到处都是基于不同状态的条件判断,比如动不动就switch,动不动就层层嵌套if判断,这种情况下,十有八九是你的代码“有问题”了。

Interfaces

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

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
// 正确的做法
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

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

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);
}

问题的引出

假设你有一个articleclass:

1
2
3
4
5
6
7
8
9
10
11
12
13
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格式。
可能你一开始会想着这么来处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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下,硬要去做太多的事情了。那么这个时候,也就是该尝试多态实现了。

尝试解决

定义 Interface

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

具体实现 Interface

XMLWriter可以这样来实现:

1
2
3
4
5
6
7
8
9
10
11
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:

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

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

具体调用

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

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

http://pilishen.com/posts/Understanding-and-Applying-Polymorphism-in-PHP