胡琪

为今天工作,为明天投资,为未来孵化一些东西!

【设计模式之】工厂方法模式

在讲工厂方法模式之前先来看看简单工厂模式,虽然简单工厂模式不属于23种设计模式的范畴,但是前面提到过,学习设计模式以应用为主,简单工厂模式在实际开发中使用的场景也是很多的,在Android源码和很多知名开源项目中都能看到简单工厂模式的身影。

简单工厂模式

应用场景

  • 隔离复杂对象的创建和使用,所谓复杂对象是指不能简单通过一个new操作符就创建的对象,这种情况往往是需要经过一系列函数调用才能拿到调用该对象构造函数的参数
  • 根据不同参数返回不同类型的实例对象(这些实例对象往往具有相同的父类)。本质上是使用一个工厂类来包装创建其他一系列产品类的细节。客户端在使用时只需要给一个参数(或调用工厂类中不同函数)即可返回该参数对应的对象类型。

简单工厂模式知名项目分析

在安卓中最典型的使用到简单工厂模式的就是android中的Bitmap生成类BitmapFactory。从名称就可以看出来这个类使用了工厂方法模式,实际上使用的是简单工厂模式。看下这个类的结构图:

《【设计模式之】工厂方法模式》

从上面可以看到BitmapFactory类包含一系列创建Bitmap对象的方法,这些方法的参数不同,总共可以概括为三大类:

  • 根据文件创建(包括文件路径和文件描述符2类)
  • 根据Resource创建
  • 根据I/O操作创建(包括流操作和字节数组)

在使用时只需要根据自己的需要调用相应的方法即可得到对应的Bitmap对象。具体创建细节被隐藏在了BitmapFactory中。这就是简单工厂方法在安卓源码中的一个典型的应用场景。那么这里为何安卓设计者要使用简单工厂方法呢?我们选一个decodeStream方法看下实现:

从上面的代码示例可以看出,简单工厂模式中创建具体产品类的方法都声明为public static的,因此简单工厂模式也被称作静态工厂模式。

从代码的实现可以看到,虽然我们传入的只是一些简单参数,如InputStream。但是内部创建Bitmap的逻辑是比较复杂的,不是简单的拿我们传入的参数去new一个Bitmap出来。这里实际上应用的就是简单工厂方法模式的第一种场景,创建复杂对象时,我们可以将这些复杂对象的创建放到一个单独的Factory类中,对外提供一个相对简单的工厂方法,隐藏具体实现细节。这么做有什么好处呢?首先这么做一个很明显的好处就是对于调用者而言使用更加简单了,还有另外一个好处就是如果要修改创建某种参数类型Bitmap的实现逻辑,只需要修改简单工厂中对应方法的一处逻辑即可。能很容易做到一改全改。

假设不提供简单工厂来创建这些对象,那么在项目的任何一个需要使用到该复杂对象的地方都需要调用者自己实现,一旦后续创建该对象的逻辑需要修改,那么项目的每一个使用到该对象的地方都需要修改,如果只是一个通过简单的new操作符就能创建的对象这么修改还是比较容易的,但是对于一个复杂对象的创建在项目的多处地方修改很容易出现不可预期的错误。

下面举个第2种应用场景的例子大家就很容易理解了。在安卓开发中网络请求是肯定会用到的,主流的知名网络请求框架包括Volley,OkHttp,Retrofit等。而在产品不同时期,不同场合下可能网络框架的选型不一样。假设之前你们团队使用了Volley作为项目的网络请求框架,现在发现Retrofit更好用,想切为Retrofit,最简单的做法就是直接把之前用到Volley的那部分逻辑改为用Retrofit实现,但万一使用Retrofit之后出现了难以解决的bug,又需要切为Okhttp,亦或者随着时间推移谷歌又发布了一个更好更强大的网络框架VolleyPlus,想尝试VolleyPlus怎么办?难道又把项目中使用Retrofit部分的逻辑改为VolleyPlus?如果你们面临这种需求,很显然这么直接改来改去是非常不明智的,项目比较大的时候频繁的改动很可能会出现隐藏的bug,为了确保改动之后无难以发现的bug,意味着整个项目需要重新测试。所以直接改来改去的代价是非常高的。这种情况下简单工厂方法模式就很适合解决这种问题了。

使用简单工厂模式将对象的创建交给工厂统一管理,客户端使用只需要指定一个参数,由工厂返回该参数代表的对象。这样如果要切换使用的网络框架的类型,只需要在调用工厂方法的时候传递另外一个参数即可。这么说可能不大好理解,对照着看下代码就很容易理解了:

首先定义一个接口INetWorkClient,在该接口中定义需要包装的框架的各种网络请求,为了简单这里假设只存在get和post两种请求。

然后实现各个网络框架对这2个接口的具体功能。如下所示:

Retrofit和上面类似,这里就不贴代码了,然后定义一个工厂类,用来根据不同参数返回具体的网络客户端类型。

这样在使用时只需要传递不同参数即可切换不同网络框架,如下所示:

使用Volley框架只需要在createNetWorkClient函数中传递NetWorkFactory.VOLLEY参数即可。如果要切换到Okhttp框架,只需将项目中createNetWorkClient函数的参数替换为NetWorkFactory.OKHTTP。亦或是将需要使用的网络框架类型放在常量类中统一管理,统一切换。

上面的场景就是前面提到的简单工厂模式的第2个应用场景:根据不同参数,返回不同类型实例,这些实例对象一般拥有相同的父类或实现了同一个接口。

从这个实例更容易体会到前面说的一改全改的好处。之所以能够具备这种好处,是因为简单工厂模式也属于创建型模式,其作用也达到了将对象的创建和使用分离的效果,对象的创建交给工厂类来管理。对客户端而言隐藏具体实现细节。

工厂方法模式

应用场景:工厂方法模式的应用场景和简单工厂模式差不多。

工厂方法模式对于简单工厂模式而言,主要是克服了简单工厂模式在工厂类创建新的产品类的时候扩展性差的问题。比如上面的实例,如果要增加一个VolleyPlus框架,那么需要首先需要增加一个VolleyPlusClient类实现INetWorkClient接口,同时修改NetWorkFactory类,在switch-case语句中增加新的分支实现逻辑。对于网络框架这个示例是没什么问题的,因为主流的网络框架就那么几个,但是很显然如果是其他场景,那么随着项目推移,这里的switch-case语句的业务逻辑越来越复杂。也就是工厂类本身会越来越复杂。另外很明显如果新增一个网络框架类型,那么必须修改工厂类NetWorkFactory,不符合开闭原则。而工厂方法模式就能够避免简单工厂容易出现的上述问题。

工厂方法模式相对于简单工厂而言主要是把简单工厂中负责生产具体产品类的核心工厂类给去掉了,其职责被一个抽象工厂接口替代,具体产品类的创建由实现了该抽象工厂接口的具体工厂类创建,而不在放到一个工厂类中去统一创建管理。

工厂方法版的网络框架选择

还是以上面网络框架的例子为例,如果用工厂方法来实现,代码如下:

首先定义一个工厂接口IFactory,该接口中定义了其他具体工厂应该实现的接口方法

然后具体的工厂类实现该接口,创建具体的产品类

创建RetrofitClient的工厂类和上面类似,这里就不贴代码了。在客户端使用时只需要根据需要创建相应的工厂即可:

从这里可以看到工厂方法模式相比较简单工厂模式而言,去除了统一创建各个产品类的工厂类,各个产品类的创建不在是在一个工厂类中,而是通过创建具体的工厂,然后由该具体工厂(如上例中的VolleyFactory)来创建具体产品类(如上例中的VolleyClient),进而使用该产品类的功能。如果要切换到其他网络框架,只需要客户端在调用时把new VolleyFactory()替换为对应的工厂类即可。另外如果需要新增加一个网络框架。只需要创建一个新的工厂类实现IFactory接口,在该工厂类中创建对应的产品类即可。符合开闭原则。

使用简单工厂模式/工厂方法模式的好处

和任何一种设计模式一样,使用它的好处和应用场景是一一对应的:

  • 隔离复杂对象创建的场景:将对象的创建和使用分离,由工厂类提供静态方法来创建对象,所以一个很明显的好处就是对于接入者而言使用更简单(对于一个优秀的框架而言,灵活,简单的API接口是非常重要的,个人觉得这是衡量一个开发者设计能力的重要标准
  • 根据不同参数返回不同类型实例对象场景:将多个产品类对象的创建统一交给工厂类来创建,对外提供一个接口来根据不同参数创建不同产品类对象,易于统一管理,很容易做到一改全改,易于维护。

就目前我接触到的一些开源项目来看,一般使用简单工厂模式的次数高于使用工厂模式次数。需要注意的是对于第一种应用场景,如果要创建的对象很容易通过一个new操作创建的话,不建议使用简单工厂模式,对于第2中场景则无此要求。

 

 

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注