导读:本期聚焦于崔健创作的《PHP怎么实现工厂模式?工厂模式三种形态的编写方法详解》,敬请观看详情。工厂模式到底解决什么问题?简单来说,它把对象的创建过程封装起来,调用方不需要关心new了什么类,只需要告诉工厂我要什么,就能拿到对应的实例。本文围绕PHP环境下工厂模式的三种形态展开:简单工厂、工厂方法和抽象工厂,分别给出可直接运行的代码示例,分析各自的适用场景与优缺点,并结合依赖注入容器讲解工厂模式在真实项目框架中的落地方式。如果你在写代码时遇到过大量new散落各处、切换实现类要改动几十个文件的情况,这篇文章能帮你理清思路,用更优雅的方式组织对象创建逻辑。

工厂模式是面向对象设计中最常用的创建型模式之一。它的核心思想很简单:把实例化对象的活儿交给一个专门的类去做,调用方只管要结果,不关心对象是怎么new出来的。在PHP项目里,无论是封装数据库驱动、支付渠道,还是处理不同格式的文件解析,工厂模式都能派上用场。这篇文章会用具体的PHP代码,把简单工厂、工厂方法、抽象工厂三种形态讲透,并说说各自的取舍。

PHP怎么实现工厂模式?工厂模式三种形态的编写方法详解

为什么需要工厂模式

先看一段典型的坏味道代码。假设系统需要支持多种缓存,调用处直接写死了实现类:

$cache = new RedisCache();
$cache->set('user_1', $data);

这段代码的问题在于,调用方和具体实现类RedisCache紧紧绑在了一起。哪天要把Redis换成Memcached,你就得全局搜索,把每一处new RedisCache()都改一遍。如果这个调用发生在几十个文件里,改动的代价非常高,而且很容易漏改。

工厂模式就是为这个问题而生的。它引入一个中间角色,把创建逻辑集中到一处。调用方只依赖工厂暴露的接口,具体实例化哪个类,只有工厂自己知道。这样即便将来实现类换了,也只需要改工厂内部的一行代码,调用方完全无感知。这种解耦带来的另一个好处是便于测试:单元测试时可以让工厂返回一个假对象,避免测试代码真的去连Redis。

简单工厂:最直接的封装方式

简单工厂严格来说不是GoF定义的标准模式,但它是理解工厂模式的最佳起点。它的结构非常朴素:一个静态方法,根据传入的参数决定返回哪个类的实例。先定义一个缓存接口和两个实现:

interface Cache
{
    public function set(string $key, $value): void;
    public function get(string $key);
}

class RedisCache implements Cache
{
    public function set(string $key, $value): void
    {
        // 实际项目中这里调用Redis扩展
        echo "写入Redis: {$key}\n";
    }

    public function get(string $key)
    {
        return null;
    }
}

class MemcachedCache implements Cache
{
    public function set(string $key, $value): void
    {
        echo "写入Memcached: {$key}\n";
    }

    public function get(string $key)
    {
        return null;
    }
}

然后编写简单工厂类:

class CacheFactory
{
    public static function create(string $type): Cache
    {
        switch ($type) {
            case 'redis':
                return new RedisCache();
            case 'memcached':
                return new MemcachedCache();
            default:
                throw new InvalidArgumentException("不支持的缓存类型: {$type}");
        }
    }
}

// 调用方式
$cache = CacheFactory::create('redis');
$cache->set('user_1', ['name' => '张三']);

这段代码的关键点在于返回值类型声明: Cache。它强制工厂返回的对象都实现了统一接口,调用方拿到实例后可以放心调用setget,不用做任何类型判断。另外注意default分支抛出异常,比静默返回null要安全得多,能让错误尽早暴露。

简单工厂的缺点也很明显:每新增一种缓存类型,都要修改工厂类的switch语句,违背了开闭原则。当产品类型不多、变化不频繁时,简单工厂完全够用;如果类型会持续增长,就该考虑工厂方法了。

工厂方法模式:让每个工厂只负责一个产品

工厂方法模式的做法是:不再用一个上帝工厂包揽所有创建逻辑,而是把工厂本身也抽象出来,让每种产品对应一个专属工厂。新增产品时,只需要加一个新的产品类和一个新的工厂类,原有代码一行不动。先看工厂侧的定义:

interface CacheFactoryInterface
{
    public function createCache(): Cache;
}

class RedisCacheFactory implements CacheFactoryInterface
{
    public function createCache(): Cache
    {
        return new RedisCache();
    }
}

class MemcachedCacheFactory implements CacheFactoryInterface
{
    public function createCache(): Cache
    {
        return new MemcachedCache();
    }
}

调用方的用法变成这样:

function handleRequest(CacheFactoryInterface $factory)
{
    $cache = $factory->createCache();
    $cache->set('session_abc', ['uid' => 100]);
}

// 需要Redis时传入Redis工厂,需要切换时换一个工厂即可
handleRequest(new RedisCacheFactory());

注意handleRequest的参数类型是工厂接口而不是具体工厂,这就是依赖倒置的体现:高层逻辑依赖抽象,不依赖具体实现。想切换缓存方案,只需在入口处换掉传入的工厂实例,业务代码内部完全不用动。

工厂方法的代价是类的数量翻倍,每加一种产品要写两个类。对于小型项目来说可能显得繁琐,但在组件化程度高、需要插件式扩展的系统里,这种结构能带来极大的灵活性。Laravel的服务容器内部大量使用的就是这种思路,只是它用一个通用容器统一管理,省去了手写工厂类的过程。

抽象工厂:一次创建一族相关对象

如果系统里的对象是成组出现的,比如一套存储方案包含缓存和队列两个组件,Redis方案配RedisCache加RedisQueue,Memcached方案配MemcachedCache加MemcachedQueue,这时用简单工厂或工厂方法就要创建两次,还可能搭配出错。抽象工厂把这些相关对象的创建打包到一个工厂里:

interface Queue
{
    public function push(string $job): void;
}

class RedisQueue implements Queue
{
    public function push(string $job): void
    {
        echo "任务推入Redis队列: {$job}\n";
    }
}

class MemcachedQueue implements Queue
{
    public function push(string $job): void
    {
        echo "任务推入Memcached队列: {$job}\n";
    }
}

interface StorageFactory
{
    public function createCache(): Cache;
    public function createQueue(): Queue;
}

class RedisStorageFactory implements StorageFactory
{
    public function createCache(): Cache
    {
        return new RedisCache();
    }

    public function createQueue(): Queue
    {
        return new RedisQueue();
    }
}

使用时,调用方从同一个工厂取缓存和队列,天然保证它们来自同一套方案,不会出现Redis缓存配Memcached队列的混搭错误。当整个产品族需要整体切换时,只需要替换工厂实例。

抽象工厂的短板在于扩展产品种类很痛苦。假设后来要给每套方案再加一个日志组件,就得修改抽象工厂接口以及所有具体工厂类。所以抽象工厂适合产品族结构稳定的场景,比如多数据库适配层、跨平台UI组件库,而不适合产品种类频繁增加的场景。

实战建议与常见坑

写工厂模式时有两个容易踩的坑。第一点是工厂里不要塞业务逻辑。工厂的职责只有创建对象,如果发现工厂方法里开始写数据校验、调接口之类的代码,说明职责已经越界,应该把这些逻辑挪到产品类或服务类里。第二点是参数校验要严格,简单工厂收到未知类型时果断抛异常,不要返回null或空对象,否则错误会延迟到很远的地方才爆出来,排查成本翻倍。

另外提一个进阶技巧:结合PHP的类名做动态实例化,可以让工厂代码更精简:

class CacheFactory
{
    public static function create(string $type): Cache
    {
        $class = ucfirst($type) . 'Cache';
        if (!class_exists($class)) {
            throw new InvalidArgumentException("类 {$class} 不存在");
        }
        $instance = new $class();
        if (!$instance instanceof Cache) {
            throw new RuntimeException("{$class} 未实现Cache接口");
        }
        return $instance;
    }
}

这种写法省去了switch分支,新增类型只要按命名规则建类即可。但要注意class_existsinstanceof双重校验不能省,否则外部传入的type字符串可能实例化出意料之外的类,留下安全隐患。如果type来自用户输入,务必用白名单过滤,绝不能直接拼类名。

总结一下选择思路:产品类型少且稳定,用简单工厂;产品会持续增加,或想让框架使用者自行扩展,用工厂方法;对象成族出现且需要整体切换,用抽象工厂。在现代PHP开发中,更常见的做法是直接使用Composer生态中的依赖注入容器,它本质上是一个功能更强的超级工厂,理解了本文的三种形态,再看Laravel、Symfony的容器实现就会轻松很多。

PHP工厂模式设计模式Factory模式修改时间:2026-09-14 13:29:02

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260914/56713.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。