工厂模式,从第三方登录说起


现在的很多平台在登陆的时候,下面都会有一排选项,可以选择微信、QQ、微博账号等登陆,这些账号对平台来说都是第三方账号。第三方账号登陆是最近几年流行起来的,第三方账号登录一般都是基于 OAuth2.0 协议开发的。如果你不了解 OAuth2.0 协议,可以自行百度,也许会对你看这篇文章有所帮助。

现在由于公司要给平台引入流量,为了降低注册门槛,让更多的人来使用你们的平台,领导决定在你们的平台上接入第三方账号登陆功能。现阶段先接入微信、支付宝、QQ、GitHub 这四个第三方账号登陆。这个任务也顺利的落到你的头上,由于你了解 OAuth2.0 协议,你知道这个是一个固定的三段式操作,第一步获取 Authorization Code,第二步获取 Access Token,第三步调用信息接口,但是每个平台返回来的数据字段或者格式可能会不一样,所以你根据你的开发经验,为第三方账号登录模块抽取出来了一个 IdentityProvider 抽象类,该类主要有上面提到的三步需要的接口,IdentityProvider 类的代码如下:

public abstract class IdentityProvider {

    // 获取Authorization Code
    abstract void authorizationCode();

    // 获取 Access Token
    abstract void accessToken();

    // 获取用户信息
    abstract void getUserInfo();
}

每一个第三方账号平台都继承 IdentityProvider,根据每个第三方账号平台的规则去实现这三个接口,我们已支付宝为例,我们定义一个 AliPayIdentityProvider 类,该类继承了 IdentityProvider 类,AliPayIdentityProvider 类代码如下:

/**
 * 支付宝 第三方登陆具体实现
 */
public class AliPayIdentityProvider extends IdentityProvider{

    private static final String APPID = "你申请的运用id";
    private static final String APPKEY = "你的私钥";

    public AliPayIdentityProvider() {
        System.out.println("我是支付宝第三方登陆具体实现");
    }

    @Override
    abstract void getUserInfo(){
        // 获取用户信息
    }

    @Override
    public void authorizationCode() {
        //获取authorization Code
    }

    @Override
    public void accessToken() {
        //获取access Token
    }
}

四个第三方账号登录平台都按照上面的方式做了具体的实现,除此之外,你还创建了一个 IdentityFactory 类,该类是创建实例的唯一入口,里面提供了一个静态 create 方法, create 方法的作用是根据传入的参数返回对应的第三方账号平台实例。IdentityFactory 类代码如下:

public class IdentityFactory {
    /**
     * 第三方登陆实例获取
     * @param type 标识符,1:支付宝登陆 2:微信登陆 3:QQ登录 4:github登陆 
     */
    public static IdentityProvider create(int type){
        IdentityProvider identityProvider = null;
        switch (type){
            case 1:
                identityProvider = new AliPayIdentityProvider();
                break;
            case 2:
                identityProvider = new WeChatIdentityProvider();
                break;
            case 3:
                identityProvider = new QQIdentityProvider();
                break;
            case 4:
                identityProvider = new GitHubIdentityProvider();
                break;
        }
        return identityProvider;
    }
}

客户端调用时只需要调用 create() 方法即可以获取对应的实例,比如要使用 GitHub 账号登陆,我们只要调用 IdentityProvider identityProvider = IdentityFactory.create(4); ,获取到 GitHubIdentityProvider ,获取到对象之后,可以进行 GitHub 账号登陆的具体操作。提交、部署、测试、上线,完美完成任务。

在第三方账号平台登陆功能的实现中,你使用到了一种设计模式,叫作简单工厂模式,此时你心里肯定大喊一声,卧槽,这就用上了设计模式?是的,没错,这就是设计模式。既然你好奇,那我们就一起来看看简单工厂模式。

简单工厂模式的定义

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,是创建型模式的一种。在简单工厂模式中,有一个工厂类来负责其他类的实例创建,这些被创建的实例类都有一个共同的父类,在我们的第三方账号登陆中 AliPayIdentityProviderWeChatIdentityProvider 都是被实例化的类,它们都有一个共同的父类 IdentityProvider ,在简单工厂模式中,工厂类中可以根据传入的参数返回不同的实例,在我们的 IdentityFactory 类中,我们提供了一个静态的 create(int type) ,可以根据传入的类型返回不同的实例。所以我们这个是标准的简单工厂模式的实例。

上面这一大段不好理解?没关系,那我们在抽象一下,简单工厂模式主要有以下三个成员:

抽象产品:抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口,例如 IdentityProvider 具体产品:具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例,例如 AliPayIdentityProvider 工厂类:负责实现创建所有实例的内部逻辑,例如 IdentityFactory 我们再来看一下简单工厂模式的UML图:

这些该明白简单工厂模式了吧,虽然名字中带有简单两个字,但是按照常理来说,就算再简单,也该会有一些优点吧。既然你还好奇,那就继续来简单工厂模式有哪些优点吧。

简单工厂模式的优点

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

第三方账号登陆功能上线后,你们公司平台的用户急速增强,boss甚是高兴,于是又给你安排活来了,这次boss叫你把微博账号登陆加上,实现使用微博账号登陆到你们的平台,有了前面的经验之后,这事对你来说太简单的。你给系统新增了一个 WeiBoIdentityProvider 类,用来实现微博账号登录,WeiBoIdentityProvider 类如下:

/**
 * 微博账号登陆
 */
public class WeiBoIdentityProvider extends IdentityProvider{

    private static final String APPID = "你申请的运用id";
    private static final String APPKEY = "你的私钥";

    public WeiBoIdentityProvider() {
        System.out.println("我是微博第三方登陆具体实现");
    }

    @Override
    abstract void getUserInfo(){
        // 获取用户信息
    }

    @Override
    public void authorizationCode() {
        //
    }

    @Override
    public void accessToken() {

    }
}

IdentityFactory 类中添加了 case 5 分支,用来返回微博账号登陆实例,变更之后 IdentityFactory 类如下::

public class IdentityFactory {
    /**
     * 第三方登陆验证
     * @param type 标识符,1:支付宝登陆 2:微信登陆 3:QQ登录 4:github登陆 5:微博账号
     */
    public static IdentityProvider crete(int type){
        IdentityProvider identityProvider = null;
        switch (type){
            case 1:
                identityProvider = new AliPayIdentityProvider();
                break;
            case 2:
                identityProvider = new WeChatIdentityProvider();
                break;
            case 3:
                identityProvider = new QQIdentityProvider();
                break;
            case 4:
                identityProvider = new GitHubIdentityProvider();
            case 5:
                identityProvider = new WeiBoIdentityProvider();
                break;
        }
        return identityProvider;
    }
}

部署、测试微博账号登陆,没有问题,打包上线,关机下班。上线之后,大量用户反馈GitHub账号登陆不上。小伙子,出来接锅了,于是你又要屁颠屁颠的跑回公司加班改 bug ,苦逼的程序员。你找呀找呀,最后发现了,case 4的break语句被你删掉了,所以在使用GitHub账号登陆时, IdentityFactory 工厂返回的实例一直都是 WeiBoIdentityProvider ,导致GitHub账号登陆会失败。不经意间的一个小失误,造成了一次线上事故。生产上都出事了,后果你懂的。虽然这事故是你人为造成的,但这也是简单工厂模式的缺点,你每新增第三方账号登入平台时,都需要去改动工厂类,这难免会出现这种误删的情况。简单工厂模式虽然简单,但是也有不少缺点,那我们一起看看简单工厂模式有哪些缺点吧。

简单工厂模式的缺点

  • 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就容易造成错误,就像我们上面那样,一不小心造成线上事故
  • 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

经过了这次事故之后,你一心想证明自己,重新获得领导的赏识,你下定决心要对第三方账号登陆模块进行重构。老话说的好:在哪里跌倒就要在哪里爬起来。于是你想呀想呀,最后灵光一现,需要对 IdentityFactory 类进行重构,工厂类也需要像提供方一样,提取出一个抽象类,然后每个提供方有自己的工厂,这样就可以避免新增时对原有系统模块的改动。于是你抽象出来一个 IdentityProviderFactory 类,用来定义工厂需要的接口。IdentityProviderFactory 类如下:

/**
 * 第三方登陆抽象工厂
 */
public abstract class IdentityProviderFactory<T> {
    // 创建具体的IdentityProvider
    public abstract IdentityProvider create();
}

每个第三方账号平台都需要有自己的生产工厂,这个工厂必须继承 IdentityProviderFactory 类,然后重写 create() 方法,在 create() 方法里实例化自己的 identityProvider 实现类,我们以支付宝工厂为例,我们需要创建一个 AliPayIdentityProviderFactory 类, AliPayIdentityProviderFactory 类代码如下:

