工厂模式 (Factory)

未匹配的标注

手握设计模式宝典 - 工厂模式 Factory

Design-Pattern - Creational - Factory

  • Title: 《手握设计模式宝典》 之 工厂模式 Factory
  • Tag: Design-PatternCreationalFactory工厂模式设计模式简单工厂工厂方法抽象工厂
  • Author: Tacks
  • Create-Date: 2023-08-21
  • Update-Date: 2023-08-21

大纲

0、REF

1、5W1H

1.1 WHAT 什么是工厂模式?

工厂模式 是一种 创建型 设计模式,用于封装创建对象的代码,负责处理创建对象的实例的细节。

解耦: 单一职责,把创建对象和使用对象分离

工厂模式:将 new 实例化对象(产品)的逻辑封装到工厂中,使客户端只需要与工厂类进行交互,从而使得产品类与客户端进行解耦。

一些情况下,有些创建对象的时候,需要做一些初始化工作。那么客户端调用的时候,在 new 对象之前就需要写几行代码,一个调用处还好,要是项目中多个地方都要用到这个对象,那么就是会出现很多重复代码,并且当需要变动的时候,修改起来也很不方便。那么不如用一个工厂来掩盖实例化对象的逻辑步骤,客户端只需要告诉工厂你需要什么,工厂就直接返回什么对象。

当然,这个里面细分还有不同的工厂实现;

  • 简单工厂 Simple Factory
  • 工厂方法 Factory Method
  • 抽象工厂 Abstract Factory
          简单<--            -->扩展                
[简单工厂] ------ [工厂方法] ------ [抽象工厂]

1.1.1 简单工厂模式-概念

简单工厂 封装一个工厂的创建器,根据客户端传入参数,来判断具体返回什么类的对象实例。

(非 GOF 提出的23种设计模式)

特点

  • 一个工厂类;
    • 职责过重,创建器根据参数来识别创建对应具体产品

1.1.2 工厂方法模式-概念

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

特点

  • 多个工厂类;
    • 多少个产品对应多少个工厂;
    • 一个工厂只能创建对应的一个具体产品实例
  • 一个产品抽象类
    • 派生处多个具体产品类

1.1.3 抽象工厂模式-概念

抽象工厂 提供一个创建一系列相关或相互依赖对象的接口,而不需要指明它们具体的类。

特点

  • 多个工厂类
    • 产品分组,有多少组,就有多少工厂类;
    • 每个工厂类可以创建多个具体产品的实例
  • 产品等级分组
    • 相同功能或特点的,由于不同的产地导致有不同系列
    • 比如:
      • 椅子;有宜家风格、有中式风格
      • 桌子;有宜家风格、有中式风格
      • 沙发:有宜家风格、有中式风格
    • 那么这里的椅子就有两种等级分组,最后的工厂也是两个
  • 产品分族
    • 同一个系列的不同功能或者特点的产品
    • 那么这里的中式风格,就是一个分族,表示同一系列的产品

1.2 WHY 为什么有工厂模式?

这些工厂都具备的一些优点

  • 解耦;对象的创建和使用分离

1.2.1 简单工厂模式-优缺点

  • 优点
    • 简单易于实现;对于产品对象已经确定的,不经常修改工厂类的,比较适用
    • 隐藏细节;客户端无需关心对象的具体创建过程
  • 缺点
    • if/else 的使用
    • 违反开闭原则;如果需要新增产品,要修改工厂的逻辑,随着产品越来越多,工厂的生产器也会越复杂

1.2.2 工厂方法模式-优缺点

  • 优点
    • 解决了开闭原则
    • 解决 if/else 的使用
  • 缺点
    • 代码量增多;每一个产品对应都需要有一个工厂类。

所以说。 工厂方法 -> 简单化处理 -> 简单工厂

1.2.3 抽象工厂模式-优缺点

  • 优点
    • 依赖倒置原则;要依赖抽象,不要依赖具体的类
  • 缺点
    • 违反了开闭原则;

1.3 WHEN 什么时间使用工厂?

  • 当需要创建多种类型的对象,可以利用工厂集中管理对象的创建过程
  • 创建对象的过程比较复杂,需要隐藏对象创建的细节,只提供一个统一的对象获取器

1.4 WHERE 在什么地方使用工厂模式?

  • 数据库连接器
  • 视图连接器

1.5 WHO 谁负责调用工厂?

客户端调用工厂类,来获取具体的产品对象

