1、动机与定义
我们在程序中使用一个对象时,需要new一下,如果需要设置其他值就再初始化一下。比如我要使用一个按钮,手动new一个矩形按钮,然后初始化一些值,如显示文字,背景色等。
// 矩形按钮
IButton btn = new RecButton();
// 初始化其他值
btn.setText("提交");
btn.setBackgroundColor("#00aaff");
// 其他初始化省略
// 圆形按钮
IButton btn2 = new RoundButton();
btn.setText("关于");
btn.setBackgroundColor("#00aaff");
// 其他初始化省略
这样写有几个缺点:
1、写一次没有问题,如果需要100个,就要写100次,太麻烦了。
2、很多设置是重复的,比如例子中的背景色,一个系统可能风格统一,只有几种背景色,不需要每次都手动设置。
3、耦合性太强,客户端必须知道具体的按钮创建过程,必须会创建按钮才行,后续按钮的方法改变,客户端也可要跟着修改,如以后按钮必须设置大小了,所有客户端代码都要变动。
4、重复对象没有控制,比如btn2的关于,可能每个页面都有,但是每个页面的这个按钮都是一模一样的,没必要每次都创建一遍。
5、没有封装变化,假如写了100个new RoundButton,后续这个按钮发生改变了,我们要改100处代码。
等等,如果你再仔细想想,各种各样的情况下都有各种各样的缺点(当然这么写也有优点的,至少简单嘛,如何设计没有最好,只有合适的),那么我们有没有其他方式来规避这些问题呢?其实我们需要一个对象时,除了自己new之外,还有就是从其他地方获取,我们完全可以把这些按钮的创建过程放到一起,客户端使用的时候直接获取就行了。比如下面代码:
public class RoundButtonFactory implements Creater {
public static IButton createButton(String text) {
// 圆形按钮
IButton btn = new RoundButton();
btn.setText(text);
btn.setBackgroundColor("#00aaff");
return btn;
}
}
工厂模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
客户端使用时,只需要调用createButton就行,屏蔽了底层的具体实现,后续实现类变化了,只需要修改这个方法就行了;也使客户端和具体的按钮实现类解耦开来,其实这就是最基本的工厂模式。
简单说就是将要使用的对象抽象出一个接口(产品),还有一个接口的创建工厂,每个具体实现类(产品的实现类)的创建由工厂的实现类创建。
2、结构与类图
工厂模式通用类图如下:
上面举的例子的类图如下:
工厂方法包含四个角色:
1、抽象产品(Product):负责定义产品公有属性;
2、具体产品(ConcreteProduct):具体的产品实现类;
3、抽象工厂(Creater):抽象的创建类,也就是抽象工厂;
4、具体工厂(ConcreteCreater):具体创建者,也就是具体工厂,负责具体产品实现类的创建。
3、适用场景及效果(优缺点)
没有工厂的时候,假如我们要做饭,需要用到火,创建火的同时发现需要用到木柴,还要创建一个锯来锯木柴......代码如下:
// 创建锯
Saw saw = new Saw ();
// 使用锯,锯木柴
FireWood fw = saw.cut();
// 使用木柴创建火
Fire fire = new WoodFire(fw);
可以看到,这样的话,做饭的逻辑就依赖了锯、木柴、火等东西,如果使用工厂呢
Fire fire = WoodFireFactroy.create();
1、具有良好的封装性,逻辑代码清晰,不用new了,不用初始化了,只需要简单的get或者create就行了。
2、耦合性低,工厂模式是典型的解耦框架,屏蔽了具体产品类(都是用产品接口嘛),调用者无需关心底层如何实现,产品类变化时只要接口不变就不影响调用者,使客户端和具体产品解耦,更能屏蔽创建具体产品时需要的其他关联类(如做饭就不需要依赖锯了),符合迪米特法则,只和需要的类交流,也符合依赖导致原则,只依赖抽象,更符合里氏替换原则,使用产品子类代替父类,完全没问题。
3、扩展非常方便,新加一类产品时(如上面例子新增一种按钮),只需要新增一个工厂实现类即可。无需修改原有代码,达到了“拥抱变化”,符合开闭原则。
当产品创建简单,比较固定的时候,或者调用者个性化情况太多时,工厂就体现不出他的优势了,比如常用的List,我们就没必要弄个ListFactory.createArrayList(),徒增代码复杂性。还有种类比较固定,不会太多,没必要抽象出接口,那也不必非用工厂模式。最好在下面的情况下才考虑使用工厂模式:
1、调用者不需要直到具体的产品创建过程时;
2、调用者使用的对象存在变动的可能,甚至完全不知道使用哪个具体对象时。
3、需要做出灵活、可扩展的功能时,再考虑工厂模式,其实不一定所有功能都要做到可扩展,谨慎过度设计。
4、需要解耦时,减少调用者和具体实现类的依赖时。
4、示例和扩展
1、退化成简单工厂模式,当要创建的产品种类较少时,并且可以预见时,可以把工厂实现类合并到一起,对外提供一个静态工厂方法,比如上面的按钮例子中:
public static IButton crateButton(String type, String text) {
IButton btn = null;
if ("round".equals(type)) {
btn = new RoundButton(); // 圆形按钮
} else if ("rec".equals(type)) {
btn = new RecButton(); // 矩形按钮
} else {
return null ;
}
btn.setText(text);
btn.setBackgroundColor("#00aaff");
return btn;
}
这种简单工厂模式用起来非常简单,缺点是扩展困难,不符合开闭原则,要注意设计没有最好,只有适不适合,在可预见的变化下,简单工厂模式非常好用。
2、约束产品类实例数量,通常和其他模式组合能达到很多效果,比如使用工厂创建好对象后缓存起来,达到单例或多例的目的,比如下面单例工厂:
//单例工厂
public class SingletonFactory {
private static Map<Class<?>, Object> objCache = new HashMap<Class<?>, Object>();
public synchronized static Object getInstance(Class<?> clazz) throws Exception {
Object singleton = objCache.get(clazz);
if (singleton == null) {
singleton = createInstance(clazz);
objCache.put(clazz, singleton);
}
return singleton;
}
private static Object createInstance(Class<?> clazz) throws Exception {
Constructor ct = clazz.getDeclaredConstructor();
ct.setAccessible( true);
return ct.newInstance();
}
}
3、多工厂协调,工厂模式中,一个工厂创建一个产品也行,创建多个产品也行,当产品种类过多时,如果工厂类也较多,此时最好弄一个协调类来协调,方便调用者使用,而不是让调用者逐个去找工厂类。
单例可以,多例也就没问题了,比如数据库连接池,设置最大100个,使用工厂模式就很有效,此时需要考虑每个实例的状态,使用中的话不能被获取等等。
4、延迟实例化,有的时候产品创建和销毁比较耗费资源,可以考虑创建好之后缓存起来,用完之后不销毁,或者使用完毕后将对象改成初始状态,而不是重新创建,方便后续使用,还是连接池的例子,如果用完了,是不销毁的,还会重新使用。
5、结合反射或配置文件,代替程序new。虽然例子中我们使用的是new创建对象,但是在现实编程中,大部分工厂都是配合反射来使用的,可以考虑将要创建的产品属性,设置工厂属性放到配置文件中,程序启动就将对象创建好,这样当增加一个简单产品时,可以做到修改配置文件即可,就算增加复杂产品,只需要新写一个工厂类,配置配置就行,而不用大量修改源码。
结束语,工厂方法其实在项目中使用非常非常频繁,这个模式几乎人尽皆知,但却不是每个人都能用好,工厂模式通常和其他模式混合使用,变化出无穷的优秀设计。