设计模式,是软件开发中非常重要的概念。它是前人在软件开发过程中,总结出来的解决特定问题的最佳实践和经验总结。掌握设计模式,能让我们写出更优雅、更可维护、更可扩展的代码。

作为一个PHP开发者,设计模式是必备的技能。无论是框架开发,还是业务开发,设计模式都无处不在。Laravel、Symfony、Yii等主流PHP框架,都大量使用了设计模式。掌握设计模式,不仅能让我们写出更好的代码,也能让我们更好地理解框架的源码和设计思想。

今天,我们来详细学习PHP设计模式,从设计模式的基本概念,到常用的设计模式,再到实际应用,帮你掌握设计模式,写出更优雅、更可维护的代码。

什么是设计模式

设计模式(Design Pattern),是软件开发中针对特定问题的通用、可复用的解决方案。它不是具体的代码,而是解决问题的思路和模板。

设计模式的概念,最早由Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides四人(人称"GoF",Gang of Four)在1994年出版的《设计模式:可复用面向对象软件的基础》一书中提出。书中总结了23种经典的设计模式,分为三大类:创建型模式、结构型模式、行为型模式。

设计模式的本质,是"面向接口编程,而非面向实现编程"和"优先使用对象组合,而非类继承"这两个面向对象设计原则的具体体现。

为什么要学习设计模式

1. 提高代码质量

设计模式,是经过验证的最佳实践。使用设计模式,可以让代码更加优雅、更加规范、更加可维护、更加可扩展。

2. 提高开发效率

设计模式,提供了通用的解决方案。遇到类似的问题,可以直接套用设计模式,不需要重新思考解决方案,提高开发效率。

3. 便于团队协作

设计模式,是开发者之间的通用语言。使用设计模式,团队成员可以更好地理解彼此的代码,便于沟通和协作。

4. 更好地理解框架

主流框架,都大量使用了设计模式。学习设计模式,可以更好地理解框架的源码和设计思想,更好地使用和扩展框架。

5. 提升编程思维

学习设计模式,不仅仅是学习几种模式,更是学习一种解决问题的思维方式。它能让我们从更高的层次思考代码设计,写出更好的代码。

设计模式的分类

GoF的23种设计模式,分为三大类:

1. 创建型模式(Creational Patterns)

创建型模式,处理对象的创建机制,试图在不指定具体类的情况下创建对象。

包括:

  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 建造者模式(Builder)
  • 原型模式(Prototype)
  • 单例模式(Singleton)

2. 结构型模式(Structural Patterns)

结构型模式,处理类和对象的组合,帮助将多个对象组合成更大的结构。

包括:

  • 适配器模式(Adapter)
  • 桥接模式(Bridge)
  • 组合模式(Composite)
  • 装饰器模式(Decorator)
  • 外观模式(Facade)
  • 享元模式(Flyweight)
  • 代理模式(Proxy)

3. 行为型模式(Behavioral Patterns)

行为型模式,处理类和对象之间的通信,以及职责的分配。

包括:

  • 责任链模式(Chain of Responsibility)
  • 命令模式(Command)
  • 解释器模式(Interpreter)
  • 迭代器模式(Iterator)
  • 中介者模式(Mediator)
  • 备忘录模式(Memento)
  • 观察者模式(Observer)
  • 状态模式(State)
  • 策略模式(Strategy)
  • 模板方法模式(Template Method)
  • 访问者模式(Visitor)

下面,我们详细学习PHP中最常用的几种设计模式。

一、单例模式(Singleton)

定义

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

适用场景

  • 数据库连接:避免多次连接,节省资源
  • 配置管理:全局唯一的配置对象
  • 日志记录:全局唯一的日志对象
  • 缓存管理:全局唯一的缓存对象

PHP实现

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

    // 私有构造函数,禁止外部实例化
    private function __construct() {
        $this->pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
    }

    // 私有克隆函数,禁止克隆
    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;
    }
}

// 使用
$db = Database::getInstance();
$pdo = $db->getConnection();

要点

  • 构造函数、克隆函数、反序列化函数都设为私有
  • 用静态变量保存唯一实例
  • 提供静态方法获取实例
  • 注意:PHP中,单例模式在每次请求中有效,不同请求之间不共享(因为PHP是无状态的)

二、工厂方法模式(Factory Method)

定义

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

适用场景

  • 需要创建不同类型的对象,但具体类型在运行时决定
  • 客户端不需要知道对象的创建细节
  • 需要灵活扩展,新增产品类型时不需要修改现有代码

PHP实现

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

// 具体产品
class FileLogger implements Logger {
    public function log($message) {
        file_put_contents('app.log', $message . "\n", FILE_APPEND);
    }
}

class DatabaseLogger implements Logger {
    public function log($message) {
        // 写入数据库
        echo "Log to database: $message\n";
    }
}

// 工厂接口
interface LoggerFactory {
    public function createLogger(): Logger;
}

// 具体工厂
class FileLoggerFactory implements LoggerFactory {
    public function createLogger(): Logger {
        return new FileLogger();
    }
}

class DatabaseLoggerFactory implements LoggerFactory {
    public function createLogger(): Logger {
        return new DatabaseLogger();
    }
}

// 使用
$factory = new FileLoggerFactory();
$logger = $factory->createLogger();
$logger->log('Hello World');

要点

  • 定义产品接口和具体产品类
  • 定义工厂接口和具体工厂类
  • 每个具体工厂创建对应的具体产品
  • 新增产品类型时,只需要新增产品类和工厂类,不需要修改现有代码(符合开闭原则)

三、抽象工厂模式(Abstract Factory)

定义

抽象工厂模式,提供一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。

适用场景

  • 需要创建一组相关或相互依赖的对象
  • 需要动态切换产品族
  • 需要保证客户端使用的对象属于同一个产品族

PHP实现

<?php
// 产品接口
interface Button {
    public function render();
}

interface TextBox {
    public function render();
}

// Windows产品族
class WindowsButton implements Button {
    public function render() {
        echo "Windows Button\n";
    }
}

class WindowsTextBox implements TextBox {
    public function render() {
        echo "Windows TextBox\n";
    }
}

// Mac产品族
class MacButton implements Button {
    public function render() {
        echo "Mac Button\n";
    }
}

class MacTextBox implements TextBox {
    public function render() {
        echo "Mac TextBox\n";
    }
}

// 抽象工厂
interface GUIFactory {
    public function createButton(): Button;
    public function createTextBox(): TextBox;
}

// 具体工厂
class WindowsFactory implements GUIFactory {
    public function createButton(): Button {
        return new WindowsButton();
    }
    public function createTextBox(): TextBox {
        return new WindowsTextBox();
    }
}

class MacFactory implements GUIFactory {
    public function createButton(): Button {
        return new MacButton();
    }
    public function createTextBox(): TextBox {
        return new MacTextBox();
    }
}

// 使用
$factory = new WindowsFactory();
$button = $factory->createButton();
$textBox = $factory->createTextBox();
$button->render();
$textBox->render();

要点

  • 定义多个产品接口(Button、TextBox)
  • 每个产品接口有多个产品族的实现(Windows、Mac)
  • 抽象工厂定义创建每个产品的方法
  • 具体工厂创建同一产品族的所有产品
  • 客户端只依赖抽象工厂和产品接口,不依赖具体类

四、建造者模式(Builder)

定义

建造者模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用场景

  • 创建复杂对象,对象有很多可选参数
  • 需要分步构建对象
  • 需要创建不同表示的对象

PHP实现

<?php
// 产品
class SQLQuery {
    private $select = '*';
    private $from;
    private $where = [];
    private $orderBy;
    private $limit;

    public function setSelect($select) { $this->select = $select; }
    public function setFrom($from) { $this->from = $from; }
    public function setWhere($where) { $this->where[] = $where; }
    public function setOrderBy($orderBy) { $this->orderBy = $orderBy; }
    public function setLimit($limit) { $this->limit = $limit; }

    public function getSQL() {
        $sql = "SELECT {$this->select} FROM {$this->from}";
        if (!empty($this->where)) {
            $sql .= " WHERE " . implode(' AND ', $this->where);
        }
        if ($this->orderBy) {
            $sql .= " ORDER BY {$this->orderBy}";
        }
        if ($this->limit) {
            $sql .= " LIMIT {$this->limit}";
        }
        return $sql;
    }
}

// 建造者
class SQLQueryBuilder {
    private $query;

    public function __construct() {
        $this->query = new SQLQuery();
    }

    public function select($fields) {
        $this->query->setSelect($fields);
        return $this;
    }

    public function from($table) {
        $this->query->setFrom($table);
        return $this;
    }

    public function where($condition) {
        $this->query->setWhere($condition);
        return $this;
    }

    public function orderBy($orderBy) {
        $this->query->setOrderBy($orderBy);
        return $this;
    }

    public function limit($limit) {
        $this->query->setLimit($limit);
        return $this;
    }

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

// 使用
$builder = new SQLQueryBuilder();
$query = $builder->select('id, title, content')
    ->from('articles')
    ->where('status = "published"')
    ->where('created_at > "2015-01-01"')
    ->orderBy('created_at DESC')
    ->limit(10)
    ->build();

echo $query->getSQL();
// SELECT id, title, content FROM articles WHERE status = "published" AND created_at > "2015-01-01" ORDER BY created_at DESC LIMIT 10

要点

  • 产品类有很多属性,设置方法
  • 建造者类提供链式调用的方法,分步设置属性
  • 每个方法返回$this,支持链式调用
  • build()方法返回最终产品
  • 客户端通过建造者,灵活地构建复杂对象

五、适配器模式(Adapter)

定义

适配器模式,将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用场景

  • 需要使用一个已存在的类,但它的接口不符合需求
  • 需要创建一个可复用的类,该类可以与其他不相关的类或不可预见的类协同工作
  • 需要使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口

PHP实现

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

// 已存在的类(Adaptee)
class Alipay {
    public function alipayPay($amount) {
        echo "Alipay pay: $amount\n";
    }
}

class WechatPay {
    public function wechatPay($amount) {
        echo "Wechat pay: $amount\n";
    }
}

// 适配器
class AlipayAdapter implements Payment {
    private $alipay;

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

    public function pay($amount) {
        $this->alipay->alipayPay($amount);
    }
}

class WechatPayAdapter implements Payment {
    private $wechatPay;

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

    public function pay($amount) {
        $this->wechatPay->wechatPay($amount);
    }
}

// 使用
$payment = new AlipayAdapter(new Alipay());
$payment->pay(100);

$payment = new WechatPayAdapter(new WechatPay());
$payment->pay(200);

要点

  • 目标接口(Payment)定义客户端需要的接口
  • 已存在的类(Alipay、WechatPay)有不同的接口
  • 适配器类实现目标接口,内部调用已存在类的方法
  • 客户端通过目标接口使用适配器,不需要关心已存在类的接口

六、装饰器模式(Decorator)

定义

装饰器模式,动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

适用场景

  • 需要动态地给一个对象添加功能,这些功能可以再动态地撤销
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实
  • 当不能采用生成子类的方法进行扩充时

PHP实现

<?php
// 组件接口
interface Coffee {
    public function cost();
    public function description();
}

// 具体组件
class SimpleCoffee implements Coffee {
    public function cost() {
        return 10;
    }
    public function description() {
        return "Simple Coffee";
    }
}

// 装饰器基类
abstract class CoffeeDecorator implements Coffee {
    protected $coffee;

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

// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
    public function cost() {
        return $this->coffee->cost() + 2;
    }
    public function description() {
        return $this->coffee->description() . ", Milk";
    }
}

class SugarDecorator extends CoffeeDecorator {
    public function cost() {
        return $this->coffee->cost() + 1;
    }
    public function description() {
        return $this->coffee->description() . ", Sugar";
    }
}

class WhipDecorator extends CoffeeDecorator {
    public function cost() {
        return $this->coffee->cost() + 3;
    }
    public function description() {
        return $this->coffee->description() . ", Whip";
    }
}

// 使用
$coffee = new SimpleCoffee();
echo $coffee->description() . ": " . $coffee->cost() . "\n";
// Simple Coffee: 10

$coffee = new MilkDecorator($coffee);
echo $coffee->description() . ": " . $coffee->cost() . "\n";
// Simple Coffee, Milk: 12

$coffee = new SugarDecorator($coffee);
echo $coffee->description() . ": " . $coffee->cost() . "\n";
// Simple Coffee, Milk, Sugar: 13

$coffee = new WhipDecorator($coffee);
echo $coffee->description() . ": " . $coffee->cost() . "\n";
// Simple Coffee, Milk, Sugar, Whip: 16

要点

  • 组件接口(Coffee)定义基本功能
  • 具体组件(SimpleCoffee)实现基本功能
  • 装饰器基类(CoffeeDecorator)实现组件接口,持有组件对象
  • 具体装饰器(MilkDecorator等)扩展功能,调用被装饰对象的方法,并添加新功能
  • 可以动态地、多层地装饰对象,灵活组合功能

七、观察者模式(Observer)

定义

观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

适用场景

  • 一个对象的改变需要通知其他对象,而且它不知道具体有多少对象有待改变
  • 一个对象必须通知其他对象,而它又不能假定其他对象是谁
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象

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 Article implements Subject {
    private $observers = [];
    private $title;

    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 publish($title) {
        $this->title = $title;
        $this->notify('article.published', ['title' => $title]);
    }
}

// 具体观察者
class EmailNotifier implements Observer {
    public function update($event, $data) {
        if ($event === 'article.published') {
            echo "Email: New article published - {$data['title']}\n";
        }
    }
}

class SmsNotifier implements Observer {
    public function update($event, $data) {
        if ($event === 'article.published') {
            echo "SMS: New article published - {$data['title']}\n";
        }
    }
}

class CacheCleaner implements Observer {
    public function update($event, $data) {
        if ($event === 'article.published') {
            echo "Cache: Clean article list cache\n";
        }
    }
}

// 使用
$article = new Article();
$article->attach(new EmailNotifier());
$article->attach(new SmsNotifier());
$article->attach(new CacheCleaner());

$article->publish('PHP设计模式详解');
// Email: New article published - PHP设计模式详解
// SMS: New article published - PHP设计模式详解
// Cache: Clean article list cache

要点

  • 观察者接口(Observer)定义update方法
  • 主题接口(Subject)定义attach、detach、notify方法
  • 具体主题(Article)维护观察者列表,状态变化时通知所有观察者
  • 具体观察者(EmailNotifier等)实现update方法,处理通知
  • 主题和观察者之间是松耦合的,主题不需要知道观察者的具体实现
  • PHP内置了SplSubject和SplObserver接口,可以直接使用

八、策略模式(Strategy)

定义

策略模式,定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

适用场景

  • 多个类只有在算法或行为上稍有不同的场景
  • 算法需要自由切换的场景
  • 需要屏蔽算法规则的场景

PHP实现

<?php
// 策略接口
interface SortStrategy {
    public function sort(array $data): array;
}

// 具体策略
class BubbleSort implements SortStrategy {
    public function sort(array $data): array {
        $n = count($data);
        for ($i = 0; $i < $n - 1; $i++) {
            for ($j = 0; $j < $n - $i - 1; $j++) {
                if ($data[$j] > $data[$j + 1]) {
                    [$data[$j], $data[$j + 1]] = [$data[$j + 1], $data[$j]];
                }
            }
        }
        return $data;
    }
}

class QuickSort implements SortStrategy {
    public function sort(array $data): array {
        if (count($data) <= 1) return $data;
        $pivot = $data[0];
        $left = $right = [];
        for ($i = 1; $i < count($data); $i++) {
            if ($data[$i] < $pivot) $left[] = $data[$i];
            else $right[] = $data[$i];
        }
        return array_merge($this->sort($left), [$pivot], $this->sort($right));
    }
}

class PHPSort implements SortStrategy {
    public function sort(array $data): array {
        sort($data);
        return $data;
    }
}

// 上下文
class Sorter {
    private $strategy;

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

    public function setStrategy(SortStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function sort(array $data): array {
        return $this->strategy->sort($data);
    }
}

// 使用
$data = [5, 2, 8, 1, 9, 3];

$sorter = new Sorter(new BubbleSort());
print_r($sorter->sort($data));

$sorter->setStrategy(new QuickSort());
print_r($sorter->sort($data));

$sorter->setStrategy(new PHPSort());
print_r($sorter->sort($data));

要点

  • 策略接口(SortStrategy)定义算法接口
  • 具体策略(BubbleSort等)实现具体算法
  • 上下文(Sorter)持有策略对象,调用策略的方法
  • 可以在运行时动态切换策略
  • 客户端只依赖策略接口,不依赖具体算法
  • 符合开闭原则,新增算法只需要新增策略类

设计模式的设计原则

学习设计模式,还要理解背后的设计原则。设计模式,都是这些设计原则的具体体现。

1. 单一职责原则(SRP)

一个类,应该只有一个引起它变化的原因。即一个类,只负责一项职责。

2. 开闭原则(OCP)

软件实体(类、模块、函数等),应该对扩展开放,对修改关闭。即新增功能时,应该通过扩展来实现,而不是修改现有代码。

3. 里氏替换原则(LSP)

子类型,必须能够替换掉它们的父类型。即继承时,子类应该可以在任何地方替换父类,而不影响程序的正确性。

4. 接口隔离原则(ISP)

客户端,不应该依赖它不需要的接口。即接口应该小而专,不应该大而全。

5. 依赖倒置原则(DIP)

高层模块,不应该依赖低层模块,二者都应该依赖其抽象。抽象,不应该依赖细节,细节应该依赖抽象。即面向接口编程,而非面向实现编程。

6. 迪米特法则(LoD)

一个对象,应该对其他对象保持最少的了解。即类之间的耦合,应该尽可能低。

设计模式的使用建议

1. 不要为了使用模式而使用模式

设计模式,是为了解决问题而存在的。如果一个问题,用简单的方式就能解决,就不需要强行使用设计模式。过度使用设计模式,会让代码变得复杂,难以理解。

2. 理解模式的本质,而不是死记硬背

学习设计模式,要理解每个模式的本质、适用场景和优缺点,而不是死记硬背代码。只有理解了本质,才能在实际开发中,灵活地运用和变通。

3. 模式可以组合使用

实际开发中,往往不是只使用一种设计模式,而是多种模式组合使用。比如,工厂模式+单例模式,策略模式+工厂模式,观察者模式+单例模式等。要学会灵活组合。

4. 重构中引入模式

很多时候,不是一开始就使用设计模式,而是在代码重构的过程中,发现问题,然后引入合适的设计模式来解决问题。这样,模式的引入,才是自然的、有价值的。

5. 框架中学习模式

学习设计模式,最好的方式之一,就是阅读框架的源码。主流框架,都大量使用了设计模式。通过阅读源码,可以看到设计模式在实际项目中的应用,加深理解。

总结

设计模式,是软件开发中非常重要的概念,是前人总结的最佳实践。

核心要点:

  1. 什么是设计模式:针对特定问题的通用、可复用的解决方案,不是具体代码,而是解决问题的思路
  2. 为什么学习:提高代码质量、提高开发效率、便于团队协作、更好地理解框架、提升编程思维
  3. 三大分类:创建型模式(单例、工厂方法、抽象工厂、建造者、原型)、结构型模式(适配器、装饰器、代理、外观、桥接、组合、享元)、行为型模式(观察者、策略、责任链、命令、模板方法、状态、迭代器、中介者、备忘录、解释器、访问者)
  4. 常用模式:单例模式(确保唯一实例)、工厂方法模式(子类决定实例化哪个类)、抽象工厂模式(创建产品族)、建造者模式(分步构建复杂对象)、适配器模式(转换接口)、装饰器模式(动态添加功能)、观察者模式(一对多通知)、策略模式(算法可替换)
  5. 设计原则:单一职责、开闭原则、里氏替换、接口隔离、依赖倒置、迪米特法则
  6. 使用建议:不要为了模式而模式、理解本质而非死记硬背、模式可以组合、重构中引入模式、框架中学习模式

设计模式,不是银弹,不能解决所有问题。但掌握设计模式,能让我们在遇到特定问题时,有现成的、经过验证的解决方案,写出更优雅、更可维护、更可扩展的代码。

"授人以鱼,不如授人以渔。"设计模式,就是"渔",是解决问题的方法论。希望这篇文章,能帮你掌握设计模式,写出更优雅、更可维护的代码。