[客户业务逻辑] ------> [工厂] ------> [基础类]

1.6 HOW 如何去实现工厂?

讲述一个不用工厂,到用简单工厂带来的好处,虽然例子没有很形象,但是看一下代码大概就能理解,工厂解耦的好处,以及封装的思想隐藏实例化对象的细节。

1.6.1 不用工厂-Ⅰ

其实看下面代码也没有什么问题,无非就是需要啥的时候去 new 就行

namespace App\Creational\Factory\NoFactory;

abstract class Fruit {
    abstract public function intro();
}

class Apple extends Fruit {
    public function intro() {
        echo sprintf("[%s] 我是又脆又甜的苹果!", __CLASS__) . PHP_EOL;
    }
}

class Banana extends Fruit {
    public function intro() {
        echo sprintf("[%s] 我是香甜软绵的香蕉!", __CLASS__) . PHP_EOL;
    }
}

$appleObj = new Apple();
$appleObj->intro();

$bananaObj = new Banana();
$bananaObj->intro();

1.6.2 不用工厂-Ⅱ

  • 场景切换

之前就是简单的 苹果类、香蕉类,直接 new 实例化就行,不需要做什么初始化工作。

现在引入 Market 市场类,市场监管局来了,想要生产什么水果,需要在这注册一下,并且设置市场价格

就会多了一步操作,需要去传入市场价格。

  • 之前 $appleObj = new Apple();
  • 现在 $appleObj = new Apple($market->getPrice('apple'));

看似也问题不大,但是哪天万一是大促,苹果可以在市场价上打个折,那就需要做一个 $market->getPrice('apple') * 9 操作,重新传入进去,如果客户端很多,也就是零售点贼多,你还需要让他们自己调整价格。

要是打折结束了,又要回调市场价,那又要告诉下面零售店再重新改成 $market->getPrice('apple') 。 太栓Q !

  • 想一个思路

要是有一个统一的生产厂商多好,只需要他跟商场监管局打好招呼就行,该是多少钱,就是多少钱,顶多生产商定期活动啥的,也是再市场价格上下浮动。零售点都根据生产商提供的价格来卖,不再关心价格,只是关心买什么。

namespace App\Creational\Factory\NoFactory2;

// h
class Market {
    private $prices;
    private static $instance = null;

    private function __construct(){}