/**
 * 支付宝第三方登陆工厂类
 */
public class AliPayIdentityProviderFactory extends IdentityProviderFactory<AliPayIdentityProvider> {
    @Override
    public IdentityProvider create() {
        //支付宝登录实现实例
        return new AliPayIdentityProvider();
    }
}

create() 方法中返回 AliPayIdentityProvider 实例,每个工厂都返回对应的实例就可以,客户端在调用时,也要发生相应的改变,不在传入参数来获取实例,而是通过调用对应的工厂来获取实例。比如我们使用支付宝账号登陆

// 调用支付宝工厂
IdentityProviderFactory providerFactory  = new AliPayIdentityProviderFactory();
// 获取IdentityProvider
IdentityProvider provider = providerFactory.create();
// 一些列第三方认证操作

重构之后,我们肯定不会再出现上一次的问题,因为现在每个第三方账号提供方都有自己的工厂,每个产品的构建运行都是独立的。小伙子,恭喜你,你离升职加薪不远了。

在你重构的过程中,你也将简单工厂模式进行了升级,现在它不叫简单工厂模式了,因为它已经不简单了,现在的模式叫作工厂方法模式(Factory Method Pattern)。既然我们都用上了工厂方法模式,那就不妨一起来了解一下工厂方法模式吧。

工厂方法模式的定义

工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它也是类创建型模式的一种。工厂方法模式与简单工厂模式的区别在于,在工厂方法模式中,实例的创建不是集中在一个工厂中,而是抽取出来了一个工厂父类,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。就像我们的 IdentityProviderFactory 类和 AliPayIdentityProviderFactory 类。

跟简单工厂模式一样,我们对工厂方法模式也进行抽象一下,工厂方法模式有下面四个成员:

  • 抽象产品:定义好产品具有的属性方法,例如 IdentityProvider
  • 具体产品:具体的产品实现,例如 AliPayIdentityProvider
  • 抽象工厂:定义好工厂的抽象方法,例如 IdentityProviderFactory
  • 具体工厂:具体的生产工厂,例如 AliPayIdentityProviderFactory

老惯例,一起看看工厂方法模式的UML图,加深印象:

工厂方法模式好处在我们重构第三方账号登录模块的时候,我们已经体验到了,工厂方法模式的好处可不止那么一点,一起来看看工厂方法模式有哪些优点?

工厂方法模式的优点

  • 工厂方法模式的扩展性非常强,在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,而只要添加一个具体工厂和具体产品,就可以拥抱变化,就像如果我们现在要接入钉钉账号登陆,我们只需要创建 DingDingIdentityProviderFactoryDingDingIdentityProvider 就好了
  • 良好的封装性,代码结构清晰。调用者需要一个具体的产品对象时,只需要知道这个产品的类名就可以了,不需要知道具体的创建过程,降低的模块之间的耦合
  • 屏蔽产品类,产品类的实现如何变化,调用者不需要关系,它只关系产品的接口,只要接口保持不变,系统中的上层模块就不需要变化。所以工厂方法模式经常用来解耦,高层模块只需要知道产品的抽象类,实现类不需要关系,这符合迪米特法则,也符合依赖倒置原则。

工厂方法模式虽然有诸多好处,但是它也有不少缺点,因为不可能有完美无缺的设计模式,那我们一起来看看工厂方法模式的缺点。

工厂方法模式的缺点

  • 增加了系统复杂度,我们将工厂类拆分出来,无形之中给我们的系统带来了复杂性
  • 增加了开发量,在使用简单工厂模式时,我们只想要添加一个case分支,现在则需要创建类
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度

总结

本文主要简单的介绍了一下简单工厂模式和工厂方法模式这两种设计模式,通过第三方账号登陆这个案例,从简单工厂模式开始,一步一步的到了工厂方法模式。想要更深入的了解工厂模式,需要参考大量的案例,spring等开源框架中应用了大量的设计模式,工厂模式自然少不了,不管学习哪种设计模式,我们都可以去参考这些开源框架,它能够加深你对设计模式的理解。

知识共享许可协议
《工厂模式,从第三方登录说起》 常伟华 创作。
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议 | 3.0 中国大陆许可协议进行许可。

站内公告

A PHP Error was encountered

Severity: Core Warning

Message: PHP Startup: zip: Unable to initialize module Module compiled with module API=20060613 PHP compiled with module API=20090626 These options need to match

Filename: Unknown

Line Number: 0

Backtrace: