类与对象

类和对象是面向对象编程的核心概念。类是对象的模板或蓝图,而对象是类的具体实例。理解类和对象的关系是掌握OOP编程的基础。

学习目标

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

  • 理解类和对象的概念和关系
  • 掌握类的定义语法
  • 学会创建和使用对象
  • 理解对象的内存管理
  • 掌握静态成员的使用
  • 了解类与对象的调试技巧

类的定义

基本语法

<?php
class ClassName {
    // 类的属性和方法
}

// 示例:定义一个简单的Person类
class Person {
    // 属性(成员变量)
    public $name;
    public $age;
    public $gender;

    // 方法(成员函数)
    public function introduce() {
        return "大家好,我叫{$this->name},今年{$this->age}岁。";
    }

    public function setAge($age) {
        $this->age = $age;
    }

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

类的命名规范

<?php
// 好的类命名示例
class User {}
class Product {}
class DatabaseConnection {}
class EmailValidator {}

// 不推荐的命名
class user {}           // 小写开头
class product_info {}    // 使用下划线
class DBConnection {}      // 缩写不清晰

// 推荐使用大驼峰命名法(PascalCase)
class ShoppingCart {}
class UserProfile {}
class ConfigurationManager {}
?>

类的组成部分

<?php
class Student {
    // 1. 常量 - 使用 const 关键字定义
    const MAX_AGE = 120;
    const MIN_AGE = 6;
    const STATUS_ACTIVE = 'active';
    const STATUS_INACTIVE = 'inactive';

    // 2. 静态属性 - 属于类而不是对象
    private static $totalStudents = 0;
    private static $schoolName = '示例学校';

    // 3. 实例属性 - 属于每个对象实例
    private $id;
    private $name;
    private $age;
    private $grade;
    private $status = self::STATUS_ACTIVE;

    // 4. 构造方法 - 创建对象时调用
    public function __construct($name, $age, $grade) {
        $this->id = uniqid('student_');
        $this->name = $name;
        $this->age = $this->validateAge($age);
        $this->grade = $grade;

        self::$totalStudents++;
        echo "学生 {$this->name} 已创建\n";
    }

    // 5. 实例方法 - 对象的行为
    public function introduce() {
        return "我是{$this->grade}的学生{$this->name},今年{$this->age}岁。";
    }

    public function promote() {
        if ($this->grade === '六年级') {
            return "已经毕业了!";
        }

        $gradeMap = [
            '一年级' => '二年级',
            '二年级' => '三年级',
            '三年级' => '四年级',
            '四年级' => '五年级',
            '五年级' => '六年级'
        ];

        $this->grade = $gradeMap[$this->grade] ?? $this->grade;
        return "恭喜升入{$this->grade}!";
    }

    // 6. 静态方法 - 属于类而不是对象
    public static function getTotalStudents() {
        return self::$totalStudents;
    }

    public static function setSchoolName($name) {
        self::$schoolName = $name;
    }

    public static function getSchoolName() {
        return self::$schoolName;
    }

    // 7. 析构方法 - 对象销毁时调用
    public function __destruct() {
        self::$totalStudents--;
        echo "学生 {$this->name} 已注销\n";
    }

    // 8. 私有辅助方法
    private function validateAge($age) {
        if ($age < self::MIN_AGE) {
            throw new Exception("学生年龄不能小于" . self::MIN_AGE . "岁");
        }
        if ($age > self::MAX_AGE) {
            throw new Exception("学生年龄不能大于" . self::MAX_AGE . "岁");
        }
        return $age;
    }

    // Getter 方法
    public function getId() {
        return $this->id;
    }

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

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

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

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

    // Setter 方法
    public function setName($name) {
        $this->name = $name;
    }

    public function setAge($age) {
        $this->age = $this->validateAge($age);
    }

