12、工厂模式实战(一):数据库连接工厂(支持MySQL、PostgreSQL、SQLite)

好,咱们今天来聊点实在的。

工厂模式,说白了就是帮你“生产对象”的。你想想看,写代码的时候,是不是经常要 new 这个、new 那个?如果项目小还好,一旦系统复杂起来,到处都是 new,后期想换个实现,那叫一个痛苦。

我个人习惯,遇到这种“创建对象逻辑可能变化”的场景,第一反应就是上工厂模式。今天咱们就拿数据库连接来练手,做一个支持 MySQL、PostgreSQL、SQLite 的工厂。

为什么需要数据库连接工厂?

先问个问题:你的项目里,数据库连接代码是怎么写的?

很多新手会这样写:

// 直接硬编码
$conn = new mysqli($host, $user, $pass, $db);
// 或者
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);

嗯,看起来没问题。但你想过没有——如果有一天,客户要求从 MySQL 换成 PostgreSQL,你要改多少处?

我在项目中遇到过这种惨痛教训。一个老系统,数据库连接散落在 50 多个文件里,改了一个通宵,还漏了两个地方没改,第二天线上直接报错。从那以后,我给自己定了个规矩:所有对象的创建,必须集中管理

核心思想:工厂模式把“创建对象”这件事封装起来。调用方只需要告诉工厂“我要什么”,不需要关心“怎么创建”。

先画个图:工厂模式的核心结构

动手写代码之前,咱们先看看结构。我画了一张图,帮你理清思路:

数据库连接工厂 - 结构图 客户端代码 调用 getConnection() DBConnectionFactory MySQLConnection PostgreSQLConnection SQLiteConnection 工厂根据传入参数,返回对应的数据库连接对象

你看,客户端只跟工厂打交道。工厂内部根据参数,决定返回哪个数据库连接。这样客户端代码就干净了——它不需要知道 MySQL 怎么连、PostgreSQL 怎么连,它只需要说“给我一个数据库连接”。

第一步:定义产品接口

既然是生产“数据库连接”,那咱们先定义一下“连接”长什么样。所有数据库连接,都得实现这个接口:

// 数据库连接接口
interface DBConnectionInterface {
    public function connect();
    public function query($sql);
    public function close();
}

这个接口很简洁,就三个方法:连接、查询、关闭。你想想看,不管什么数据库,最终不都是这三件事吗?

小提示:接口定义得越简单,扩展起来越容易。我见过有人把接口搞了十几个方法,结果每个实现类里一半都是空方法。没必要。

第二步:实现具体产品类

接口有了,咱们来实现三个具体的数据库连接类。

MySQL 连接:

class MySQLConnection implements DBConnectionInterface {
    private $host;
    private $dbname;
    private $username;
    private $password;
    private $connection;

    public function __construct($host, $dbname, $username, $password) {
        $this->host = $host;
        $this->dbname = $dbname;
        $this->username = $username;
        $this->password = $password;
    }

    public function connect() {
        // 这里用 PDO 做底层,更安全
        $dsn = "mysql:host={$this->host};dbname={$this->dbname}";
        $this->connection = new PDO($dsn, $this->username, $this->password);
        echo "MySQL 连接成功\n";
    }

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

    public function close() {
        $this->connection = null;
        echo "MySQL 连接已关闭\n";
    }
}

PostgreSQL 连接:

class PostgreSQLConnection implements DBConnectionInterface {
    private $host;
    private $dbname;
    private $username;
    private $password;
    private $connection;

    public function __construct($host, $dbname, $username, $password) {
        $this->host = $host;
        $this->dbname = $dbname;
        $this->username = $username;
        $this->password = $password;
    }

    public function connect() {
        $dsn = "pgsql:host={$this->host};dbname={$this->dbname}";
        $this->connection = new PDO($dsn, $this->username, $this->password);
        echo "PostgreSQL 连接成功\n";
    }

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

    public function close() {
        $this->connection = null;
        echo "PostgreSQL 连接已关闭\n";
    }
}

SQLite 连接:

class SQLiteConnection implements DBConnectionInterface {
    private $dbPath;
    private $connection;

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

    public function connect() {
        $dsn = "sqlite:{$this->dbPath}";
        $this->connection = new PDO($dsn);
        echo "SQLite 连接成功\n";
    }

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

    public function close() {
        $this->connection = null;
        echo "SQLite 连接已关闭\n";
    }
}

你有没有发现,这三个类的结构几乎一样?只是 DSN 字符串不同。这就是接口的好处——统一了行为,细节各自实现。

第三步:创建工厂类

重头戏来了。工厂类负责根据参数,决定实例化哪个数据库连接:

class DBConnectionFactory {
    // 用一个数组来存储配置
    private static $config = [];

    // 设置配置
    public static function setConfig($config) {
        self::$config = $config;
    }

    // 核心方法:创建连接
    public static function getConnection($type = 'mysql') {
        $type = strtolower($type);

        switch ($type) {
            case 'mysql':
                return new MySQLConnection(
                    self::$config['mysql']['host'],
                    self::$config['mysql']['dbname'],
                    self::$config['mysql']['username'],
                    self::$config['mysql']['password']
                );
            case 'pgsql':
            case 'postgresql':
                return new PostgreSQLConnection(
                    self::$config['pgsql']['host'],
                    self::$config['pgsql']['dbname'],
                    self::$config['pgsql']['username'],
                    self::$config['pgsql']['password']
                );
            case 'sqlite':
                return new SQLiteConnection(
                    self::$config['sqlite']['db_path']
                );
            default:
                throw new Exception("不支持的数据库类型: {$type}");
        }
    }
}

注意:这里的 switch-case 虽然简单,但如果你要支持几十种数据库,代码会变得臃肿。我曾经在一个项目中维护过 20 多种数据库的工厂,后来改用注册表模式来优化。不过对于咱们这个场景,switch 完全够用。

第四步:使用工厂

好了,现在看看客户端代码有多清爽:

// 配置数据库信息
DBConnectionFactory::setConfig([
    'mysql' => [
        'host' => 'localhost',
        'dbname' => 'test_db',
        'username' => 'root',
        'password' => '123456'
    ],
    'pgsql' => [
        'host' => 'localhost',
        'dbname' => 'test_db',
        'username' => 'postgres',
        'password' => '654321'
    ],
    'sqlite' => [
        'db_path' => '/data/test.db'
    ]
]);

// 获取 MySQL 连接
$mysqlConn = DBConnectionFactory::getConnection('mysql');
$mysqlConn->connect();
$mysqlConn->query("SELECT * FROM users");
$mysqlConn->close();

// 获取 PostgreSQL 连接
$pgsqlConn = DBConnectionFactory::getConnection('pgsql');
$pgsqlConn->connect();
$pgsqlConn->query("SELECT * FROM users");
$pgsqlConn->close();

// 获取 SQLite 连接
$sqliteConn = DBConnectionFactory::getConnection('sqlite');
$sqliteConn->connect();
$sqliteConn->query("SELECT * FROM users");
$sqliteConn->close();

你看,客户端只需要改一个参数,就能切换数据库。如果以后要加 MongoDB、Redis,只需要新增一个类,然后在工厂的 switch 里加一行。调用方完全不用改。

避坑指南:我曾经踩过的坑

讲几个实际项目中容易遇到的问题:

  • 连接泄漏:我曾经在一个项目中,工厂只负责创建连接,不负责管理生命周期。结果调用方忘了 close,数据库连接池被耗尽。后来我加了一个析构函数自动关闭,或者在工厂里做引用计数。
  • 配置硬编码:早期我把数据库配置写在工厂类里,结果测试环境和生产环境要改代码。后来我改成从配置文件读取,或者用环境变量注入。
  • 单例滥用:有人喜欢把工厂做成单例,然后全局共享。但如果你有多个数据库实例(比如读写分离),单例就不合适了。我建议工厂本身保持无状态,只负责创建。

总结一下

数据库连接工厂,说白了就是把“创建连接”这件事集中管理。它的好处很明显:

  • 解耦:调用方不依赖具体数据库实现
  • 易扩展:加新数据库类型,只需要加一个类,改一行工厂代码
  • 易维护:所有连接配置集中管理,改一处就行

嗯,今天的内容就到这。代码不多,但思路很重要。你可以在自己的项目里试试,把散落的数据库连接代码收拢到工厂里,相信我,你会爱上这种清爽的感觉。

一句话记住:工厂模式不是炫技,是让你少加班。


公众号:蓝海资料掘金营,微信 deep3321