类的组合与继承

来源:互联网 发布:秒赞网源码 编辑:程序博客网 时间:2024/05/16 06:20

在“对象与类”那篇博客中,我们定义了两个类,一个是person,一个是family;在family类中创建person类中的对象,把这个对象视为family类的一个属性,并调用它的方法处理问题,这种复用方法叫“组合”。还有一种复用方式,就是继承。

类与类之间有一种父与子的关系,子类继承父类的属性和方法,称为继承。在继承里,子类拥有父类的方法和属性,同事子类也可以有自己的方法和属性。

我们把那一篇博客中的组合代码用继承来实现,代码如下:

<?php/** * Created by PhpStorm. * User: lightWay * Date: 2017/2/9 * Time: 18:24 */class Person{    public $name = 'Tom';    public $gender;    static $money = 10000;    public function __construct()    {        echo '这就是父类',PHP_EOL;    }    public function say()    {        echo $this->name,"\tis",$this->gender,"\r\n";    }}class Family extends Person{    public $name;    public $gender;    public $age;    static $money = 100000;    public function __construct()    {        parent::__construct(); //调用父类的构造方法        echo '这里是子类',PHP_EOL;    }    public function say()    {        parent::say();        echo $this->name,"\tis\t",$this->gender,"and is\t",$this->age,PHP_EOL;    }    public function cry()    {        echo parent::$money,PHP_EOL;        echo '%>_<%',PHP_EOL;        echo self::$money,PHP_EOL;        echo '(*^_^*)';    }}$poor = new Family();$poor->name = 'Lee';$poor->gender = 'female';$poor->age = 25;$poor->say();$poor->cry();
运行上面的代码,可以得到如下图所示的结果:



从上面代码中可以了解继承的实现。在继承中,用parent指代父类,用self指代自身。使用 “::” 运算符(范围解析操作符)调用父类的方法。“::” 操作符还用来作为类常量和静态方法的调用,不要把这两种应用混淆。

既然提到了静态,就需要强调一点,如果声明类成员或方法为static,就可以不实例化类而直接访问,同时也就不能通过一个对象访问其中的静态成员(静态方法除外),也不能用 “::” 访问一个非静态方法。比如,把上例中的$poor->cry();换成$poor::cry(),按照这个规律,应该是要报错的。可能试验时,并没有报错,而且能够正确输出。这是因为用“::”方式调用一个非静态方法会导致一个E_STRICT级别的错误,而这里的PHP设置默认没有开启这个级别的报错提示。打开PHP安装目录下的php.ini文件,设置如下:

error_reporting = E_ALL|E_STRICTdisplay_errors = On
再次运行,就会看到错误提示。因此,用 “::” 访问一个非静态方法不符合语法,但PHP仍然能够正确地执行代码,这只是PHP所做的一个“兼容”或者说“让步”。在开发时,设置最严格的报错等级,在部署时可适当调低

组合与继承都是提高代码可重用性的手段。在设计对象模型时,可以按照语义识别类之间的组合关系和继承关系。比如,通过一些总结,得出了继承是一种“是、像”的关系,而组合是一种 “需要” 的关系。利用这条规律,就可以很简单地判断出父亲与儿子应该是继承关系,父亲与家庭应该是组合关系。还可以从另外一个角度看,组合偏重于整体与局部的关系,而继承偏重于父与子的关系,如下图所示:

从方法复用角度考虑,如果两个类具有很多相同的代码和方法,可以从这两个类中抽象出一个父类,提供公共方法,然后两个类作为子类,提供个性方法。这时用继承语意更好。继承的UML图如下图所示:


而组合就没有这么多限制。组合之间的类关系(体现为复用代码)很小,甚至没有关系,如下图所示:


然而在编程中,继承与组合的取舍旺旺并不是这么直接明了,很难说说二者是“像”的关系还是“需要”的关系,甚至把它拿到现实世界中建模,还是无法决定应该是继承还是组合。那应该怎么办呢?有什么标准码?的确有,这个标准就是“低耦合”。

耦合是一个软件结构内不同模块之间互连程度的度量,也就是不同模块之间的依赖关系。

低耦合指模块与模块之间,尽可能地使模块之间独立存在;模块与模块之间的接口尽量少而简单。现代的面向对象的思想不强调为真实世界建模,变得更理性化一些,把目标放到解耦上。

解耦是要解除模块与模块之间的依赖。

按照这个思想,继承与组合二者语义上难以区分,在二者均可使用的情况下,更倾向于使用组合。为什么呢?继承存在什么问题呢?

  1. 继承破坏封装性。

    比如,定义鸟类为父类,具有羽毛属性和飞翔方法,其子类天鹅、鸭子、鸵鸟等继承鸟这个类。显然,鸭子和鸵鸟不需要飞翔这个方法,但作为子类,他们却可以无区别地使用飞翔这个方法,显然破坏了类的封装性。而组合,从语义上来说,要优于继承。

  2. 继承是紧耦合的。

    继承使得子类和父类捆绑在一起。组合仅通过唯一接口和外部进行通信,耦合度要低于继承。

  3. 继承扩展复杂

    随着继承层数的增加和子类的增加,讲涉及及大量方法重写。使用组合,可以根据类型约束,实现动态组合,减少代码。

  4. 不恰当地使用继承可能违反现实世界中的逻辑。

    比如,人作为父类,雇员、经理、学生作为子类,可能存在这样的问题,经理一定是雇员,学生也可能是雇员,而使用继承的话,一个人就无法拥有多个角色。这种问题归结起来就是“角色”和“权限”问题。在权限系统中很可能存在这样的问题,经理权利和职位大于主管,但出于分工和安全的考虑,经理没有权限直接操作主管所负责的资源,技术部经理也没权限直接命令市场部主管。这就要求角色和权限几桶的设计要更灵活。不恰当的继承可能导致逻辑混乱,而使用组合就可以较好地解决这个问题。

当然,组合并非没有缺点。在创建组合对象时,组合需要一一创建局部对象,这一定程度上增加了一些代码,而继承则不需要这一步,因为子类自动有了父类的方法,代码如下所示:

<?php/** * Created by PhpStorm. * User: lightWay * Date: 2017/2/10 * Time: 16:16 */class Car{    public function addoil()    {        echo "Add oil \r\n";    }}//继承实现class Bmw extends Car{}//组合实现class Benz{    public $car;    public function __construct()    {        $this->car = new Car();    }    public function addoil()    {        $this->car->addoil();    }}$bmw = new Bmw();$bmw->addoil();$benz = new benz();$benz->addoil();

显然,组合比继承增加了代码量。组合还有其他的一些缺点,不过总体来说,是优点大于缺点。

继承最大的有点事扩展简单,但是其缺点大于优点,所以在设计时,需要慎重考虑。那应该如何使用继承呢?

  • 精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不要多于三层。
  • 对于不是专门用于被继承的类,禁止其被继承,也就是使用final修饰符。使用final修饰符既可以防止重要方法被非法覆写,又能给编辑器寻找优化的机会。
  • 优先考虑用组合关系提高代码的可重用性。
  • 子类是一种特殊的类型,而不只是父类的一个角色。
  • 子类扩展,而不是覆盖或者使父类的功能失效。
  • 底层代码多用组合,顶层/业务层代码多用继承。底层用组合可以提高效率,避免臃肿。顶层代码用继承可以提高灵活性,让业务使用更方便。
继承并非一无是处,而组合也不是完美无缺的。如果既要组合的灵活,又要继承的简洁,可以做到吗?

当然可以,譬如多重继承,就具有这个特性。多重继承里一个类可以同时继承多个父类,组合两个父类的功能。C++里就是使用的这种模型来增强继承的灵活性的,但是多重继承过于灵活,并且会带来“菱形问题”,故为其使用带来了不少困难,模型变得复杂起来,因此在大多数语言中,都放弃了多重继承这一模型。

多重继承太复杂,那么还有其他方式能比较好地解决这个问题吗?PHP5.4引入的新的语法结构Traits就是一种很好的解决方案。Traits的思想来源于C++和Ruby里的Mixin以及Seala里的Traits,可以方便我们实现对象的扩展,是除extend、implement外的另外一种扩展对象的方式。Traits既可以使单继承模式的语言获得多重继承的灵活,又可以避免多重继承带来的种种问题。

0 0