PHP面向对象继承用法详解:从代码复用到封装优化
在PHP开发中,处理具有相似特征但又有细微差异的对象时,直接复制粘贴代码会导致严重的维护负担。本文将深入探讨如何利用继承机制重构代码,从基础的子类化逐步演进至完整的封装设计。通过具体的类实例对比,演示如何抽离公共逻辑、覆盖特有行为、安全调用父级方法,并最终通过私有化属性保障数据完整性。
继承解决的问题与初始现状
在面向对象编程(OOP)中,extends 关键字用于实现继承,允许子类复用父类的属性和方法。如果多个类存在大量相同的属性定义和业务逻辑,这种重复不仅浪费资源,也增加了出错的概率。
第1步:观察重复的类设计
假设我们需要管理两种商品:CD产品和图书产品。它们都有标题、制作人信息和价格,但CD有播放时长,图书有页数。
在不使用继承的原始写法中,两个类几乎完全独立,代码如下:
<?php
class CdProduct {
public $playLength; // 播放时间
public $title;
public $producerMainName;
public $producerFirstName;
public $price;
function __construct( $title, $firstName,
$mainName, $price,
$playLength ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->playLength = $playLength;
}
function getPlayLength() {
return $this->playLength;
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
$base .= ": playing time - {$this->playLength}";
return $base;
}
function getProducer() {
return "{$this->producerFirstName}" . " {$this->producerMainName}";
}
}
class BookProduct {
public $numPages; // 看的页数
public $title;
public $producerMainName;
public $producerFirstName;
public $price;
function __construct( $title, $firstName,
$mainName, $price,
$numPages ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
}
function getNumberOfPages() {
return $this->numPages;
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
$base .= ": page count - {$this->numPages}";
return $base;
}
function getProducer() {
return "{$this->producerFirstName}" . " {$this->producerMainName}";
}
}
$product1 = new CdProduct("cd1", "bob", "bobbleson", 4, 50 );
print $product1->getSummaryLine();
?>
可以看到,CdProduct 和 BookProduct 拥有高度重合的代码块,特别是标题组合和制作人获取逻辑。这正是引入继承的最佳契机。
采用继承进行初步重构
我们可以创建一个通用的父类 ShopProduct,将共有的属性和方法转移进去。子类通过使用 extends 关键字继承父类,并仅保留自身的差异化逻辑。
第2步:建立共享父类与子类继承
我们将共有字段(如 $title, $price 等)移至 ShopProduct,并为可选参数设置默认值。
<?php
class ShopProduct {
public $numPages;
public $playLength;
public $title;
public $producerMainName;
public $producerFirstName;
public $price;
function __construct( $title, $firstName,
$mainName, $price,
$numPages=0, $playLength=0 ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
$this->numPages = $numPages;
$this->playLength = $playLength;
}
function getProducer() {
return "{$this->producerFirstName}" . " {$this->producerMainName}";
}
function getSummaryLine() {
$base = "$this->title ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
return $base;
}
}
class CdProduct extends ShopProduct {
function getPlayLength() { // 增加属于自己的方法
return $this->playLength;
}
function getSummaryLine() { // 改造了父类的方法
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
$base .= ": playing time - {$this->playLength}";
return $base;
}
}
class BookProduct extends ShopProduct {
function getNumberOfPages() {
return $this->numPages;
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
$base .= ": page count - {$this->numPages}";
return $base;
}
}
?>
此时,两个子类的初始化调用方式略有变化,需要传入对应的特定参数。
进一步优化:精准分配属性与方法
上一步虽然使用了继承,但父类仍保留了不相关的属性(如 CD 不应该关心页数)。更严谨的做法是将共有属性集中在父类构造函数中处理,将独有属性留给子类。
第3步:隔离公共与特有逻辑
父类只负责自己该管的部分。子类在构造时需显式调用 parent::__construct() 来完成父类部分的初始化。
<?php
class ShopProduct {
// 抽离出共有属性
public $title;
public $producerMainName;
public $producerFirstName;
public $price;
function __construct( $title, $firstName,
$mainName, $price ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
}
function getProducer() {
return "{$this->producerFirstName}" . " {$this->producerMainName}";
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
return $base;
}
}
class CdProduct extends ShopProduct {
// 抽离出属于自己特有的属性
public $playLength;
function __construct( $title, $firstName,
$mainName, $price, $playLength ) {
parent::__construct( $title, $firstName,
$mainName, $price ); // 继承父类的构造函数
$this->playLength = $playLength; // 处理自己专有的属性
}
function getPlayLength() {
return $this->playLength;
}
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
$base .= ": playing time - {$this->playLength}";
return $base;
}
}
class BookProduct extends ShopProduct {
public $numPages;
function __construct( $title, $firstName,
$mainName, $price, $numPages ) {
parent::__construct( $title, $firstName,
$mainName, $price );
$this->numPages = $numPages;
}
function getNumberOfPages() {
return $this->numPages;
}
function getSummaryLine() {
$base = "$this->title ( $this->producerMainName, ";
$base .= "$this->producerFirstName )";
$base .= ": page count - $this->numPages";
return $base;
}
}
?>
进阶技巧:调用父类方法
子类在覆盖(Override)父类方法时,往往需要在父类原有结果的基础上追加自己的逻辑。例如 getSummaryLine 在生成基础信息后,再添加时长或页数。与其重新写一遍拼接逻辑,不如直接复用父类的这个方法。
第4步:使用 parent:: 调用父类函数
这种方式减少了代码行数,并确保如果父类的基础展示格式变更,子类会自动同步。
<?php
class ShopProduct {
// ... (同上省略非变动部分)
function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
return $base;
}
}
class CdProduct extends ShopProduct {
// ... 构造函数同上
function getSummaryLine() {
$base = parent::getSummaryLine(); // 直接获取父类组装好的基础字符串
$base .= ": playing time - {$this->playLength}";
return $base;
}
}
class BookProduct extends ShopProduct {
// ... 构造函数同上
function getSummaryLine() {
$base = parent::getSummaryLine();
$base .= ": page count - {$this->numPages}";
return $base;
}
}
?>
安全性升级:属性私有化与多态表现
良好的 OOP 设计应当尽量隐藏内部状态。当属性被设为 private 后,外部无法直接访问,必须通过公有的 Getter/Setter 方法。此外,子类可以重新定义特定的业务逻辑(如打折规则),体现对象的多态性。
第5步:实施私有化与重写价格计算逻辑
此处演示:ShopProduct 定义了基于折扣的计算逻辑,而 BookProduct 因为定价策略不同,重写了 getPrice 方法来忽略折扣。
<?php
class ShopProduct {
private $title;
private $discount = 0;
private $producerMainName;
private $producerFirstName;
protected $price; // 供子类访问
public function __construct( $title, $firstName,
$mainName, $price ) {
$this->title = $title;
$this->producerFirstName = $firstName;
$this->producerMainName = $mainName;
$this->price = $price;
}
public function setDiscount( $num ) {
$this->discount=$num;
}
public function getPrice() {
return ($this->price - $this->discount);
}
public function getSummaryLine() {
$base = "{$this->title} ( {$this->producerMainName}, ";
$base .= "{$this->producerFirstName} )";
return $base;
}
}
class CdProduct extends ShopProduct {
private $playLength = 0;
// 构造函数略...
public function getPlayLength() {
return $this->playLength;
}
public function getSummaryLine() {
$base = parent::getSummaryLine();
$base .= ": playing time - {$this->playLength}";
return $base;
}
}
class BookProduct extends ShopProduct {
private $numPages = 0;
// 构造函数略...
public function getPrice() {
// 重写父类方法:书籍不享受此类折扣
return $this->price;
}
public function getNumberOfPages() {
return $this->numPages;
}
public function getSummaryLine() {
$base = parent::getSummaryLine();
$base .= ": page count - {$this->numPages}";
return $base;
}
}
// 测试
$product1 = new CdProduct("cd1", "bob", "bobbleson", 4, 50 );
$product1->setDiscount( 3 );
print $product1->getSummaryLine();
print "\n";
print "price: {$product1->getPrice()}\n";
$product2 = new BookProduct("book1", "harry", "harrelson", 4, 30 );
$product2->setDiscount( 3 );
print $product2->getSummaryLine();
print "\n";
print "price: {$product2->getPrice()}\n";
?>
运行结果:
cd1 ( bobbleson, bob ): playing time - 50
price: 1
book1 ( harrelson, harry ): page count - 30
price: 4
如上所示,CD应用了折扣(4-3=1),而Book因为重写了 getPrice,忽略了父类的折扣设置,保持了原价。这体现了面向对象设计中“开闭原则”的精神:对扩展开放,对修改封闭。
总结
通过层层递进的代码重构,我们看到了 PHP 继承的强大之处。它不仅是代码复用的工具,更是构建清晰层级关系、规范数据访问的安全手段。掌握 extends、parent:: 调用、以及属性可见性修饰符(public/private/protected)的配合使用,是写出高质量 PHP OOP 代码的关键。以上就是PHP面向对象继承用法详解(优化与减少代码重复)的详细内容,更多关于PHP面向对象设计的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









