设计模式-工厂模式

什么是工厂模式
    工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。工厂模式在java程序系统可以说是随处可见,因为工厂模式就是相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a = new A()工厂模式也是用来创建实例对象的,所以以后new对象就要多个心眼,是否可以考虑使用工厂模式。虽然这样做可能多做一些工作,但会给系统带来更大的可拓展性和尽可能少的修改量。

简单工厂模式
    简单工厂模式是工厂模式的第一种形式,又叫。顾名思义,因为这种工厂模式中获取对象的方法一般定义成静态方法,形式如下:

1
2
3
4
5
public class Factory {
public static <R,P> R getInstance(P type) {
// 根据传入的type不同做if else判断返回不同类型的对象。
}
}

    或者还有另一种形式:

1
2
3
4
5
6
7
8
public class Factory {
public static <R,P> R getInstance01() {
// 创建一种类型的对象
}
public static <R,P> R getInstance02() {
// 创建另一种类型的对象
}
}

    这两种形式本质上无非就是我们是通过直接修改返回值(或者修改配置文件)来返回不同类型对象,这边是通过对传入的字段判断达到返回不同类型对象的目的。
    当然,你也可以将对象的方法定义成普通方法,然后通过Factory的实例调用其getInstance(),完全没问题,如下:

1
2
3
4
5
public class Factory{
public <R,P> R getInstance(P type) {
// 根据传入的type不同做if else判断返回不同类型的对象。
}
}

    其实这种方式又叫做。这两种方式的区别在于,静态工厂可以直接用类名调用而不需要创建出工厂类的实例。但是我们知道,静态方法可以继承,但是没有办法被重写,没有重写也就不可能实现多态的特性了。所以静态工厂是没有办法通过继承方式来动态改变工厂创建对象的行为的。
    优缺点:通过上面这个例子,我们不难看出,简单工厂模式确实在一定程度上实现代码的解耦,而这种解耦的特点在于,这种模式将对象的创建和使用分离,其实这个特点是所有创建型模式的共性。另外,简单工厂模式也起到了一定的封装功能。假设DaoFactory.getDao()返回的JpaDao和MybatisDao对象创建过程非常繁琐冗长,如果不使用工厂模式,JpaDao和MybatisDao对象的创建逻辑就会散落到项目中各个Service类,以及其他用到该对象的地方,造成代码重复冗余。但是这种模式的缺点也很明显,不符合开闭原则。这种模式的本质在于通过一个传入的参数,做if else判断,来达到返回不同类型对象的目的。因此,如果需要增加新的类型,就不得不去修改原来代码,违反开闭原则。虽然这个问题可以通过前面反射+外部配置文件的方式在一定程度上得到缓解,但是这只是一种“投机取巧”的解决方案,并没有从根本上解决简单工厂模式的问题。
工厂方法模式
    工厂方法模式的产生就是为了解决上面提到的简单工厂模式所遇到的问题的。工厂方法模式是在简单工厂模式基础上进一步抽象,将工厂类设计成抽象类或接口,不同的产品实现各自的工厂实体类。创建对象时,只需要提供对应的产品类和该产品的工厂类即可,不需要知道内部创建工程。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface DaoFactory {
Dao getDao();
}
class JpaDaoFactory implements DaoFactory {
public Dao getDao() {
return new JpaDao();
}
}
class MybatisFactory implements DaoFactory {
public Dao getDao() {
return new MybatisDao();
}
}

    然后在创建Service类时将具体的实现类对应的工厂类传入,利用多态就可以实现不同的持久层实现切换。代码如下:

1
2
3
4
5
6
7
8
9
public class Service {
private Dao dao;
public service(DaoFactory daoFactory) {
this.dao = daoFactory.getDao();
}
void service() {
// 使用dao进行一系列持久层操作
}
}

    优缺点:工厂方法模式轻松解决了简单工厂模式的问题,符合开闭原则。在上面例子中,当需要切换另外某持久层实现,比如JDBC,此时只需要提供一个对应的JDBCFactory实现DaoFactory的getDao方法,返回对应的Dao对象即可,对于原先代码再不需要做任何修改。但是每个类型的对象都会有一个与之对应的工厂类。如果对象的类型不是很多,那还好。如果对象的类型非常多,意味着会需要创建很多的工厂实现类,因此非常繁琐,造成类数量膨胀,对后续维护带来一些麻烦。
抽象工厂模式
    抽象工厂模式是工厂模式的第三种形式,也是工厂模式中最为复杂和难以理解的一种形式。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface Weapon {}
interface Gun extents Weapon {}
interface Bullet extents Wrapon {}
// A类武器
class GunA implements Gun {}
class BulletA implements Bullet {}
// B类武器
class GunB implements Gun {}
class BulletB implements Bullet {}
// 顶级武器工厂
public interface WeaponFactory {
Gun getGun();
Bullet getBullet();
}
// A类武器工厂,用来创建A类武器,包括A类手枪和A类子弹
class AWeaponFactory implements WeaponFactory {
public Gun getGun() {
return new GunA();
}
publi Bullet getBullet() {
return new BulletA();
}
}
// B类武器工厂,用来创建B类武器,包括B类手枪和B类子弹
class BWeaponFactory implements WeaponFactory {
public Gun getGun() {
return new GunB();
}
public Bullet getBullet() {
return new BulletB();
}
}

    其实上面的A类武器和B类武器有一个专用术语叫做产品族。抽象工厂模式就是为了创建一系列以产品族为单位的对象,而产品族内各个单独组件关系密切。这样在需要大量系列对象是可以大大提高开发效率,降低维护成本。
如何选择工厂模式

  • 从设计原则来说,简单工厂模式不符合开闭原则。但是很神奇,在实际场景中,简单工厂模式确实用的最多。有以下原因:①.实现方式相对比较简单,维护起来也不困难。②.实际场景中需要工厂模式创建的对象数量一般也不会特别多。③.可以通过反射+外部配置文件的手段解决开闭原则的问题。
  • 工厂方法模式是专门用于解决单个对象创建工作,本身模式没问题,也符合开闭原则。但是存在工厂类数量膨胀的问题。如果需要创建的工厂类不是很多,是一种不错的选择。
  • 抽象工厂模式天生就是为生产产品族而生的。所以如果你需要创建的对象非常之多,但对象之间存在明显产品族特征,那么这个时候用抽象工厂模式非常合适。