PHP面向对象继承用法详解:从代码复用到封装优化

陆景行
陆景行
发布时间:2026-10-13| 5人浏览过
原创

在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面向对象设计的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。