如何在Laravel工程中优雅的实践工厂模式

本文将分享如何在laravel工程里面实践工厂模式。

什么是工厂模式

工厂模式是对一组类的实例化管理方法的实践总结。所谓一组类是这些类属于一个领域体内,具有一些共性,也有不同的个性, 往往在实际的业务场景之下,会需要用到不同类的实例去做业务逻辑处理。为了避免重复性的类实例过程,就需要一个工厂类 去管理和协调类的实例化。就像一个实际的工厂那样,每个生产线,会根据不同的设计磨具去生产对应的产品那样。

工程模式的优点

  1. 能减少重复代码量。
  2. 能解耦类的实例化和类使用过程。
  3. 提高了系统的灵活性,需要添加新类时可灵活扩展。

Laravel框架里面的工厂模式

其实在Laravel的框架源码里面有很多地方用到了工厂模式。比如:Illuminate\Auth模块

AuthManager作为工厂角色承担 对AuthFacade类的管理,可以看到SessionGuardTokenGuard在AuthManager中有对应的实例化处理:

    /**
     * Create a session based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        // When using the remember me functionality of the authentication services we
        // will need to be set the encryption instance of the guard, which allows
        // secure, encrypted cookie values to get generated for those cookies.
        if (method_exists($guard, 'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        if (method_exists($guard, 'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard, 'setRequest')) {
            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
        }

        return $guard;
    }

    /**
     * Create a token based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\TokenGuard
     */
    public function createTokenDriver($name, $config)
    {
        // The token guard implements a basic API token based guard implementation
        // that takes an API token field from the request and matches it to the
        // user in the database or another persistence layer where users are.
        $guard = new TokenGuard(
            $this->createUserProvider($config['provider'] ?? null),
            $this->app['request']
        );

        $this->app->refresh('request', $guard, 'setRequest');

        return $guard;
    }

AuthManager通过guard方法来实例化不同的AuthGuard类:

    /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();
        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    ...
    // 再通过resolve方法做最终的实例化
    /**
     * Resolve the given guard.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);
        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }
        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }
        throw new InvalidArgumentException("Auth driver [{$config['driver']}] for guard [{$name}] is not defined.");
    }

同时利用魔术方法__call实现对实现类方法的代理调用。

    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }

最后通过Laravel工厂管理类AuthManager以单例的形式注入到Laravel的容器内:

    /**
     * Register the authenticator services.
     *
     * @return void
     */
    protected function registerAuthenticator() 
    {
        $this->app->singleton('auth', function ($app) {
            // Once the authentication service has actually been requested by the developer
            // we will set a variable in the application indicating such. This helps us
            // know that we need to set any queued cookies in the after event later.
            $app['auth.loaded'] = true;
            return new AuthManager($app);
        });
        ...
    }

这样处理的目的是可以方便的建立AuthFacade,从而可以直接 实现下面极其简洁而直观的写法:

    Auth::guard("session")->check()

在Illuminate\Cache通过CacheManager实现多个缓存驱动, 在Illuminate\Filesystem通过FilesystemManager实现多个文件驱动, 均是通过上面的思路实现了工厂模式。

如何优雅的实践

Laravel框架的源码里面包含了很多可以学习和借鉴的设计模式实现范例,甚至上面所写的工厂模式,Laravel框架源码里面已经提供了一个通用的工厂实现抽象类Manager来方便开发者利用它实现工厂模式。仔细看它的内部结构, 其实和上面的几个Manager类的思路是一致的。我们在业务开发中,如果需要实现工厂模式时,完全可以继承Manager抽象类,来管理自己的类实例化。

例如下面的写法:

    use Illuminate\Support\Manager;
    use Illuminate\Support\Facades\Facade;
    use Illuminate\Support\ServiceProvider;

    // 工厂管理类
    class DemoManager extends Manager
    {
        public function getDefaultDriver()
        {
            return 'default';
        }

        public function createDefaultDriver()
        {
            return new DefaultDemo;
        }

        public function createDemo1Driver()
        {
            return new OneDemo;
        }
    }

    // DefaultDemo
    class DefaultDemo 
    {
        public function foo()
        {
            return 'bar';
        }
    }

    // DefaultDemo
    class OneDemo 
    {
        public function foo()
        {
            return 'bar1';
        }
    }
    
    // 工厂门面
    class DemoFacade extends Facade
    {
        /**
        * Get the registered name of the component.
        *
        * @return string
        */
        protected static function getFacadeAccessor()
        {
            return 'demo';
        }
    }

    // 在ServiceProvider实例工厂管理类单例
    class DemoServiceProvider extends ServiceProvider 
    {
        public function boot()
        {
            $this->app->singleton('demo', function ($app) {
                return new DemoManager($app);
            });

            // 类名转名
            if (! class_exists('Demo')) {
                class_alias(DemoFacade::class, 'Demo')
            }
        }
    }
    

这样我们便可方便的使用如下的代码:

    // 使用示例
    Demo::driver()->foo(); // 输出 'bar'
    Demo::driver("demo1")->foo(); // 输出 'bar1'