第12章:面向对象基础

面向对象编程(Object-Oriented Programming,简称OOP)是现代编程中最重要的编程范式之一。它提供了一种组织和管理代码的方式,使程序更加模块化、可维护和可扩展。PHP从PHP 5开始完全支持面向对象编程。

学习目标

完成本章学习后,你将能够:

  • 理解面向对象编程的基本概念和优势
  • 掌握类和对象的定义与使用
  • 学会创建和使用属性和方法
  • 理解封装的概念和应用
  • 掌握构造函数和析构函数的使用
  • 学会基本的继承和扩展
  • 能够使用面向对象的方式构建应用程序

什么是面向对象编程

编程范式的演进

从过程式编程到面向对象编程的转变:

<?php
// 过程式编程方式
function calculateArea($width, $height) {
    return $width * $height;
}

function calculatePerimeter($width, $height) {
    return 2 * ($width + $height);
}

// 使用过程式编程
$width = 10;
$height = 5;

$area = calculateArea($width, $height);
$perimeter = calculatePerimeter($width, $height);

echo "面积:$area, 周长:$perimeter\n";
?>
<?php
// 面向对象编程方式
class Rectangle {
    private $width;
    private $height;

    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }

    public function getPerimeter() {
        return 2 * ($this->width + $this->height);
    }

    public function getWidth() {
        return $this->width;
    }

    public function getHeight() {
        return $this->height;
    }
}

// 使用面向对象编程
$rectangle = new Rectangle(10, 5);
$area = $rectangle->getArea();
$perimeter = $rectangle->getPerimeter();

echo "面积:$area, 周长:$perimeter\n";
?>

面向对象的优势

  1. 模块化:将数据和操作数据的方法封装在一起
  2. 可重用性:通过类和对象实现代码重用
  3. 可维护性:代码结构清晰,易于修改和维护
  4. 可扩展性:通过继承和接口轻松扩展功能
  5. 安全性:通过访问控制保护数据安全

面向对象的基本概念

1. 对象(Object)

对象是面向对象编程的基本单位,是类的实例。对象包含:

  • 属性(Properties):对象的数据或状态
  • 方法(Methods):对象的行为或操作

2. 类(Class)

类是对象的模板或蓝图,定义了对象的属性和方法。

3. 封装(Encapsulation)

将数据和操作数据的方法捆绑在一起,隐藏内部实现细节。

4. 继承(Inheritance)

一个类可以继承另一个类的属性和方法。

5. 多态(Polymorphism)

不同对象对同一消息的不同响应。

现实世界中的面向对象概念

为了更好地理解面向对象,让我们用现实世界的例子来说明:

<?php
// 汽车类
class Car {
    // 属性:汽车的特征
    private $brand;      // 品牌
    private $model;      // 型号
    private $color;      // 颜色
    private $speed;      // 当前速度
    private $isRunning;  // 是否在运行

    // 构造方法:创建汽车时初始化
    public function __construct($brand, $model, $color) {
        $this->brand = $brand;
        $this->model = $model;
        $this->color = $color;
        $this->speed = 0;
        $this->isRunning = false;

        echo "创建了一辆 {$this->color} 的 {$this->brand} {$this->model}\n";
    }

    // 方法:汽车的行为
    public function start() {
        if (!$this->isRunning) {
            $this->isRunning = true;
            echo "汽车启动了!\n";
        } else {
            echo "汽车已经在运行中!\n";
        }
    }

    public function stop() {
        if ($this->isRunning) {
            $this->speed = 0;
            $this->isRunning = false;
            echo "汽车停止了!\n";
        } else {
            echo "汽车已经停止了!\n";
        }
    }

    public function accelerate($amount) {
        if ($this->isRunning) {
            $this->speed += $amount;
            echo "加速到 {$this->speed} km/h\n";
        } else {
            echo "请先启动汽车!\n";
        }
    }

    public function brake($amount) {
        if ($this->speed >= $amount) {
            $this->speed -= $amount;
            echo "减速到 {$this->speed} km/h\n";
        } else {
            $this->speed = 0;
            echo "汽车已经停止\n";
        }
    }

    // 获取器方法
    public function getBrand() {
        return $this->brand;
    }

    public function getModel() {
        return $this->model;
    }

    public function getColor() {
        return $this->color;
    }

    public function getSpeed() {
        return $this->speed;
    }

    public function isRunning() {
        return $this->isRunning;
    }

    // 设置器方法
    public function setColor($color) {
        $this->color = $color;
        echo "汽车颜色改为 {$color}\n";
    }

    // 获取汽车信息
    public function getInfo() {
        $status = $this->isRunning ? "运行中" : "停止";
        return "这是一辆 {$this->color} 的 {$this->brand} {$this->model},当前{$status},速度 {$this->speed} km/h";
    }
}

// 使用汽车类
echo "=== 创建和使用汽车 ===\n";

$myCar = new Car("丰田", "卡罗拉", "白色");
echo $myCar->getInfo() . "\n\n";

$myCar->start();
$myCar->accelerate(50);
$myCar->accelerate(30);
$myCar->brake(20);
$myCar->stop();

echo "\n" . $myCar->getInfo() . "\n";
?>

面向对象 vs 过程式编程对比

让我们通过一个实际的例子来对比两种编程方式:

学生管理系统 - 过程式方式

<?php
// 数据存储
$students = [];

// 添加学生函数
function addStudent(&$students, $name, $age, $grade) {
    $student = [
        'id' => count($students) + 1,
        'name' => $name,
        'age' => $age,
        'grade' => $grade
    ];

    $students[] = $student;
    return count($students);
}

// 获取学生信息函数
function getStudent($students, $id) {
    foreach ($students as $student) {
        if ($student['id'] == $id) {
            return $student;
        }
    }
    return null;
}

// 更新学生年龄函数
function updateStudentAge(&$students, $id, $newAge) {
    foreach ($students as &$student) {
        if ($student['id'] == $id) {
            $student['age'] = $newAge;
            return true;
        }
    }
    return false;
}

// 计算平均年龄函数
function calculateAverageAge($students) {
    if (empty($students)) {
        return 0;
    }

    $totalAge = 0;
    foreach ($students as $student) {
        $totalAge += $student['age'];
    }

    return $totalAge / count($students);
}

// 使用过程式方式
echo "=== 过程式编程方式 ===\n";

$studentId1 = addStudent($students, "张三", 20, "一年级");
$studentId2 = addStudent($students, "李四", 19, "一年级");
$studentId3 = addStudent($students, "王五", 21, "二年级");

updateStudentAge($students, $studentId2, 20);

$student = getStudent($students, $studentId1);
if ($student) {
    echo "学生信息:{$student['name']}, {$student['age']}岁, {$student['grade']}\n";
}

$avgAge = calculateAverageAge($students);
echo "平均年龄:" . round($avgAge, 1) . "岁\n";
?>

学生管理系统 - 面向对象方式

<?php
class Student {
    private static $nextId = 1;
    private $id;
    private $name;
    private $age;
    private $grade;
    private static $students = [];

    public function __construct($name, $age, $grade) {
        $this->id = self::$nextId++;
        $this->name = $name;
        $this->age = $age;
        $this->grade = $grade;

        self::$students[$this->id] = $this;

        echo "创建了学生:{$this->name} (ID: {$this->id})\n";
    }

    public function getId() {
        return $this->id;
    }

    public function getName() {
        return $this->name;
    }

    public function setName($name) {
        $this->name = $name;
    }

    public function getAge() {
        return $this->age;
    }

    public function setAge($age) {
        if ($age > 0 && $age < 150) {
            $this->age = $age;
            return true;
        }
        return false;
    }

    public function getGrade() {
        return $this->grade;
    }

    public function setGrade($grade) {
        $this->grade = $grade;
    }

    public function getInfo() {
        return "学生 {$this->name},{$this->age}岁,{$this->grade}";
    }

    // 静态方法:获取所有学生
    public static function getAllStudents() {
        return self::$students;
    }

    // 静态方法:根据ID获取学生
    public static function getById($id) {
        return self::$students[$id] ?? null;
    }

    // 静态方法:计算平均年龄
    public static function calculateAverageAge() {
        if (empty(self::$students)) {
            return 0;
        }

        $totalAge = 0;
        foreach (self::$students as $student) {
            $totalAge += $student->getAge();
        }

        return $totalAge / count(self::$students);
    }

    // 静态方法:获取学生总数
    public static function getTotalCount() {
        return count(self::$students);
    }

    // 静态方法:根据年级获取学生
    public static function getByGrade($grade) {
        $gradeStudents = [];
        foreach (self::$students as $student) {
            if ($student->getGrade() === $grade) {
                $gradeStudents[] = $student;
            }
        }
        return $gradeStudents;
    }

    // 析构方法
    public function __destruct() {
        echo "学生对象 {$this->name} 已销毁\n";
    }
}

// 使用面向对象方式
echo "\n=== 面向对象编程方式 ===\n";

$student1 = new Student("张三", 20, "一年级");
$student2 = new Student("李四", 19, "一年级");
$student3 = new Student("王五", 21, "二年级");

// 修改学生信息
$student2->setAge(20);

// 获取学生信息
$foundStudent = Student::getById(1);
if ($foundStudent) {
    echo $foundStudent->getInfo() . "\n";
}

// 计算平均年龄
$avgAge = Student::calculateAverageAge();
echo "平均年龄:" . round($avgAge, 1) . "岁\n";

echo "学生总数:" . Student::getTotalCount() . "\n";

// 获取一年级学生
$grade1Students = Student::getByGrade("一年级");
echo "一年级学生数量:" . count($grade1Students) . "\n";
?>

面向对象的设计原则

1. 单一职责原则(SRP)

一个类应该只有一个引起变化的原因。

<?php
// 好的设计:职责分离
class Logger {
    public function log($message) {
        echo "[LOG] " . date('Y-m-d H:i:s') . " - $message\n";
    }
}

class Calculator {
    public function add($a, $b) {
        return $a + $b;
    }

    public function subtract($a, $b) {
        return $a - $b;
    }
}

// 使用
$logger = new Logger();
$calculator = new Calculator();

$result = $calculator->add(10, 5);
$logger->log("计算结果:$result");
?>

2. 开闭原则(OCP)

软件实体应该对扩展开放,对修改关闭。

<?php
// 基础形状类
abstract class Shape {
    abstract public function getArea();
}

// 圆形类
class Circle extends Shape {
    private $radius;

    public function __construct($radius) {
        $this->radius = $radius;
    }

    public function getArea() {
        return pi() * $this->radius * $this->radius;
    }
}

// 矩形类
class Rectangle extends Shape {
    private $width;
    private $height;

    public function __construct($width, $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea() {
        return $this->width * $this->height;
    }
}

// 形状计算器
class AreaCalculator {
    public function calculateTotalArea(array $shapes) {
        $totalArea = 0;
        foreach ($shapes as $shape) {
            $totalArea += $shape->getArea();
        }
        return $totalArea;
    }
}

// 使用
$shapes = [
    new Circle(5),
    new Rectangle(10, 20),
    new Rectangle(15, 25)
];

$calculator = new AreaCalculator();
$totalArea = $calculator->calculateTotalArea($shapes);
echo "总面积:" . round($totalArea, 2) . "\n";
?>

面向对象的实际应用

简单的用户管理系统

<?php
class User {
    private $id;
    private $username;
    private $email;
    private $passwordHash;
    private $isActive;
    private $createdAt;
    private static $users = [];

    public function __construct($username, $email, $password) {
        $this->id = uniqid();
        $this->username = $this->validateUsername($username);
        $this->email = $this->validateEmail($email);
        $this->passwordHash = $this->hashPassword($password);
        $this->isActive = true;
        $this->createdAt = date('Y-m-d H:i:s');

        self::$users[$this->id] = $this;
    }

    private function validateUsername($username) {
        if (strlen($username) < 3) {
            throw new Exception("用户名至少需要3个字符");
        }
        return $username;
    }

    private function validateEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new Exception("邮箱格式不正确");
        }
        return $email;
    }

    private function hashPassword($password) {
        return password_hash($password, PASSWORD_DEFAULT);
    }

    public function authenticate($password) {
        return password_verify($password, $this->passwordHash);
    }

    public function activate() {
        $this->isActive = true;
    }

    public function deactivate() {
        $this->isActive = false;
    }

    public function isActive() {
        return $this->isActive;
    }

    public function getUsername() {
        return $this->username;
    }

    public function getEmail() {
        return $this->email;
    }

    public function getCreatedAt() {
        return $this->createdAt;
    }

    public function getProfile() {
        return [
            'username' => $this->username,
            'email' => $this->email,
            'is_active' => $this->isActive,
            'created_at' => $this->createdAt
        ];
    }

    public static function getByUsername($username) {
        foreach (self::$users as $user) {
            if ($user->username === $username) {
                return $user;
            }
        }
        return null;
    }

    public static function getAll() {
        return self::$users;
    }

    public static function getActiveCount() {
        $count = 0;
        foreach (self::$users as $user) {
            if ($user->isActive()) {
                $count++;
            }
        }
        return $count;
    }
}

// 用户管理服务
class UserService {
    public function registerUser($username, $email, $password) {
        // 检查用户名是否已存在
        $existingUser = User::getByUsername($username);
        if ($existingUser) {
            throw new Exception("用户名已存在");
        }

        // 创建新用户
        $user = new User($username, $email, $password);
        return $user;
    }

    public function loginUser($username, $password) {
        $user = User::getByUsername($username);
        if (!$user) {
            throw new Exception("用户不存在");
        }

        if (!$user->authenticate($password)) {
            throw new Exception("密码错误");
        }

        if (!$user->isActive()) {
            throw new Exception("账户已被禁用");
        }

        return $user;
    }

    public function getUserProfile($username) {
        $user = User::getByUsername($username);
        if (!$user) {
            throw new Exception("用户不存在");
        }

        return $user->getProfile();
    }

    public function getAllActiveUsers() {
        $activeUsers = [];
        foreach (User::getAll() as $user) {
            if ($user->isActive()) {
                $activeUsers[] = $user->getProfile();
            }
        }
        return $activeUsers;
    }
}

// 使用示例
try {
    $userService = new UserService();

    echo "=== 用户注册 ===\n";
    $user1 = $userService->registerUser("alice", "alice@example.com", "password123");
    $user2 = $userService->registerUser("bob", "bob@example.com", "password456");

    echo "用户注册成功\n";
    echo "活跃用户数:" . User::getActiveCount() . "\n\n";

    echo "=== 用户登录 ===\n";
    $loggedInUser = $userService->loginUser("alice", "password123");
    echo "用户 {$loggedInUser->getUsername()} 登录成功\n\n";

    echo "=== 获取用户信息 ===\n";
    $profile = $userService->getUserProfile("alice");
    echo "用户信息:\n";
    print_r($profile);

    echo "\n=== 活跃用户列表 ===\n";
    $activeUsers = $userService->getAllActiveUsers();
    foreach ($activeUsers as $user) {
        echo "- {$user['username']} ({$user['email']})\n";
    }

} catch (Exception $e) {
    echo "错误:" . $e->getMessage() . "\n";
}
?>

总结

面向对象编程是一种强大的编程范式,它帮助我们构建更加模块化、可维护和可扩展的应用程序。

关键要点:

  1. 核心概念

    • 对象是类的实例,包含属性和方法
    • 类是对象的模板,定义了结构和行为
    • 封装将数据和操作捆绑在一起
    • 继承实现代码重用和扩展
    • 多态提供灵活的接口
  2. OOP的优势

    • 代码更易维护和扩展
    • 提高代码重用性
    • 增强程序的安全性
    • 更好地组织复杂项目
  3. 设计原则

    • 单一职责原则
    • 开闭原则
    • 其他SOLID原则(后续章节详细介绍)

最佳实践:

  • 将相关的数据和操作封装在同一个类中
  • 使用访问控制保护内部数据
  • 为类和方法选择有意义的名称
  • 保持类的方法简洁和专注
  • 优先使用组合而不是继承(在某些情况下)

通过本节学习,你应该对面向对象编程有了基本的理解,为后续学习更高级的OOP特性打下坚实基础。