顾名思义,就是一种很简单的工厂模式。在业务比较简单的情况下使用。
简单工厂模式类图如下:
上述类图中有如下三种角色:
工厂类角色(Creator):这是本模式的核心,含有一定的业务逻辑和判断逻辑,在Java中往往由一个具体类实现。工厂类依赖于具体产品。
抽象产品角色(Product):一般是具体产品继承的父类或者实现的接口,在Java中由接口或者抽象类实现。
具体产品角色(ConcreteProduct):工厂类所创建的对象就是此角色的实例,在Java中由一个具体类来实现。具体产品继承于抽象产品。
功能:工厂是用来造东西的。在Java里面,通常情况下用它来造接口,但是也可以造抽象类,甚至是一个具体类实例。可以利用简单工厂来创建接口、抽象类或者普通类的实例。
静态工厂:当使用简单工厂的时候,通常不用创建简单工厂类的类实例,因为没有必要,因此可以把简单工厂类实现成一个工具类,直接使用静态方法就可以。也就是说简单工厂的方法通常都是静态的,所以也被肠胃静态工厂,如果要防止客户端误创建简单工厂类实例,可以把构造方法私有化。
万能工厂:一个简单工厂可以包含很多用来构造东西的方法,这些方法可以创造不同的接口、抽象类或者类实例,一个简单工厂,理论上可以构造任何东西,所以又被称为万能工厂。在工厂里可以有一个或者多个创造方法。
范围:理论上简单工厂什么都能创造。但是对于简单工程可创建对象的范围来说,通常建议不要太大,建议将其控制在一个独立的组件级别或者一个模块级别,否则这个简单工厂会职责不明,有点大杂烩的感觉。
命名建议:类名建议写成“模块名称+Factory”,比如用户模块的工厂为 UserFactory。方法名通常为 get+接口名称,或者是 create+接口名称,比如有个接口名为User,那么方法名通常为 getUser或者createUser。还有一些人习惯为 new+接口名称,比如newUser,通常不建议这种方式,因为new在Java中代表特定的含义,而且通过简单工厂来获取对象的实例,并不是每次都new,因为使用new可能会造成误会错觉。
简单工厂的思想源于Java中的接口,Java中接口是一种特殊的抽象类,跟一般的抽象类相比,接口中所有的方法都是抽象的,接口中所有的属性通常都是常量,也就是说接口里只有定义没有实现。
(1)接口用来干什么
通常用接口来定义实现类的外观,也就是定义实现类的行为,也可以说用来约束实现类必须实现的行为。但是通常实现类除了实现接口定义的方法外,还可以根据需要实现一些其它的功能,也就是说实现类的功能包含但是不仅限于接口约束的功能。通过使用接口可以让不相关的类实现相同的行为,而不需要考虑这些类之间的层次,接口就是实现类对外的外观。
(2)接口的思想
根据接口的作用和用途,简单来说,接口的思想就是封装隔离。一般的封装是封装数据,接口的封装是封装行为,也可以说是职责。隔离指的是外部调用和内部实现的隔离,外部调用只能通过接口调用,而不知道内部具体实现。
(3)使用接口的好处
由于外部调用和内部实现被隔离了,那么只要接口本身不变,内部实现的变化就不会影响到外部调用的代码,从而使得系统更灵活,更具有扩展性和可维护行,也就是常说的接口是系统可插拔性的保证。
(4)接口和抽象类的选择
接口是一种特殊的抽象类,在开发中如何选择,一般遵循两点:
》优先选用接口
》如果既要定义子类的行为,又要为子类提供功公共的功能,要选择抽象类
除了客户端调用代码,简单工厂模式大致可以分为三种代码,一是定义客户端所需要功能的接口,二是工厂类,用来选择合适的实现类创建接口对象,三是各种实现类。
接下来举个简单的例子,可以把运动员看成一个总的接口,分别有篮球运动员和足球运动员两个实现类,要想成为运动员必须在联盟注册,球员联盟可以看成一个工厂。
首先看一下运动员的接口代码:
可以看到接口中只定义了一个注册方法,传入了一个字符串,注册方法就是所有运动员都必须有的行为。
接下来看足球运动员和篮球运动员两个具体实现类:
可以看到两个实现类也非常简单,在实现方法中只是打印了身份和传入的字符串。
接下来看一下工厂类的具体内容:
工厂类定义了一个create接口对象的方法,根据传入的条件判断使用哪个实现类创建实例,内容也很简单。
上面四个类构成了工厂模式的全部要素。使用的时候分两步,首先通过工厂类根据传入的条件返回一个运动员实例,然后运行注册方法就可以看到成功创建了实例,调用代码如下:
运行结果如下:
这样可以让工厂对创建实例而不再由具体的类去创建。从调用代码看,我们也不关心具体的实现是什么,也不知道如何具体实现。事实上简单工厂帮助我们真正开始面向接口编程,以前的做法只是接口多态的功能,最重要的封装隔离性并没有体现出来。让调用者不知道内部具体实现。
上面的代码虽然仅仅是把new操作放到了工厂方法里面,但是却真正实现了封装和隔离,让调用代码无法了解具体的实现内容,而工厂和实现封装在一起是没有关系的。虽然仅仅是new操作的位置变了,却发生了质的变化。
上面的简单工厂也有不足,如果接口和实现类不变,新增了一个实现类,那么工厂类的方法必须做出修改,在一定程度上增加了维护成本。要想做到增加了实现类而不修改任何代码,一个简单的思路是利用配置文件和反射来实现动态新增创建,新建一个配置类simple.properties,内容如下:
可以看到定义了传入条件和具体实现类的键值对。
接下来我们要对工厂类的创建方法做出修改:
可以看到方法根据传入的条件动态获取配置文件中的类路径,然后利用反射创建对应实例,这样新增实现类只需要增加一行配置即可,不需要修改任何代码。
首先不新增实现类,运行前面的客户端代码,
可以看到结果正确。然后新增一个实现类,
然后新增一行配置:
修改客户端代码并运行:
可以看到实现类增加实现类而无需修改代码。
简单工厂模式的总结:
优点
(1)实现了组件封装,让组件外部真正面向接口编程
(2)实现了客户端和具体实现类的真正解耦
缺点
(1)根据参数决定创建的具体实例,势必让客户端理解参数所代表的含义,也暴露了部分内部实现。
(2)不方便通过工厂的子类改变创建接口的方法行为,不过一般也不需要。
简单工厂的本质就是,选择实现,屏蔽具体类。
使用建议:灵活传递选择条件,可以使用配置文件,也可以读取运行期间内存的某个值。