前言
工厂方法模式的优点只有在一定场景并且有一定规模的条件下才能体现出来,为了理解工厂模式的结构通常都是以比较简单的例子来表示,主要是为了表达工厂方法模式的类间关系,因此常常会有一种明明很简单的事情却因为引进了工厂方法模式反而变得更加复杂的感觉;因此后面会介绍一下Mybatis的Mapper工厂;
工厂方法简介
描述
定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类;
类图
代码实现
/**
* 枪支
*/
abstract class Gun{
/**
* 枪支都具有的功能
*/
public abstract void shot();
}
/**
* 具体的枪支(手枪)
*/
class HandGun extends Gun{
@Override
public void shot() {
System.out.println("手枪哒 哒 哒");
}
}
abstract class AbstractGunFactory{
public abstract <T extends Gun> T createGun(Class<T> clazz);
}
/**
* 负责制造枪支
*
*/
class GunFactory extends AbstractGunFactory{
/**
* 你只需要告诉我你需要什么枪,我就给你什么枪
* @param clazz
* @param <T>
* @return
*/
@Override
public <T extends Gun> T createGun(Class<T> clazz){
try {
if(Gun.class == clazz){
return null;
}
return (T)Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null ;
}
}
/**
*杀手
*
*/
public class Killer {
@Test
public void kill(){
AbstractGunFactory gunFactory = new GunFactory();
/**告诉枪支制造厂我需要手枪即可获得手枪*/
Gun gun =gunFactory.createGun(HandGun.class);
gun.shot();
}
}
说明
上面的代码实现了对枪支制造过程的封装,所以我们再回顾一下工厂方法的描述;
定义一个用于创建对象的接口(AbstractGunFactory),让子类(GunFactory)决定实例化哪一个类(HandGun),工厂方法使一个类(HandGun)的实例化延迟到其子类(GunFactory);
分析
对于杀手来说,只需要负责杀人,枪是怎么制造出来跟他一点关系都没有;如果杀手杀人之前还需要制造枪支,那么从代码的层面不符合单一职责原则,也造成了杀手和枪支之间的强耦合,如果杀手需要机关枪,就得去修改杀手的代码,那么这就违背了开闭原则;
那么来看一下工厂模式,如果杀手需要一把机关枪我们的做法只需要增加一个实现类即可:
/**
* 机关枪
*/
class SubmachineGun extends Gun{
@Override
public void shot() {
System.out.println("机关枪哒哒哒 哒哒哒 哒哒哒");
}
}
public class Killer {
@Test
public void kill(){
AbstractGunFactory gunFactory = new GunFactory();
/**告诉枪支制造厂我需要机关枪即可*/
Gun gun =gunFactory.createGun(SubmachineGun.class);
gun.shot();
}
}
所以,工厂方法模式符合对扩展开放对修改关闭的原则,大大提高了可扩展性;
扩展-Mybatis中的工厂方法模式
说明
前面的例子因为创建一个对象的复杂度比较低,谈工厂方法模式的优点还是多少有一点牵强,所以这里将mybatis获取一个Mapper对象的过程简单梳理出来,分析一下使用工厂方法模式和不适用工厂方式的差距,下面是简单整理的时序图,过程不是很详细,有些部分被省略了;
分析
图中蓝色部分就是一个Mapper的创建过程,可以看到还是挺复杂的,但是对我们开发者来说是感受不到,如果我们每次使用一个Mapper都要亲自创建,那么复杂度可想而知;所以Mybatis框架帮我们做了这样一个事情,我们只需要告诉SqlSession我们需要的Mapper类型就好了;
那么可以看到实际上,每一个Mapper接口都是对应一个MapperProxyFactory的,从之前单个工厂的例子实际上又升级为了多个工厂;随着规模和具体场景不同,工厂方法模式的也会有不同变化;所以模式不是一成不变的,最初的目的都是解决问题,而不是为了设计而设计;
参考资料
- 书籍看了大话设计模式和设计模式之禅 还有一些博客
- 更多的是看实际的代码比如GitHub上java-design-patterns项目还有Spring、Mybatis