/ dp

DP 工厂模式

这是一个漫长的故事,故事的起因是小俞心血来潮搞了一个个人博客。小俞的博客因为一开始没啥人气,想想自己写一个登录注册的完整系统还是麻烦了点,最终选择了先使用微信的第三方登录。

注意,引入第三方登录对于需要登录注册的个人网站是很有效的驻留用户的手段。

小俞创建了一个User接口以及一个实现User接口的WxUser类。

interface User 
{
    public function login();
}

class WxUser implements User
{
    public function login()
    {
        // weixin users login
    }
}

// clinet
$wxUser = new WxUser();
$wxUser->login();

小俞的博客上线之后,又趁热打铁写了一个cms内容管理,一个商城,这三个项目共用一套用户系统。那个时候小俞还不知道单点登录的实现,那么小俞的项目中就会存在3个需要登录的地方,当然以后可能会更多。

小俞从第三方登录的便捷中受益后,觉得只有微信这一个入口太少了,决定再引入qq登录。于是乎小俞又创建了一个实现User接口的QqUser类,并在客户端修改了调用的代码。

interface User 
{
    public function login();
}

class WxUser implements User
{
    public function login()
    {
        // weixin users login
    }
}

class QqUser implements User
{
    public function login()
    {
        // qq users login
    }
}

// client
$type = $_GET['type'];

switch ($type) {
    case 'wx':
        $user = new WxUser();
        break;
    case 'qq':
        $user = new QqUser();
        break;
    default:
        // throw error
}
$user->login();

小俞接收了一个来自客户端的参数$type,然后根据$type的值来生成不同的实例。

注意,这里并没有对$type这个参数做过滤,实际开发中要对客户端传过来的数据做过滤,这里仅做演示。

在修改的过程中,聪明的小俞发现了一个问题。这一次小俞修改了3个new WxUser的地方,如果项目越来越多地方需要使用登录的话,需要修改的地方将会越来越多。这一切都是生成实例的类的锅,如果有一个可以提供自由切换生成实例的方法,那这个问题就解决。这个时候我们的简单工厂模式就登场了。


接下来我们可以创建一个UserFactory工厂类,它提供了一个静态方法,作用就是生成一个我们需要登录的用户实例。

interface User
{
    public function login();
}

class WxUser implements User
{
    public function login()
    {
        // weixin users login
    }
}

class QqUser implements User
{
    public function login()
    {
        // qq users login
    }
}

class UserFactory
{
    public static function create($type)
    {
        switch ($type) {
            case 'wx':
                $user = new WxUser();
                break;
            case 'qq':
                $user = new QqUser();
                break;
            default:
                // throw error
        }

        return $user;
    }
}

// client
$type = $_GET['type'];
$user = UserFactory::create($type);
$user->login();

现在我们使用工厂类来创建我们需要的实例,这样我们只需要修改工厂类这一个地方,而不需要修改3次调用实例的地方。

热爱刷微博的小俞在做完qq登录的接入后想顺便把微博的第三方登录也接入了,这时候小俞发现要添加一个微博的登录需要添加一个实现User接口的WeiboUser类,以及在UserFacorycreate方法的switch里加一个case。那么问题来了,万一以后引入更多的第三方或者做自己的登录注册方法,那么需要不断的修改UserFacorycreate方法,这一点很容易忘记,方法也会越来越臃肿。现在,我们需要改造下我们的UserFactory,改造的工具叫做工厂方法模式

我们将UserFactory也变成一个接口,然后创建3个实现了它的工厂类,WxUserFactory类、QqUserFactory类与WeiboUserFactory类。

现在我们的用户类:

interface User
{
    public function register();
}

class WxUser implements User
{
    public function login()
    {
        // weixin users login
    }
}

class QqUser implements User
{
    public function login()
    {
        // qq users login
    }
}

class WeiboUser implements User
{
    public function login()
    {
        // weibo users login
    }
}

现在我们的工厂类:

interface UserFactory {
    public static function create();
}

class WxUserFactory implements UserFactory
{
    public static function create()
    {
        return new WxUser();
    }
}

class QqUserFactory implements UserFactory
{
    public static function create()
    {
        return new QqUser();
    }
}

class WeiboUserFactory implements UserFactory
{
    public static function create()
    {
        return new WeiboUser();
    }
}

现在我们的客户端调用:

$type = $_GET['type'];

switch ($type) {
    case 'wx':
        $user = WxUserFactory::create();
        break;
    case 'qq':
        $user = QqUserFactory::create();
        break;
    case 'weibo':
        $user = WeiboUserFactory::create();
        break;
    default:
        // throw error
}
$user->login();

简单了分析下,现在我们将工厂也变成了一个接口,并将具体的实例化对象的操作交给各个具体的工厂了,解放了原本作为上帝类(做所有的事)的UserFactory类。当然这个方法也会有问题,比如我们的类会剧增,当登录方式变多时,会出现更多的工厂类,这是后话了,解决问题先从眼前出发。

随着用户量的增加,小俞发现cms内容管理的网站用户量剧增(可能是发车发多了),于是乎小俞决定给用户信息做个缓存。之前小俞用的是mysql的数据库,并创建了2张用户相关的表,用户信息表和用户授权表(存放第三方授权的信息),具体到类是UserTable类和OauthTable类,并且实际数据库的操作存放在UserModel类和OauthModel类中。

现在小俞决定用redis来做缓存,这时候你有了2种数据库,需要在这2种数据库中存放与取出用户的信息。小俞当然不想为了实现redis的数据库操作直接拷贝UserModel类和OauthModel类,那太麻烦了,而且比较难分辨到底使用的哪种数据库。那如果将我们的数据库也比作一个工厂,让这个工厂来生成实际调用的数据库操作实例不就方便很多,这就是抽象工厂模式

用户表部分

interface UserTable
{

}

class MysqlUserModel
{

}

class RedisUserModel
{

}

用户授权表部分

interface OauthTable
{

}

class MysqlOauthModel
{

}

class RedisOauthModel
{

}

数据库工厂部分

interface DataBaseFactory
{
    public static function createUserModel();
    public static function createOauthModel();
}

class MysqlFactory implements DataBaseFactory
{
    public static function createUserModel()
    {
        return new MysqlUserModel();
    }

    public static function createOauthModel()
    {
        return new MysqlOauthModel();
    }
}

class RedisFactory implements DataBaseFactory
{
    public static function createUserModel()
    {
        return new RedisUserModel();
    }

    public static function createOauthModel{
        return new RedisOauthModel();
    }
}

抽象工厂模式其实就是复杂化的工厂方法模式,工厂产出的不再是单个产品(Use接口的实现),而是可以产出一个系列的产品(UserTable和OauthTable的实现)。这个工厂模式同样是不适合增加需求的,一旦增加需求我们所需创建的类会剧增。

完。下一章