    public static function getInstance()
    {
        if(null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function setPrice(string $goods, float $price)
    {
        !isset($this->prices[$goods]) ? $this->prices[$goods] = $price : null;
    }

    public function getPrice(string $goods)
    {
        return $this->prices[$goods] ?? -1;
    }
}

abstract class Fruit {
    private $price;
    public function __construct(float $price)
    {
        $this->price = $price;
    }
    abstract public function intro();

    public function getPrice() {
        echo sprintf("[%s] 单价:%s/斤", get_class($this), $this->price) . PHP_EOL;
    }
}

class Apple extends Fruit {
    public function intro() {
        echo sprintf("[%s] 我是又脆又甜的苹果!", __CLASS__) . PHP_EOL;
    }
}

class Banana extends Fruit {
    public function intro() {
        echo sprintf("[%s] 我是香甜软绵的香蕉!", __CLASS__) . PHP_EOL;
    }
}

// 市场调控
$market = Market::getInstance();
$market->setPrice('apple', '3.0');
$market->setPrice('banana', '3.5');

// 实例化苹果
$appleObj = new Apple($market->getPrice('apple'));
$appleObj->intro();
$appleObj->getPrice();

// 实例化香蕉
$bananaObj = new Banana($market->getPrice('banana'));
$bananaObj->intro();
$bananaObj->getPrice();

1.6.3 简单工厂-Ⅲ

  • 引入水果生产商

利用组合的方式,再工厂实例化的时候,就传入市场监管局定的价格,然后提供一个创建器,用来根据用户参数需求,提供对应水果的对象实例。

class FruitFactory {
    private $market;
    public function __construct(Market $market)
    {
        $this->market = $market;
    }

    public function createor(string $originType) {
        $type = strtolower($originType);
        $price= $this->market->getPrice($type);
        if($price == -1) {
            throw new Exception("{$originType} 商品市场未分配价格,暂时无法售卖");
        }

        if ($type == 'apple') {
            return new Apple($price);
        } else if ($type == 'banana') {
            return new Banana($price);
        }

        throw new Exception("{$originType} 水果店-暂时未引入该水果,暂时无法提供");
    }
}

// 市场调控
$market = Market::getInstance();
$market->setPrice('apple', '3.0');
$market->setPrice('banana', '3.5');

/*
// *************************未用工厂
// 实例化苹果
$appleObj = new Apple($market->getPrice('apple'));
$appleObj->intro();
$appleObj->getPrice();

// 实例化香蕉
$bananaObj = new Banana($market->getPrice('banana'));
$bananaObj->intro();
$bananaObj->getPrice();
*/

// *************************使用工厂
try {
    // 水果店
    $factory = new FruitFactory($market);

    // 想要水果
    $appleObj = $factory->createor('apple');
    $appleObj->intro();
    $appleObj->getPrice();

    // 想要香蕉
    $bananaObj = $factory->createor('banana');
    $bananaObj->intro();
    $bananaObj->getPrice();


    $cherryObj = $factory->createor('cherry');
    $cherryObj->intro();
    $cherryObj->getPrice();

} catch(Exception $e) {
    echo $e->getMessage() . PHP_EOL;
}

2、Code

2.1 简单工厂实现-水果工厂卖苹果香蕉

  • 抽象产品 FruitAbstract
    • abstract class 抽象父类
    • 作用:简单工厂生产的所有产品的抽象父类
    • 约束:描述所有产品的公共的接口,例如每一种水果都有一个自我介绍 intro()
  • 具体产品 AppleFruitConcrete BananaFruitConcrete
    • class 具体子类
    • 作用:简单工厂生产的具体实例对象
    • 实现:抽象方法 intro()
  • 简单工厂 FruitFactory
    • class 简单工厂
    • 作用:简单工厂核心,负责创建所有具体实例对象的逻辑,通常表现为 if/elseswitch/case
    • 创建器方法:用来供外部调用,创建所需产品的实例对象,例如 createor($type) :FruitAbstract

2.1.1 抽象层-水果父类

// 抽象产品
abstract class FruitAbstract
{
    abstract public function intro();
}

2.1.2 实现层-苹果/香蕉

// 苹果-具体产品
class AppleFruitConcrete extends FruitAbstract
{
    public function intro()
    {
        echo sprintf("[%s] 我是又脆又甜的苹果!", __CLASS__) . PHP_EOL;
    }
}

// 香蕉-具体产品
class BananaFruitConcrete extends FruitAbstract
{
    public function intro()
    {
        echo sprintf("[%s] 我是香甜软绵的香蕉!", __CLASS__) . PHP_EOL;
    }
}

2.1.3 工厂层-水果工厂

//  水果-工厂类
class FruitFactory
{
    /**
     * 创建器
     *
     * @param string $originType
     * @return FruitAbstract
     * @throws Exception
     */
    public function createor(string $originType): FruitAbstract
    {
        $type = strtolower($originType);
        if ($type == 'apple') {
            return new AppleFruitConcrete();
        } else if ($type == 'banana') {
            return new BananaFruitConcrete();
        }
        throw new \Exception("{$originType} 暂不支持");
    }
}

2.1.4 逻辑层-卖水果了,想吃啥快来

try {
    $factory = new FruitFactory();

    $appleObj = $factory->createor('apple');
    $appleObj->intro();

    $bananaObj = $factory->createor('banana');
    $bananaObj->intro();

    $cherryObj = $factory->createor('cherry');
    $cherryObj->intro();
} catch (\Exception $e) {
    echo $e->getMessage() . PHP_EOL;
}
  • 调用输出
/*
[App\Creational\Factory\SimpleFactory\AppleFruitConcrete] 我是又脆又甜的苹果!
[App\Creational\Factory\SimpleFactory\BananaFruitConcrete] 我是香甜软绵的香蕉!
cherry 暂不支持
*/

2.2 工厂方法实现-水果工厂卖苹果香蕉

2.2.1 抽象层-水果父类

abstract class Fruit {
    abstract public function intro();
}

2.2.2 实现层-苹果/香蕉

class Apple extends Fruit {
    public function intro() {
        echo sprintf("[%s] 我是又脆又甜的苹果!", __CLASS__) . PHP_EOL;
    }
}

class Banana extends Fruit {
    public function intro() {
        echo sprintf("[%s] 我是香甜软绵的香蕉!", __CLASS__) . PHP_EOL;
    }
}

2.2.3 工厂层-工厂父类约束方法/苹果工厂/香蕉工厂

abstract class FactoryBase {
    function createor(){}
}

class AppleFruitFactory extends FactoryBase {
    public function createor() {
        return new Apple();
    }
}

class BananaFruitFactory extends FactoryBase {
    public function createor() {
        return new Banana();
    }
}

2.2.4 逻辑层-吃啥找对应工厂

$appleObj = (new AppleFruitFactory())->createor();
$appleObj->intro();

$bananaObj = (new BananaFruitFactory())->createor();
$bananaObj->intro();

2.3 抽象工厂实现-本地和进口的水果工厂

2.3.1 同族的不同品种

  • PineappleFruitInterface 菠萝接口
    • PineappleA 菠萝A
    • PineappleB 菠萝B
// 菠萝接口
interface PineappleFruitInterface
{
    public function pineapple();
}
// 菠萝A产品
class PineappleA implements PineappleFruitInterface
{
    public function pineapple()
    {
        echo sprintf("[%s] 我吃菠萝!", __CLASS__) . PHP_EOL;
    }
}
// 菠萝B产品
class PineappleB implements PineappleFruitInterface
{
    public function pineapple()
    {
        echo sprintf("[%s] 我吃凤梨!", __CLASS__) . PHP_EOL;
    }
}
  • CherryFruitInterface 樱桃接口
    • CherryA 樱桃A
    • CherryB 樱桃B
// 樱桃接口
interface CherryFruitInterface
{
    public function cherry();
}
// 樱桃A产品
class CherryA implements CherryFruitInterface
{
    public function cherry()
    {
        echo sprintf("[%s] 我吃樱桃!", __CLASS__) . PHP_EOL;
    }
}
// 樱桃B产品
class CherryB implements CherryFruitInterface
{
    public function cherry()
    {
        echo sprintf("[%s] 我吃车厘子!", __CLASS__) . PHP_EOL;
    }
}

2.3.2 工厂接口层

  • AbstractFactory 抽象工厂
interface AbstractFactory
{
    public function createFruitPineapple();
    public function createFruitCherry();
}

2.3.3 工厂分组实现

  • LocalConcreteFactory 本地工厂
  • ImportConcreteFactory 进口工厂
// 本地工厂
class LocalConcreteFactory implements AbstractFactory
{
    public function createFruitPineapple()
    {
        return new PineappleA();
    }

    public function createFruitCherry()
    {
        return new CherryA();
    }
}

// 进口工厂
class ImportConcreteFactory implements AbstractFactory
{
    public function createFruitPineapple()
    {
        return new PineappleB();
    }

    public function createFruitCherry()
    {
        return new CherryB();
    }
}

2.3.4 逻辑层-不同工厂产出的品种不同

// 两家工厂
$local = new LocalConcreteFactory();
$import = new ImportConcreteFactory();

// 本地工厂-生产
$localPineapple = $local->createFruitPineapple();
$localCherry = $local->createFruitCherry();


// 进口工厂-生产
$importPineapple = $import->createFruitPineapple();
$importCherry = $import->createFruitCherry();

// 小明喜欢吃
$localPineapple->pineapple();
$localCherry->cherry();

// 小红喜欢吃
$importPineapple->pineapple();
$importCherry->cherry();

3、Application

3.1 从 Laravel \Illuminate\Database\Connectors\ConnectionFactory 数据库连接工厂类中体现出简单工厂模式

ConnectionFactory 数据库连接工厂类的 createConnection() 方法就能直接体现出 简单工厂模式

Laravel 的数据库连接工厂使用工厂模式来创建和管理数据库连接实例。它根据配置文件和驱动类型创建适当的数据库连接对象,隐藏了连接细节,使得数据库连接的创建和切换变得简单和可扩展。

3.1.1 数据库配置文件入手

// .env 配置
DB_CONNECTION=mysql
// config\database.php 文件系统配置
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [

    'sqlite' => [
        'driver' => 'sqlite',
        'url' => env('DATABASE_URL'),
        'database' => env('DB_DATABASE', database_path('database.sqlite')),
        'prefix' => '',
        'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
    ],

    'mysql' => [
        'driver' => 'mysql',
        'url' => env('DATABASE_URL'),
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', '3306'),
        'database' => env('DB_DATABASE', 'forge'),
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        'unix_socket' => env('DB_SOCKET', ''),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix' => '',
        'prefix_indexes' => true,
        'strict' => true,
        'engine' => null,
        'options' => extension_loaded('pdo_mysql') ? array_filter([
            PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
        ]) : [],
    ],

    'pgsql' => [
        'driver' => 'pgsql',
        'url' => env('DATABASE_URL'),
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', '5432'),
        'database' => env('DB_DATABASE', 'forge'),
        'username' => env('DB_USERNAME', 'forge'),
        'password' => env('DB_PASSWORD', ''),
        'charset' => 'utf8',
        'prefix' => '',
        'prefix_indexes' => true,
        'schema' => 'public',
        'sslmode' => 'prefer',
    ],

    ...

],

3.1.2 DB 的 Facade 门面操作,快速执行原始 SQL

  • DB Facades 门面操作 select 可以快速执行 SQL 查询

那肯定会想,咦~ ,没看见 数据库连接的地方,直接都能使用 SQL 查询,好神奇 😲

// 举个例子
$res = \Illuminate\Support\Facades\DB::select('select version()');
var_dump($res);
/*
array(1) {
  [0]=>
  object(stdClass)#280 (1) {
    ["version()"]=>
    string(6) "8.0.28"
  }
}
*/
namespace Illuminate\Support\Facades;
/*
 * @see \Illuminate\Database\DatabaseManager
 * @see \Illuminate\Database\Connection
 */
class DB extends Facade
{
    // 重写 Facade 父类的 getFacadeAccessor() ,返回 db 用于提供容器key,解析对应的实例
    protected static function getFacadeAccessor()
    {
        return 'db';
    }
}
  • 门面操作的背后
    • 首先所有具体 Facade 都会继承一个 Abstract Facade 门面的抽象类
    • 由于调用者执行 \Illuminate\Support\Facades\DB::select() ,但是又没有 select() 的静态方法
    • 然后会来到 Facade::__callStatic($method, $args) 魔术方法 (在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用)
    • 魔术方法会调用 Facade::getFacadeRoot() 来解析门面的具体实例 $instance 从而调用 $instance->$method()
    • 具体 $instance 实例是从 Laravel 的服务容器中解析出来的
namespace Illuminate\Support\Facades;
abstract class Facade
{
    // 
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }

    // 从门面后访问真正的对象
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

    // 获取注册的名称 (如果子类没有实现这个方法,则会抛出异常)
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

    // 从容器中解析门面模式
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }
        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }
        if (static::$app) {
            return static::$resolvedInstance[$name] = static::$app[$name];
        }
    }

}

3.1.3 DB 的 Provider 服务提供者是如何提供的

主要了解,服务提供者是如何注册并启动的。其实就是 Illuminate\Database\DatabaseServiceProvider 执行 register() 在服务提供者注册时执行,用于注册服务和配置,程序启动后执行 boot() 引导应用程序事件。

  • app.php 配置文件,可以看到配置的服务提供者 DatabaseServiceProvider ,但是这些配置是什么时候被加载的呢
// config\app.php
'providers' => [
    Illuminate\Database\DatabaseServiceProvider::class,
],
'aliases' => [
    'DB' => Illuminate\Support\Facades\DB::class,
],
  • Illuminate\Database\DatabaseServiceProvider 服务提供者的 register()boot() 方法
namespace Illuminate\Database;

class DatabaseServiceProvider extends ServiceProvider
{
    // 启动引导
    public function boot()
    {
        // 模型的数据库连接解析器
        Model::setConnectionResolver($this->app['db']);
        // 事件调度器
        Model::setEventDispatcher($this->app['events']);
    }

    // 注册
    public function register()
    {
        Model::clearBootedModels();

        // 注册数据库连接服务
        $this->registerConnectionServices();
        // 数据工厂
        $this->registerEloquentFactory();
        // 队列实体解析器
        $this->registerQueueableEntityResolver();
    }
    ...
}
  • public/index.php 入口文件

针对 web http 这种方式,还是从入口文件来看资源的加载。

// 创建 APP 
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
// 执行web处理
$response = $kernel->handle(
    $request = Request::capture()
)->send();
  • Illuminate\Foundation\Http\Kernel HTTP 内核类

Illuminate\Foundation\Http\Kernelhandle() 方法 中会调用 sendRequestThroughRouter() 也就是通过路由器来处理请求,在进入 Pipeline 管道处理之前,有一个 $this->bootstrap(); 的调用,里面主要是执行 $app 应用的 bootstrapWith()。 确保应用程序的各个组件都被正确初始化和配置。

// vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php
namespace Illuminate\Foundation\Http;

class Kernel {
    protected $bootstrappers = [
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,  // 环境变量
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class       ,  // 应用配置
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class        ,  // 异常处理
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class         ,  // 门面注册
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class       ,  // 服务提供
        \Illuminate\Foundation\Bootstrap\BootProviders::class           ,  // 引导初始化
    ];
    // 为 HTTP 请求引导应用程序
    public function bootstrap()
    {
        // 如果应用程序还未加载过
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
}
  • Illuminate\Foundation\Application App 应用的 bootstrapWith() 方法
// vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php
// 运行一组给定的启动类
public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true; // 已经进行了引导操作
    foreach ($bootstrappers as $bootstrapper) {
        // 开始:通过事件系统通知其他监听器引导过程的开始和完成
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
        // 服务容器的 make() 方法,实例化当前迭代到的引导类,然后调用 bootstrap() 方法
        $this->make($bootstrapper)->bootstrap($this);
        // 结束:
        $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
    }
}
  • Illuminate\Foundation\Bootstrap\RegisterProviders 注册服务提供类的 bootstrap() 方法
// vendor\laravel\framework\src\Illuminate\Foundation\Bootstrap\RegisterProviders.php
namespace Illuminate\Foundation\Bootstrap;

class RegisterProviders
{
    // 引导应用服务提供
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}
  • Illuminate\Foundation\Application App 应用的 registerConfiguredProviders() 方法,用于读取配置文件,加载应用程序的服务提供者
// config/app.php 配置文件
'providers' => [
    /*
    * Laravel Framework Service Providers...
    */
    Illuminate\Auth\AuthServiceProvider::class,
    Illuminate\Broadcasting\BroadcastServiceProvider::class,
    ...
     /*
    * Application Service Providers...
    */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    ...
],
// vendor\laravel\framework\src\Illuminate\Foundation\Application.php
// 读取配置文件 `config\app.php`的 `providers` 服务提供商 | 注册所有已配置的提供商
public function registerConfiguredProviders()
{
    // 获取应用程序配置中定义的服务提供者数组包含,然后转成集合
    $providers = Collection::make($this->make('config')->get('app.providers'))
                    ->partition(function ($provider) {
                        return strpos($provider, 'Illuminate\\') === 0;
                    });

    // 0 => 对应集合是 Laravel 框架基础服务提供商
    // 2 => 对应集合是 Laravel App 提供者
    // 1 => 对应集合是 框架发现的需要自动加载的 Package ,通过 PackageManifest ,读取 composer 进行获取的
    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

    // 然后会生成一个映射缓存
    // 关于从 composer 自动加载的会对应生成 bootstrap\cache\packages.php
    // 具体可以看 vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php

    // 遍历服务提供者数组, 
    // 实例化每个服务提供者并调用其 register() 方法,
    // 服务提供者的注册操作会将其所提供的服务注册到应用程序 APP 的服务容器中,可以全局使用
    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}
  • Illuminate\Foundation\Application App 应用的 register() 方法,用于将服务实例化并注册到 APP 容器中
// vendor\laravel\framework\src\Illuminate\Foundation\Application.php
/**
 * Register a service provider with the application.
 *
 * @param  \Illuminate\Support\ServiceProvider|string  $provider
 * @param  bool  $force
 * @return \Illuminate\Support\ServiceProvider
 */
public function register($provider, $force = false)
{
    // 是否已经注册了同名的服务提供者
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }
    // 如果传入的 $provider 参数是一个字符串,代码会将其解析为服务提供者类的实例
    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }
    // 调用服务提供者的 register() 方法。在服务提供者类中,register() 方法通常用于注册服务容器绑定和单例
    $provider->register();
    // 定义了 $bindings 属性,代码会遍历该属性,并使用 bind() 方法将其绑定到应用程序的服务容器
    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
            $this->bind($key, $value);
        }
    }
    // 定义了 $singletons 属性,代码会遍历该属性,并使用 singleton() 方法将其注册为应用程序的服务容器中的单例
    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
            $this->singleton($key, $value);
        }
    }
    // 将服务提供者标记为已注册状态,追加到数组中 ,$this->serviceProviders[]
    $this->markAsRegistered($provider);
    // 应用程序已启动时调用 boot() 方法执行服务提供者的启动逻辑
    if ($this->isBooted()) {
        $this->bootProvider($provider);
    }
    return $provider;
}

3.1.4 DB 的连接器工厂类 ConnectionFactory

  • 注册数据库连接服务 DatabaseServiceProviderregisterConnectionServices() 方法
// vendor\laravel\framework\src\Illuminate\Database\DatabaseServiceProvider.php

// 注册与数据库连接相关的服务和绑定
protected function registerConnectionServices()
{
    // 注册连接工厂,创建实际的数据库连接实例
    $this->app->singleton('db.factory', function ($app) {
        return new ConnectionFactory($app);
    });

    // 数据库管理器用于解决各种连接
    $this->app->singleton('db', function ($app) {
        // !!!!【核心】
        return new DatabaseManager($app, $app['db.factory']);
    });

    // 绑定连接解析器
    $this->app->bind('db.connection', function ($app) {
        return $app['db']->connection();
    });

    // 注册事务管理器
    $this->app->singleton('db.transactions', function ($app) {
        return new DatabaseTransactionsManager;
    });
}
  • 数据库管理器 DatabaseManager 的构造函数中 reconnection() 来创建连接
// vendor\laravel\framework\src\Illuminate\Database\DatabaseManager.php
namespace Illuminate\Database;

class DatabaseManager implements ConnectionResolverInterface
{
    public function __construct($app, ConnectionFactory $factory)
    {
        $this->app = $app;
        $this->factory = $factory;

        $this->reconnector = function ($connection) {
            $this->reconnect($connection->getNameWithReadWriteType());
        };
    }

    // 获取连接
    public function connection($name = null)
    {
        [$database, $type] = $this->parseConnectionName($name);

        $name = $name ?: $database;

        // !!!! [重点] !!! 
        if (! isset($this->connections[$name])) {
            $this->connections[$name] = $this->configure(
                $this->makeConnection($database), $type
            );
        }

        return $this->connections[$name];
    }

    // 创建连接实例
    protected function makeConnection($name)
    {
        // 获取指定连接名称的配置信息
        $config = $this->configuration($name);

        // 是否有针对该连接名称的扩展注册
        if (isset($this->extensions[$name])) {
            return call_user_func($this->extensions[$name], $config, $name);
        }

        // 是否有针对数据库驱动的扩展注册
        if (isset($this->extensions[$driver = $config['driver']])) {
            return call_user_func($this->extensions[$driver], $config, $name);
        }


        // !!!【核心】!!!
        // 连接工厂($this->factory)创建连接。调用连接工厂的 make() 方法
        return $this->factory->make($config, $name);
    }
    ...
}
  • 数据库连接器工厂 ConnectionFactorymake() 根据驱动实例化不同的连接器实例
// vendor\laravel\framework\src\Illuminate\Database\Connectors\ConnectionFactory.php
class ConnectionFactory
{

    // 根据PDO建立链接
    public function make(array $config, $name = null)
    {
        $config = $this->parseConfig($config, $name);

        if (isset($config['read'])) {
            // 根据读写分离配置来选择创建读写分离连接
            return $this->createReadWriteConnection($config);
        }

        // 创建连接单例
        return $this->createSingleConnection($config);
    }

    // 创建 数据库连接实例
    protected function createSingleConnection(array $config)
    {
        $pdo = $this->createPdoResolver($config);

        // 根据驱动类型(driver)和配置创建具体的数据库连接实例
        return $this->createConnection(
            $config['driver'], $pdo, $config['database'], $config['prefix'], $config
        );
    }

    /**
     * 创建 数据库连接实例
     *
     * @param  string  $driver
     * @param  \PDO|\Closure  $connection
     * @param  string  $database
     * @param  string  $prefix
     * @param  array  $config
     * @return \Illuminate\Database\Connection
     *
     * @throws \InvalidArgumentException
     */
    protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
    {
        // 是否连接过
        if ($resolver = Connection::getResolver($driver)) {
            return $resolver($connection, $database, $prefix, $config);
        }

        // 根据驱动类型选择适当的连接类
        switch ($driver) {
            case 'mysql':
                return new MySqlConnection($connection, $database, $prefix, $config);
            case 'pgsql':
                return new PostgresConnection($connection, $database, $prefix, $config);
            case 'sqlite':
                return new SQLiteConnection($connection, $database, $prefix, $config);
            case 'sqlsrv':
                return new SqlServerConnection($connection, $database, $prefix, $config);
        }

        throw new InvalidArgumentException("Unsupported driver [{$driver}].");
    }
}
  • MySqlConnection 对应的 select() 方法
// vendor\laravel\framework\src\Illuminate\Database\Connection.php
// vendor\laravel\framework\src\Illuminate\Database\MySqlConnection.php
// vendor\laravel\framework\src\Illuminate\Database\PostgresConnection.php

// 数据库连接的 select() 方法的实现
public function select($query, $bindings = [], $useReadPdo = true)
{
    // 调用 run() 方法,用于执行 SQL 查询
    return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
        // 模拟查询模式
        if ($this->pretending()) {
            return [];
        }
        // 创建预处理语句对象
        $statement = $this->prepared(
            $this->getPdoForSelect($useReadPdo)->prepare($query)
        );
        // 绑定参数
        $this->bindValues($statement, $this->prepareBindings($bindings));
        // 执行查询
        $statement->execute();
        // 获取结果集
        return $statement->fetchAll();
    });
}

3.1.5 回到开头,用 DB::select() 执行 SQL 语句

\Illuminate\Database\Connectors\ConnectionFactory 体现出来简单工厂模式的实现思想;具体可以看源码 ConnectionFactory

  1. 入口文件;
    HTTP 请求中,通过 Illuminate\Foundation\Http\Kernelhandle() 方法来接收请求并响应
  2. 加载 Kernel$bootstrappers 基础引导类;
    在 APP 执行 hanle() 之前会调用自身的 $this->bootstrap(); ,用来加载各个组件,例如 env 环境变量、config 应用配置、exception 异常处理机制、Facade 门面注册、Provider 注册服务提供商,boot 引导初始化,等等,每个组件会 make 实例化,并且执行对应的 bootstrap() 方法
  3. 调用 RegisterProviders 注册提供商方法;
    其中,Provider 服务提供商: Illuminate\Foundation\Bootstrap\RegisterProvidersbootstrap() 方法,会调用 APP 的 registerConfiguredProviders() 方法
  4. 每一个服务提供商会执行 register() 注册到 APP 容器中:
    那么 APP 的 registerConfiguredProviders() 主要是读取 config/app.php 配置文件中的 providers 服务提供商数组,来依次的实例化,并且通过 $app->register() 先执行每一个基础服务自身的 register() ,然后再注册到 APP 的服务容器中
  5. 其中,数据库服务提供商 DatabaseServiceProvider ,创建数据库管理器 DatabaseManager;
    执行自身 register,其中有 registerConnectionServices() 注册数据库连接服务的方法,利用 $this->app->singleton() 创建 DatabaseManager 的数据库管理器单例,构造函数中会有 connect() 连接相关的调用,也就是靠 ConnectionFactory 连接工厂完成的
  6. 数据库连接工厂 ConnectionFactorymake() 方法创建连接实例;
    根据传入的驱动,和配置文件,来识别创建不同的连接器,例如 MysqlPgsql
  7. 门面操作 \Illuminate\Support\Facades\DB 来调用 select() 方法;
    主要利用 __callStatic() 魔术方法,从而解析出来 db 对应的实例,从而调用 select() 方法执行 SQL 查询语句

4、 Summary

工厂模式,用于解耦客户端逻辑层和对象实例化过程 ,隐藏了对象实例化的细节,简化了使用者获取对象的方式。

  • 工厂模式
    • 创建型的设计模式
  • 为什么不直接 new 对象,非要搞个工厂?
    • 项目中,可能很多地方都用到的一些工具类,每次都 new 一下再用,如果可能还需要传入一些参数,比较繁琐,可以利用工厂模式来封装对象的实例化,还可以降低代码重复。
  • 工厂方法模式和抽象工厂有啥区别
    • 关注点:工厂方法在于创建单个对象,抽象工厂在于创建一组或者相互依赖的对象,比如前面例子中提到的 苹果工厂(工厂方法)、和本地工厂(抽象工厂)
    • 用途:工厂方法更多是解耦作用,把创建逻辑封装到工厂方法中;抽象工厂则是创建一组相关的产品,以达到这些对象可以协同工作
    • 实现:工厂方法,通常是有一个抽象类,然后具体工厂继承抽象类,从而创建具体的产品;抽象工厂则是一个抽象接口,具体工厂实现后,创建一组产品对象;
  • 创建的对象有什么区别
    • 简单工厂 :用来生产同一等级结构中的任意产品;追加新的产品,需要增加新的类,并且修改工厂方法
    • 工厂方法 :用来生产同一等级结构中的固定产品;追加新的产品,无需修改工厂方法,符合开闭原则
    • 抽象工场 :用来生成不同产品族的全部产品;

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~