    public function setStatus($status) {
        if (in_array($status, [self::STATUS_ACTIVE, self::STATUS_INACTIVE])) {
            $this->status = $status;
        }
    }
}

// 使用示例
echo "=== 类的使用示例 ===\n";

// 设置学校名称
Student::setSchoolName("阳光小学");
echo "学校名称:" . Student::getSchoolName() . "\n";

// 创建学生对象
$student1 = new Student("张小明", 8, "二年级");
$student2 = new Student("李小红", 9, "三年级");

// 访问对象方法
echo $student1->introduce() . "\n";
echo $student2->introduce() . "\n";

// 访问对象属性
echo $student1->getName() . " 当前在 " . $student1->getGrade() . "\n";

// 调用对象方法
echo $student1->promote() . "\n";
echo "现在在 " . $student1->getGrade() . "\n";

// 访问静态方法
echo "当前学生总数:" . Student::getTotalStudents() . "\n";

// 访问常量
echo "学生年龄范围:" . Student::MIN_AGE . " - " . Student::MAX_AGE . "岁\n";
?>

对象的创建和使用

创建对象

<?php
// 使用 new 关键字创建对象
$student = new Student("王大伟", 10, "四年级");

// 创建对象的其他方式
$className = 'Student';
$student2 = new $className("刘小美", 7, "一年级");

// 从变量创建对象
$studentData = [
    'name' => '陈志强',
    'age' => 11,
    'grade' => '五年级'
];
$student3 = new Student($studentData['name'], $studentData['age'], $studentData['grade']);

// 检查对象是否为某类的实例
if ($student instanceof Student) {
    echo "\$student 是 Student 类的实例\n";
}

// 使用类常量
echo "学生状态:" . Student::STATUS_ACTIVE . "\n";
?>

对象的属性访问

<?php
class Product {
    // 公共属性
    public $name;
    public $price;

    // 受保护的属性
    protected $category;

    // 私有属性
    private $stock;

    public function __construct($name, $price, $category, $stock) {
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
        $this->stock = $stock;
    }

    // Getter 方法
    public function getCategory() {
        return $this->category;
    }

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

    // Setter 方法
    public function setStock($stock) {
        if ($stock >= 0) {
            $this->stock = $stock;
            return true;
        }
        return false;
    }

    // 公共方法
    public function getInfo() {
        return "产品:{$this->name},价格:¥{$this->price},库存:{$this->stock}";
    }

    // 受保护的方法
    protected function calculateDiscount() {
        if ($this->stock > 100) {
            return 0.1; // 10% 折扣
        } elseif ($this->stock > 50) {
            return 0.05; // 5% 折扣
        }
        return 0;
    }

    // 公共方法调用受保护方法
    public function getDiscountedPrice() {
        $discount = $this->calculateDiscount();
        return $this->price * (1 - $discount);
    }
}

// 使用示例
echo "=== 属性访问示例 ===\n";

$product = new Product("笔记本电脑", 5999.00, "电子产品", 50);

// 访问公共属性
echo "产品名称:" . $product->name . "\n";
echo "产品价格:" . $product->price . "\n";

// 通过getter访问私有/受保护属性
echo "产品类别:" . $product->getCategory() . "\n";
echo "产品库存:" . $product->getStock() . "\n";

// 通过setter修改属性
$product->setStock(120);
echo "修改后库存:" . $product->getStock() . "\n";

// 调用方法
echo "产品信息:" . $product->getInfo() . "\n";
echo "折扣价格:" . $product->getDiscountedPrice() . "\n";
?>

对象的比较

<?php
class User {
    public $id;
    public $name;

    public function __construct($id, $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function equals($otherUser) {
        if ($otherUser instanceof User) {
            return $this->id === $otherUser->id && $this->name === $otherUser->name;
        }
        return false;
    }

    public function __toString() {
        return "User[id={$this->id}, name={$this->name}]";
    }
}

// 使用示例
echo "=== 对象比较示例 ===\n";

$user1 = new User(1, "张三");
$user2 = new User(1, "张三");
$user3 = new User(2, "李四");

// 对象同一性比较(===)- 比较的是对象是否为同一个实例
echo "user1 === user2: " . ($user1 === $user2 ? "true" : "false") . "\n";

// 对象相等性比较(==)- 比较的是属性值是否相同
echo "user1 == user2: " . ($user1 == $user2 ? "true" : "false") . "\n";
echo "user1 == user3: " . ($user1 == $user3 ? "true" : "false") . "\n";

// 自定义比较方法
echo "user1 equals user2: " . ($user1->equals($user2) ? "true" : "false") . "\n";
echo "user1 equals user3: " . ($user1->equals($user3) ? "true" : "false") . "\n";

// 对象字符串表示
echo "user1: " . $user1 . "\n";
echo "user2: " . $user2 . "\n";
echo "user3: " . $user3 . "\n";
?>

静态成员

静态属性

<?php
class Counter {
    private static $count = 0;
    private static $instances = [];

    private $id;
    private $name;

    public function __construct($name) {
        $this->id = ++self::$count;
        $this->name = $name;
        self::$instances[$this->id] = $this;
    }

    public static function getCount() {
        return self::$count;
    }

    public static function getInstances() {
        return self::$instances;
    }

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

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

    public static function reset() {
        self::$count = 0;
        self::$instances = [];
    }

    public function __toString() {
        return "Counter #{$this->id}: {$this->name}";
    }
}

// 使用示例
echo "=== 静态成员示例 ===\n";

// 创建多个Counter对象
$counter1 = new Counter("计数器A");
$counter2 = new Counter("计数器B");
$counter3 = new Counter("计数器C");

// 访问静态方法
echo "当前计数器数量:" . Counter::getCount() . "\n";

// 访问静态属性
$instances = Counter::getInstances();
foreach ($instances as $instance) {
    echo $instance . "\n";
}

// 注意:不能这样访问静态属性
// echo Counter::$count; // 错误:私有静态属性
?>

静态方法

<?php
class MathUtils {
    // 静态常量
    const PI = 3.14159265359;
    const E = 2.71828182846;

    // 静态方法 - 不需要创建对象实例就可以调用
    public static function add($a, $b) {
        return $a + $b;
    }

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

    public static function multiply($a, $b) {
        return $a * $b;
    }

    public static function divide($a, $b) {
        if ($b == 0) {
            throw new InvalidArgumentException("除数不能为零");
        }
        return $a / $b;
    }

    public static function power($base, $exponent) {
        return pow($base, $exponent);
    }

    // 静态方法调用其他静态方法
    public static function quadraticEquation($a, $b, $c) {
        if ($a == 0) {
            throw new InvalidArgumentException("二次项系数不能为零");
        }

        $discriminant = self::power($b, 2) - 4 * $a * $c;

        if ($discriminant < 0) {
            return null; // 无实数解
        }

        $sqrtDiscriminant = sqrt($discriminant);
        $x1 = self::subtract(-$b, $sqrtDiscriminant) / (2 * $a);
        $x2 = self::add(-$b, $sqrtDiscriminant) / (2 * $a);

        return [$x1, $x2];
    }

