工厂模式 (Factory)
手握设计模式宝典 - 工厂模式 Factory
Design-Pattern - Creational - Factory
- Title: 《手握设计模式宝典》 之 工厂模式
Factory
- Tag:
Design-Pattern
、Creational
、Factory
、工厂模式
、设计模式
、简单工厂
、工厂方法
、抽象工厂
- Author: Tacks
- Create-Date: 2023-08-21
- Update-Date: 2023-08-21
大纲
0、REF
- @refactoringguru - 工厂方法
- @refactoringguru - 抽象工厂
- @PHP设计模式全集 - 简单工厂模式(Simple Factory)
- @PHP设计模式全集 - 工厂方法模式(Factory Method)
- @PHP设计模式全集 - 抽象工厂模式(Abstract Factory)
- 解读 Laravel 中的扩展自动注册机制(Package Auto-discovery)
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/else
或switch/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
菠萝APineappleB
菠萝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
樱桃ACherryB
樱桃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\Kernel
的 handle()
方法 中会调用 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
- 注册数据库连接服务
DatabaseServiceProvider
的registerConnectionServices()
方法
// 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);
}
...
}
- 数据库连接器工厂
ConnectionFactory
的make()
根据驱动实例化不同的连接器实例
// 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
- 入口文件;
HTTP 请求中,通过Illuminate\Foundation\Http\Kernel
的handle()
方法来接收请求并响应 - 加载
Kernel
的$bootstrappers
基础引导类;
在 APP 执行hanle()
之前会调用自身的$this->bootstrap();
,用来加载各个组件,例如 env 环境变量、config 应用配置、exception 异常处理机制、Facade 门面注册、Provider 注册服务提供商,boot 引导初始化,等等,每个组件会make
实例化,并且执行对应的bootstrap()
方法 - 调用
RegisterProviders
注册提供商方法;
其中,Provider 服务提供商:Illuminate\Foundation\Bootstrap\RegisterProviders
的bootstrap()
方法,会调用 APP 的registerConfiguredProviders()
方法 - 每一个服务提供商会执行
register()
注册到 APP 容器中:
那么 APP 的registerConfiguredProviders()
主要是读取config/app.php
配置文件中的providers
服务提供商数组,来依次的实例化,并且通过$app->register()
先执行每一个基础服务自身的register()
,然后再注册到 APP 的服务容器中 - 其中,数据库服务提供商
DatabaseServiceProvider
,创建数据库管理器DatabaseManager
;
执行自身register
,其中有registerConnectionServices()
注册数据库连接服务的方法,利用$this->app->singleton()
创建DatabaseManager
的数据库管理器单例,构造函数中会有connect()
连接相关的调用,也就是靠ConnectionFactory
连接工厂完成的 - 数据库连接工厂
ConnectionFactory
的make()
方法创建连接实例;
根据传入的驱动,和配置文件,来识别创建不同的连接器,例如Mysql
、Pgsql
等 - 门面操作
\Illuminate\Support\Facades\DB
来调用select()
方法;
主要利用__callStatic()
魔术方法,从而解析出来db
对应的实例,从而调用select()
方法执行 SQL 查询语句
4、 Summary
工厂模式,用于解耦客户端逻辑层和对象实例化过程 ,隐藏了对象实例化的细节,简化了使用者获取对象的方式。
- 工厂模式
- 创建型的设计模式
- 为什么不直接 new 对象,非要搞个工厂?
- 项目中,可能很多地方都用到的一些工具类,每次都
new
一下再用,如果可能还需要传入一些参数,比较繁琐,可以利用工厂模式来封装对象的实例化,还可以降低代码重复。
- 项目中,可能很多地方都用到的一些工具类,每次都
- 工厂方法模式和抽象工厂有啥区别
- 关注点:工厂方法在于创建单个对象,抽象工厂在于创建一组或者相互依赖的对象,比如前面例子中提到的 苹果工厂(工厂方法)、和本地工厂(抽象工厂)
- 用途:工厂方法更多是解耦作用,把创建逻辑封装到工厂方法中;抽象工厂则是创建一组相关的产品,以达到这些对象可以协同工作
- 实现:工厂方法,通常是有一个抽象类,然后具体工厂继承抽象类,从而创建具体的产品;抽象工厂则是一个抽象接口,具体工厂实现后,创建一组产品对象;
- 创建的对象有什么区别
- 简单工厂 :用来生产同一等级结构中的任意产品;追加新的产品,需要增加新的类,并且修改工厂方法
- 工厂方法 :用来生产同一等级结构中的固定产品;追加新的产品,无需修改工厂方法,符合开闭原则
- 抽象工场 :用来生成不同产品族的全部产品;
推荐文章: