类的组合与继承
定义两个类,一个是person,一个是family;在family类中创建person类中的对象,把这个对象视为family类的一个属性,并调用它的方法处理问题,这种复用方式叫:“组合”。
类与类之间有一种父与子的关系,子类继承父类的属性和方法,称为继承。
在继承里,子类拥有父类的方法和属性,同时子类也可以有自己的方法和属性。
<?php class person{ public $name; public $gender; public function say(){ echo $this->name,"\tis",$this->gender,"\r\n"; } } class family{ public $people; public $location; public function __construct($p,$loc){ $this->people = $p; $this->location = $loc; } } $student = new person(); $student->name = 'Tom'; $student->gender = 'male'; $student->say(); $tom = new family($stduent,'peking'); print_r($tom); /* family object { [people] => person object { [name] => Tom [gender] => male } [location] => peking } */
<?php class person{ public $name = 'Tom'; public $gender; static $momery = 10000; public function __construct(){ echo '这里是父类'; } public function say(){ echo $this->name,"\tis",$this->gender."\r\n"; } } class family extends person{ public $name; public $gender; public $age; static $monery = 10000; public function __construct(){ parent::__construct(); // 调用父类构造方法 echo '这里是子类',PHP_EOL; } public function say(){ parent::say(); echo $this->name,"\title\t",$this->gender,",and is\t",$this->age,PHP_EOL; } public function cry(){ echo parent::$momery,PHP_EOL; echo self::$momery,PHP_EOL; //调用自身构造方法 } } $poor = new family(); $poor->name = 'Lee'; $poor->gender = 'female'; $poor->age = 25; $poor->say(); $poor->cry();
在继承中,用 parent 指代父类,用 self 指代自身。使用“::”运算符(范围解析操作符)调用父类的方法。“::”操作符还用来作为类常量和静态方法的调用,不要把这两种应用混淆。
组合与继承都是提高代码可重用性的手段。在设计对象模型时,可以按照语义识别类之间的组合关系和继承关系。
通过一些总结,得出了继承是一种“是,像”的关系,而组合是一种“需要”的关系。
利用这条规律,就可以很简单地判断出父类与儿子应该是继承关系,而父亲与家庭应该是组合关系。还可以从另一个角度看,组合偏重整体与局部的关系,而继承偏重父与子的关系。
然而在编程中,继承与组合的取舍往往并不是这么直接明了,很难说出二者是“像”的关系还是“需要”的关系,甚至把它拿到现实世界中建模,还是无法决定应该是继承还是组合。
那应该怎么办呢?有标准吗?有的,这个标准就是“低耦合”。
耦合是一个软件结构内不同模块之间互连程度的度量,也就是不同模块之间的依赖关系。
低耦合指模块与模块之间,尽可能地使模块间独立存在;模块与模块之间的接口尽量少而简单。
解耦是要解除模块与模块之间的依赖。
按照这个思想,继承与组合二者语义上难以区分,在二者均可使用的情况下,更倾向于使用组合。为什么呢?继承存在什么问题吗?
1.继承破坏封装性
比如,定义鸟类为父类,具有羽毛属性和飞翔方法,其子类天鹅、鸭子、鸵鸟等继承鸟这个类。显然,鸭子和鸵鸟不需要飞翔这个方法,但作为子类,它们却可以无区别的使用飞翔这个方法,这显然破坏了类的封装性。而组合,从语义上来说,要优于继承。
2.继承是紧耦合的
继承使得子类和父类捆绑在一起。组合仅通过唯一接口和外部进行通信,耦合度低于继承。
3.继承扩展复杂
随着继承层数的增加和子类的增加,将涉及大量方法重写。使用组合,可以根据类型约束,实现动态组合,减少代码。
4.不恰当地使用继承可能违反现实世界中的逻辑
比如人类作为父类,雇员、经理、学生作为子类,可能存在这样的问题,经理一定是雇员,学生也可能是雇员,而是用继承的话一个人就无法拥有多个角色。这种问题归结起来就是“角色”和“权限”问题。在权限系统中可能存在这样的问题,经理权利和职位大于主管,但出于分工和安全的考虑,经理没有权利直接操作主管所负责的资源,技术部经理也没权限直接命令市场部主管。这就要求角色和权限系统的设计要更灵活。不恰当的继承可能导致逻辑混乱,而实用组合就可以较好地解决这个问题。
当然,组合并非没有缺点。在创建组合对象时,组合需要一一创建局部对象,这一定程度上增加了一些代码,而继承则不需要这一步,因为子类自动有了父类的方法。
继承最大的优点就是扩展简单,但是其缺点大于优点,所以在设计时,需要慎重考虑。
- 精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不要多于三层。
- 对于不是专门用于被继承的类,禁止其被继承,也就是使用 final 修饰符。使用 final 修饰符既可防止重要方法被非法覆写,又能给编辑器寻找优化的机会。
- 优先考虑用组合关系提高代码的可重用性。
- 子类是一种特殊的类型,而不只是父类的一个角色。
- 子类扩展,而不是覆盖或者使父类的功能失效。
- 底层代码多用组合,顶层/业务层代码多用继承。底层用组合可以提高效率,避免对象臃肿。顶层代码用继承可以提高灵活性,让业务使用更方便。
继承并非一无是处,而组合也不是完美无缺的。如果既要组合的灵活,又要继承的代码简洁,能做到吗?
这是可以做到的,譬如多重继承,就具有这个特性。多重继承里一个类可以同时继承多个父类,组合两个父类的功能。
可是多重继承太复杂了,那么还有其他方式比较好地解决这个问题吗?PHP5.4引入的新的语法结构Traits,可以方便我们实现对象的扩展,是除 extend、implements 外的另外一种扩展对象方式。
Traits 既可以使单继承模式的语言获得多重继承的灵活,又可以避免多重继承带来的问题。