    // 静态工厂方法
    public static function createCalculator() {
        return new class {
            private $result = 0;

            public function add($number) {
                $this->result += $number;
                return $this;
            }

            public function multiply($number) {
                $this->result *= $number;
                return $this;
            }

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

// 使用静态方法
echo "=== 静态方法示例 ===\n";

// 直接调用静态方法
echo "10 + 5 = " . MathUtils::add(10, 5) . "\n";
echo "10 - 3 = " . MathUtils::subtract(10, 3) . "\n";
echo "6 * 7 = " . MathUtils::multiply(6, 7) . "\n";
echo "15 / 3 = " . MathUtils::divide(15, 3) . "\n";
echo "2^8 = " . MathUtils::power(2, 8) . "\n";

// 访问静态常量
echo "π = " . MathUtils::PI . "\n";
echo "e = " . MathUtils::E . "\n";

// 求解二次方程 x^2 - 5x + 6 = 0
$solutions = MathUtils::quadraticEquation(1, -5, 6);
if ($solutions) {
    echo "方程 x^2 - 5x + 6 = 0 的解:x1 = {$solutions[0]}, x2 = {$solutions[1]}\n";
}

// 使用静态工厂方法
$calculator = MathUtils::createCalculator();
$result = $calculator->add(10)->multiply(2)->add(5)->getResult();
echo "计算结果:(10 + 5) * 2 = $result\n";
?>

对象的克隆

<?php
class Document {
    public $title;
    public $content;
    public $author;
    public $version;
    private $createdAt;

    public function __construct($title, $content, $author) {
        $this->title = $title;
        $this->content = $content;
        $this->author = $author;
        $this->version = 1;
        $this->createdAt = date('Y-m-d H:i:s');
    }

    // 定义克隆行为
    public function __clone() {
        $this->version++;
        // 注意:$this->createdAt 不会被克隆,保持原值
    }

    public function updateContent($newContent) {
        $this->content = $newContent;
        $this->version++;
    }

    public function getInfo() {
        return "文档:{$this->title},作者:{$this->author},版本:{$this->version},创建时间:{$this->createdAt}";
    }
}

// 使用示例
echo "=== 对象克隆示例 ===\n";

$originalDoc = new Document("PHP教程", "这是一篇关于PHP的教程", "张老师");
echo "原文档:" . $originalDoc->getInfo() . "\n";

// 克隆对象
$clonedDoc = clone $originalDoc;
echo "克隆文档:" . $clonedDoc->getInfo() . "\n";

// 修改原文档
$originalDoc->updateContent("这是更新后的PHP教程内容");
echo "更新后原文档:" . $originalDoc->getInfo() . "\n";
echo "克隆文档(未受影响):" . $clonedDoc->getInfo() . "\n";

// 修改克隆文档
$clonedDoc->updateContent("这是克隆版本的PHP教程");
echo "修改后克隆文档:" . $clonedDoc->getInfo() . "\n";
echo "原文档(未受影响):" . $originalDoc->getInfo() . "\n";
?>

类型声明和返回类型

<?php
// PHP 7+ 支持类型声明
class Calculator {
    private $result = 0;

    // 参数类型声明和返回类型声明
    public function add(float $number): float {
        $this->result += $number;
        return $this->result;
    }

    public function subtract(float $number): float {
        $this->result -= $number;
        return $this->result;
    }

    public function multiply(float $number): float {
        $this->result *= $number;
        return $this->result;
    }

    public function divide(float $number): float {
        if ($number == 0) {
            throw new Exception("除数不能为零");
        }
        $this->result /= $number;
        return $this->result;
    }

    public function reset(): void {
        $this->result = 0;
    }

    public function getResult(): float {
        return $this->result;
    }

    // 可变参数
    public function addMultiple(float ...$numbers): float {
        foreach ($numbers as $number) {
            $this->result += $number;
        }
        return $this->result;
    }

    // 可空返回类型
    public function sqrt(float $number): ?float {
        if ($number < 0) {
            return null;
        }
        return sqrt($number);
    }

    // 数组类型声明
    public function getHistory(): array {
        return [];
    }

    // 对象类型声明
    public function setLogger(Logger $logger): void {
        $this->logger = $logger;
    }
}

// 简单的Logger类
class Logger {
    public function log($message) {
        echo "[LOG] $message\n";
    }
}

// 使用示例
echo "=== 类型声明示例 ===\n";

$calculator = new Calculator();

$result = $calculator->add(10.5);
echo "加法结果:$result\n";

$result = $calculator->multiply(2);
echo "乘法结果:$result\n";

$result = $calculator->addMultiple(1, 2, 3, 4, 5);
echo "多参数加法结果:$result\n";

$sqrtResult = $calculator->sqrt(16);
echo "平方根结果:" . ($sqrtResult ?? 'null') . "\n";

$logger = new Logger();
$calculator->setLogger($logger);
$logger->log("计算完成");
?>

实际应用示例

简单的银行账户系统

<?php
class BankAccount {
    private static $totalAccounts = 0;
    private static $bankName = "示例银行";

    private $accountNumber;
    private $accountHolder;
    private $balance;
    private $type; // 'checking' 或 'savings'
    private $createdAt;
    private $transactions = [];

    public function __construct($accountHolder, $initialBalance = 0, $type = 'checking') {
        if ($initialBalance < 0) {
            throw new InvalidArgumentException("初始余额不能为负数");
        }

        if (!in_array($type, ['checking', 'savings'])) {
            throw new InvalidArgumentException("账户类型必须是 checking 或 savings");
        }

        $this->accountNumber = $this->generateAccountNumber();
        $this->accountHolder = $accountHolder;
        $this->balance = $initialBalance;
        $this->type = $type;
        $this->createdAt = date('Y-m-d H:i:s');

        self::$totalAccounts++;

        if ($initialBalance > 0) {
            $this->recordTransaction('deposit', $initialBalance, "初始存款");
        }

        echo "账户创建成功:{$this->accountNumber}\n";
    }

    private function generateAccountNumber() {
        return date('Y') . sprintf('%04d', self::$totalAccounts + 1001);
    }

    public function deposit($amount) {
        if ($amount <= 0) {
            throw new InvalidArgumentException("存款金额必须大于0");
        }

        $this->balance += $amount;
        $this->recordTransaction('deposit', $amount, "存款");
        return true;
    }

    public function withdraw($amount) {
        if ($amount <= 0) {
            throw new InvalidArgumentException("取款金额必须大于0");
        }

        if ($this->balance < $amount) {
            throw new Exception("余额不足");
        }

        if ($this->type === 'savings' && $this->getWithdrawalCount() >= 3) {
            $fee = 2.0; // 储蓄账户每月最多3次免费取款
            if ($this->balance < $amount + $fee) {
                throw new Exception("余额不足(包含手续费)");
            }
            $this->balance -= ($amount + $fee);
            $this->recordTransaction('withdraw', $amount, "取款");
            $this->recordTransaction('fee', $fee, "取款手续费");
        } else {
            $this->balance -= $amount;
            $this->recordTransaction('withdraw', $amount, "取款");
        }

        return true;
    }

    public function transfer($toAccount, $amount) {
        if (!$toAccount instanceof BankAccount) {
            throw new InvalidArgumentException("目标账户必须是BankAccount实例");
        }

        $this->withdraw($amount);
        $toAccount->deposit($amount);

        $this->recordTransaction('transfer_out', $amount, "转账给 {$toAccount->accountNumber}");
        $toAccount->recordTransaction('transfer_in', $amount, "来自 {$this->accountNumber}");

        return true;
    }

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

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

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

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

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

    private function getWithdrawalCount() {
        $count = 0;
        foreach ($this->transactions as $transaction) {
            if ($transaction['type'] === 'withdraw') {
                $count++;
            }
        }
        return $count;
    }

    private function recordTransaction($type, $amount, $description) {
        $this->transactions[] = [
            'date' => date('Y-m-d H:i:s'),
            'type' => $type,
            'amount' => $amount,
            'description' => $description,
            'balance' => $this->balance
        ];
    }

    public function printStatement() {
        echo "\n=== 银行对账单 ===\n";
        echo "账户号码:{$this->accountNumber}\n";
        echo "账户持有人:{$this->accountHolder}\n";
        echo "账户类型:" . ($this->type === 'checking' ? '支票账户' : '储蓄账户') . "\n";
        echo "创建日期:{$this->createdAt}\n";
        echo "当前余额:¥" . number_format($this->balance, 2) . "\n";
        echo "交易记录:\n";

        foreach ($this->transactions as $transaction) {
            $sign = ($transaction['type'] === 'deposit' || $transaction['type'] === 'transfer_in') ? '+' : '-';
            echo "  {$transaction['date']} | {$sign}¥" . number_format($transaction['amount'], 2) .
                 " | {$transaction['description']} | 余额:¥" . number_format($transaction['balance'], 2) . "\n";
        }
    }

    // 静态方法
    public static function getTotalAccounts() {
        return self::$totalAccounts;
    }

    public static function getBankName() {
        return self::$bankName;
    }

    public static function setBankName($name) {
        self::$bankName = $name;
    }

    public function __destruct() {
        echo "账户 {$this->accountNumber} 已关闭\n";
    }
}

// 使用示例
echo "=== 银行账户系统示例 ===\n";

// 设置银行名称
BankAccount::setBankName("阳光银行");
echo "银行名称:" . BankAccount::getBankName() . "\n";

// 创建账户
$account1 = new BankAccount("张三", 1000.00, 'checking');
$account2 = new BankAccount("李四", 500.00, 'savings');
$account3 = new BankAccount("王五", 2000.00, 'checking');

echo "总账户数:" . BankAccount::getTotalAccounts() . "\n\n";

// 存款操作
echo "=== 存款操作 ===\n";
$account1->deposit(500.00);
$account2->deposit(300.00);
echo "张三账户余额:¥" . number_format($account1->getBalance(), 2) . "\n";
echo "李四账户余额:¥" . number_format($account2->getBalance(), 2) . "\n\n";

// 取款操作
echo "=== 取款操作 ===\n";
$account1->withdraw(200.00);
$account2->withdraw(100.00);
$account2->withdraw(50.00); // 储蓄账户第二次取款
$account2->withdraw(30.00); // 储蓄账户第三次取款
$account2->withdraw(20.00); // 储蓄账户第四次取款(含手续费)

// 转账操作
echo "\n=== 转账操作 ===\n";
$account1->transfer($account3, 300.00);
echo "张三转给王五 300 元\n";

echo "转账后余额:\n";
echo "张三:¥" . number_format($account1->getBalance(), 2) . "\n";
echo "王五:¥" . number_format($account3->getBalance(), 2) . "\n";

// 打印对账单
$account2->printStatement();
?>

总结

类和对象是面向对象编程的基础,理解它们的概念和用法对于掌握OOP至关重要。

关键要点:

  1. 类是模板,对象是实例

    • 类定义了属性和方法的蓝图
    • 对象是类的具体实例,拥有独立的状态
  2. 访问控制

    • public:可以从任何地方访问
    • protected:只能在类内部和子类中访问
    • private:只能在类内部访问
  3. 静态成员

    • 静态属性和方法属于类而不是对象
    • 通过类名直接访问,不需要创建对象
  4. 对象生命周期

    • 通过 new 创建对象
    • 通过 __destruct() 清理资源
    • 支持对象克隆

最佳实践:

  • 使用有意义的类和方法名称
  • 合理使用访问控制,保护内部数据
  • 优先使用组合而不是继承
  • 为类和方法编写清晰的文档注释
  • 使用类型声明提高代码安全性

通过本节学习,你应该能够熟练地定义和使用类与对象,为后续学习更高级的面向对象特性打下坚实基础。