设计模式是软件开发中经过反复验证的、解决特定问题的最佳实践。掌握常用的设计模式,能帮助我们写出更优雅、更可维护、更易扩展的代码。今天就来分享PHP中最常用的三种设计模式:单例模式、工厂模式、观察者模式,并结合实际代码讲解它们的应用场景和实现方法。

为什么要学习设计模式

很多初学者觉得设计模式很抽象,很难理解,在实际开发中也用不上。但是当你参与过几个中大型项目,维护过一些"祖传代码"后,就会发现设计模式的重要性。

  1. 提高代码可维护性:设计模式能让代码结构更清晰,职责更单一,更容易理解和维护。
  2. 提高代码可扩展性:设计模式遵循"开闭原则"(对扩展开放,对修改关闭),能让我们在不修改现有代码的情况下扩展功能。
  3. 提高开发效率:设计模式是前人经验的总结,遇到类似问题时直接套用,能少走弯路。
  4. 方便团队协作:设计模式有统一的术语和结构,团队成员之间沟通更顺畅。

当然,设计模式不是银弹,不能为了用模式而用模式。要根据实际问题选择合适的设计模式,简单的问题用简单的方法解决,不要过度设计。

一、单例模式(Singleton)

定义:单例模式确保一个类只有一个实例,并提供一个全局访问点。

应用场景

  • 数据库连接:避免重复创建数据库连接,节省资源
  • 配置管理:全局只有一个配置对象,统一管理配置
  • 日志记录:全局只有一个日志对象,统一记录日志
  • 缓存管理:全局只有一个缓存对象,统一管理缓存

PHP实现

<?php
class Database
{
    private static $instance = null;
    private $pdo;

    // 私有构造函数,禁止外部new
    private function __construct()
    {
        $this->pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', 'root', 'password');
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    // 私有克隆函数,禁止克隆
    private function __clone() {}

    // 私有反序列化函数,禁止反序列化
    private function __wakeup() {}

    // 全局访问点
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

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

    public function query($sql)
    {
        return $this->pdo->query($sql);
    }
}

// 使用
$db = Database::getInstance();
$result = $db->query("SELECT * FROM users");

要点

  1. 构造函数、克隆函数、反序列化函数都设为private,禁止外部创建实例
  2. 用静态变量保存唯一实例
  3. 提供静态方法getInstance()获取实例,首次调用时创建实例
  4. 单例模式是"懒加载"的,只有在第一次使用时才创建实例

优缺点

  • 优点:节省资源,避免重复创建对象,全局统一访问
  • 缺点:扩展性差,不易测试,违反单一职责原则(既负责创建对象,又负责业务逻辑)

二、工厂模式(Factory)

定义:工厂模式提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂模式分为简单工厂、工厂方法、抽象工厂三种,这里介绍最常用的简单工厂和工厂方法。

1. 简单工厂模式

应用场景:根据不同的参数创建不同的对象,将对象创建逻辑集中管理。

PHP实现

<?php
// 产品接口
interface Logger
{
    public function log($message);
}

// 具体产品:文件日志
class FileLogger implements Logger
{
    public function log($message)
    {
        file_put_contents('app.log', date('Y-m-d H:i:s') . ' ' . $message . "\n", FILE_APPEND);
    }
}

// 具体产品:数据库日志
class DatabaseLogger implements Logger
{
    public function log($message)
    {
        // 写入数据库
        echo "写入数据库日志:$message\n";
    }
}

// 具体产品:邮件日志
class EmailLogger implements Logger
{
    public function log($message)
    {
        // 发送邮件
        echo "发送邮件日志:$message\n";
    }
}

// 简单工厂
class LoggerFactory
{
    public static function create($type)
    {
        switch ($type) {
            case 'file':
                return new FileLogger();
            case 'database':
                return new DatabaseLogger();
            case 'email':
                return new EmailLogger();
            default:
                throw new InvalidArgumentException("不支持的日志类型:$type");
        }
    }
}

// 使用
$logger = LoggerFactory::create('file');
$logger->log('这是一条日志');

优缺点

  • 优点:将对象创建逻辑集中管理,客户端不需要知道具体创建细节
  • 缺点:违反开闭原则,新增产品类型需要修改工厂类

2. 工厂方法模式

定义:定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

PHP实现

<?php
// 产品接口
interface Payment
{
    public function pay($amount);
}

// 具体产品:支付宝支付
class AlipayPayment implements Payment
{
    public function pay($amount)
    {
        echo "支付宝支付:$amount 元\n";
    }
}

// 具体产品:微信支付
class WechatPayment implements Payment
{
    public function pay($amount)
    {
        echo "微信支付:$amount 元\n";
    }
}

// 具体产品:银联支付
class UnionpayPayment implements Payment
{
    public function pay($amount)
    {
        echo "银联支付:$amount 元\n";
    }
}

// 工厂接口
interface PaymentFactory
{
    public function createPayment();
}

// 具体工厂:支付宝工厂
class AlipayFactory implements PaymentFactory
{
    public function createPayment()
    {
        return new AlipayPayment();
    }
}

// 具体工厂:微信工厂
class WechatFactory implements PaymentFactory
{
    public function createPayment()
    {
        return new WechatPayment();
    }
}

// 具体工厂:银联工厂
class UnionpayFactory implements PaymentFactory
{
    public function createPayment()
    {
        return new UnionpayPayment();
    }
}

// 使用
$factory = new AlipayFactory();
$payment = $factory->createPayment();
$payment->pay(100);

优缺点

  • 优点:符合开闭原则,新增产品类型只需要新增对应的工厂类,不需要修改现有代码
  • 缺点:类的数量增多,系统复杂度增加

三、观察者模式(Observer)

定义:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

应用场景

  • 事件监听:用户注册后,发送邮件、短信、站内信
  • 消息通知:文章发布后,通知订阅者
  • 数据同步:数据变更后,同步到缓存、搜索引擎
  • 插件系统:核心功能触发事件,插件监听事件执行自定义逻辑

PHP实现

<?php
// 观察者接口
interface Observer
{
    public function update($event, $data);
}

// 被观察者(主题)接口
interface Subject
{
    public function attach(Observer $observer);
    public function detach(Observer $observer);
    public function notify($event, $data);
}

// 具体被观察者:用户注册
class UserRegister implements Subject
{
    private $observers = [];

    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function detach(Observer $observer)
    {
        $key = array_search($observer, $this->observers);
        if ($key !== false) {
            unset($this->observers[$key]);
        }
    }

    public function notify($event, $data)
    {
        foreach ($this->observers as $observer) {
            $observer->update($event, $data);
        }
    }

    public function register($username, $email)
    {
        // 用户注册逻辑
        echo "用户 $username 注册成功\n";

        // 触发事件,通知所有观察者
        $this->notify('user.register', ['username' => $username, 'email' => $email]);
    }
}

// 具体观察者:发送邮件
class EmailObserver implements Observer
{
    public function update($event, $data)
    {
        if ($event === 'user.register') {
            echo "发送邮件到 {$data['email']}:欢迎注册\n";
        }
    }
}

// 具体观察者:发送短信
class SmsObserver implements Observer
{
    public function update($event, $data)
    {
        if ($event === 'user.register') {
            echo "发送短信:欢迎注册,{$data['username']}\n";
        }
    }
}

// 具体观察者:记录日志
class LogObserver implements Observer
{
    public function update($event, $data)
    {
        echo "记录日志:事件 $event,数据 " . json_encode($data) . "\n";
    }
}

// 使用
$userRegister = new UserRegister();
$userRegister->attach(new EmailObserver());
$userRegister->attach(new SmsObserver());
$userRegister->attach(new LogObserver());

$userRegister->register('张三', 'zhangsan@example.com');

PHP内置支持: PHP提供了SplSubject和SplObserver接口,可以直接使用:

<?php
class User implements SplSubject
{
    private $observers = [];
    private $name;

    public function attach(SplObserver $observer)
    {
        $this->observers[] = $observer;
    }

    public function detach(SplObserver $observer)
    {
        $key = array_search($observer, $this->observers);
        if ($key !== false) {
            unset($this->observers[$key]);
        }
    }

    public function notify()
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

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

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

class UserLogger implements SplObserver
{
    public function update(SplSubject $subject)
    {
        echo "用户名更新为:" . $subject->getName() . "\n";
    }
}

优缺点

  • 优点:解耦被观察者和观察者,支持广播通信,符合开闭原则
  • 缺点:观察者过多时通知耗时,观察者之间可能存在循环依赖

设计模式原则

学习设计模式,更重要的是理解背后的设计原则:

  1. 单一职责原则:一个类只负责一个功能领域中的相应职责
  2. 开闭原则:对扩展开放,对修改关闭
  3. 里氏替换原则:子类可以替换父类出现在任何地方
  4. 依赖倒置原则:依赖抽象,不依赖具体实现
  5. 接口隔离原则:使用多个小接口,不使用一个大接口
  6. 迪米特法则:一个对象应当对其他对象有尽可能少的了解

总结

单例模式、工厂模式、观察者模式是PHP开发中最常用的三种设计模式,掌握它们能大大提高我们的代码质量。

  • 单例模式:确保一个类只有一个实例,适用于数据库连接、配置管理等场景
  • 工厂模式:将对象创建逻辑封装起来,适用于需要根据不同条件创建不同对象的场景
  • 观察者模式:实现对象间的一对多依赖,适用于事件监听、消息通知等场景

设计模式不是银弹,要根据实际问题选择合适的模式,不要为了用模式而用模式。理解设计模式背后的设计原则,比死记硬背模式更重要。

希望这篇文章能帮助大家更好地理解和使用设计模式,写出更高质量的代码。