工厂模式也是非常经典的设计模式之一。工厂模式的主要目的是封装创建具体对象的逻辑,只向外提供创建对象的接口方法。此外,也有地方指出加入工厂模式还为了满足项目的扩展性。
工厂模式网上已经有了很多例子,但是存在下列两个问题:
1. 将简单工厂模式(有的地方也成为“工厂方法”)也列入工厂模式;
2. 不合时宜地使用抽象工厂模式;
本文将以简单例子说明简单工厂模式、工厂模式、抽象工厂模式。
简单工厂模式
简单工厂模式封装了对象实例化,客户端不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。例如,戴尔鼠标、联想鼠标都是鼠标,我们抽象出两种鼠标共同的getBrand()函数至IMouse接口中。
public interface IMouse {
String getBrand();
}
然后,戴尔鼠标类、联想鼠标类相当于实现了IMouse的getBrand()的抽象方法:
public class DellMouse implements IMouse {
@Override
public String getBrand() {
return "Dell";
}
}
public class LenovoMouse implements IMouse {
@Override
public String getBrand() {
return "Lenovo";
}
}
那么,可以定义个工厂类SimpleMouseFactory,利用Java的多态特性封装LenovoMouse、DellMouse的对象实例化过程:
public class SimpleMouseFactory {
public IMouse createMouse(String b){
switch (b){
case "Dell":
return new DellMouse();
case "Lenovo":
return new LenovoMouse();
default:
return null;
}
}
}
其实,通过String入参来枚举判断具体生成哪个对象实例是不漂亮的,因为用户往往不知道Brand是啥。而且就String来说还要判断是否大小写、用户输入错误,比较费力不讨好。参阅一些框架的源代码,其中的入参一般都是int枚举,或者enum枚举。
例如,int枚举:
public class SimpleMouseFactory {
public static final int DELL_MOUSE = 0;
public static final int LENOVO_MOUSE = 1;
public IMouse createMouse(int b){
switch (b){
case DELL_MOUSE:
return new DellMouse();
case LENOVO_MOUSE:
return new LenovoMouse();
default:
return null;
}
}
public static void main(String[] args){
SimpleMouseFactory factory = new SimpleMouseFactory();
IMouse dellMouse = factory.createMouse(DELL_MOUSE);
//avoid using factory.createMouse(0);
IMouse lenovoMouse = factory.createMouse(LENOVO_MOUSE);
}
}
再比如使用enum类型:
public class SimpleMouseFactory {
enum Brand{Dell, Lenovo};
public IMouse createMouse(Brand b){
switch (b){
case Dell:
return new DellMouse();
case Lenovo:
return new LenovoMouse();
default:
return null;
}
}
}
简单工厂模式向外界封装了new Object()细节,工程量变大后方便维护管理。
但是,简单工厂模式缺点也很明显。最致命的一点,就是它违背了开-闭原则。开闭原则的最简单表达就是“只允许扩展,不允许修改”,即一个实体在不改变它的源代码的前提下变更它的行为。
试想,如果IMouse接口有新的产品——惠普鼠标。简单工厂模式(SimpleFactory)除了新建HPMouse类实现IMouse接口外,还要打开SimpleFactory,修改createMouse()函数,添加新的返回对象HPMouse。那么,随着项目扩展,需要增加/删除对象类型愈来愈多,SimpleFactory类打开修改的次数也会增多。这不仅使简单工厂愈来愈臃肿,耦合性大大增加,同时也违背了面向对象程序设计原则。
所以,简单工厂模式不是一种"真正"的工厂模式。
工厂模式
工厂模式就是为了解决简单工厂模式存在问题而生的。原来简单工厂耦合性很大,是因为一个工厂统管了所有产品类型的生产。如果创建很多工厂,每个工厂类专注于生产一个产品,就能降低不少耦合,此外也遵循了开-闭原则规范。
按这么做,首先我们抽象这些工厂类成一个IFactory接口:
public interface IMouseFactory {
IMouse createMouse();
}
然后,就有实现IFactory接口的DellFactory专门生产DellMouse,LenovoFactory专门生产LenovoMouse:
public class DellFactory implements IMouseFactory {
@Override
public IMouse createMouse() {
return new DellMouse();
}
}
public class LenovoFactory implements IMouseFactory {
@Override
public IMouse createMouse() {
return new LenovoMouse();
}
}
可以看到,一个工厂类专门负责生产一个IMouse对象。这样加入新产品HPMouse,不用更改IFactory,只用构建HPMouse类和配套工厂类HPFactory即可。
然而,这样的工厂模式也有弊端:
1. 每增加一个产品类,就要对应增加一个对应的工厂类,增加额外代码量;
2. 只适用于单产品族的生产管理;
抽象工厂模式
我们把之前IMouse称为”产品族“,对应IMouseFactory抽象工厂来生产。试想,假设加入新产品族IKeyBoard:
public interface IKeyBoard {
int getKeys();
}
public class DellKeyboard implements IKeyBoard {
@Override
public int getKeys() {
return 87;
}
}
public class LenovoKeyboard implements IKeyBoard {
@Override
public int getKeys() {
return 104;
}
}
那就势必除了IMouseFactory外还要有新的IKeyBoardFactory抽象工厂诞生:
public interface IKeyBoardFactory {
IKeyBoard createKeyBoard();
}
但是我们看到,DellKeyBoard、LenovoKeyBoard仍然可以一一对应DellFactory、LenovoFactory生产。因此,可以聚合IKeyBoardFactory和IMouseFactory为一个超级工厂IComputerFactory生产:
public interface IComputerFactory {
IMouse createMouse();
IKeyBoard createKeyBoard();
}
那么自然,DellFactory、LenovoFactory的职责发生了扩展:
public class DellFactory implements IComputerFactory {
@Override
public IMouse createMouse() {
return new DellMouse();
}
@Override
public IKeyBoard createKeyBoard() {
return new DellKeyboard();
}
}
public class LenovoFactory implements IComputerFactory {
@Override
public IMouse createMouse() {
return new LenovoMouse();
}
@Override
public IKeyBoard createKeyBoard() {
return new LenovoKeyboard();
}
}
也就是说,抽象工厂模式整合了多个抽象工厂为一个“超级工厂”,是为了解决多个产品族加入造成抽象工厂过多的问题(它不能解决同一产品族中多个产品类的多个工厂弊病)。
抽象工厂模式是在工厂模式基础上,加入了简单工厂的优点。但是,这样带来同样一个问题,产品族的扩展性。如果产品族增加,例如多了Screen类,IComputerFactory类扩展需要多加一个createScreen(),同样要打开IComputerFactory这个工厂,也同样容易违背开闭原则。
思辩
抽象工厂模式还有一个值得注意的地方,就是抽象聚合必须是多个产品族之间有相互关联。否则,DellFactory可以生产LenovoKeyBoard,也可以生产LenovoMouse,就会造成2*2=4种排列组合:
1. DellFactory生产DellMouse,DellKeyBoard;LenovoFactory生产LenovoMouse,LenovoKeyBoard。
2. DellFactory生产DellMouse,LenovoKeyBoard;LenovoFactory生产LenovoMouse,DellKeyBoard。
3. DellFactory生产LenovoMouse,DellKeyBoard;LenovoFactory生产DellMouse,LenovoKeyBoard。
4. DellFactory生产LenovoMouse,LenovoKeyBoard;LenovoFactory生产DellMouse,DellKeyBoard。
试想,这样就需要构建四个工厂类来实现IComputerFactory,这是个噩梦!于是,对于多个产品族之间完全独立的例子,网上流行了这种写法:
public interface IComputerFactory {
enum Brand{Dell, Lenovo};
IMouse createMouse(Brand b);
IKeyBoard createKeyBoard(Brand b);
}
public class MouseFactory implements IComputerFactory{
@Override
public IMouse createMouse(Brand b) {
switch (b){
case Dell:
return new DellMouse();
case Lenovo:
return new LenovoMouse();
default:
return null;
}
}
@Override
public IKeyBoard createKeyBoard(Brand b) {
return null;
}
}
public class KeyBoardFactory implements IComputerFactory{
@Override
public IMouse createMouse(Brand b) {
return null;
}
@Override
public IKeyBoard createKeyBoard(Brand b) {
switch (b){
case Dell:
return new DellKeyboard();
case Lenovo:
return new LenovoKeyboard();
default:
return null;
}
}
}
这种写法有两大弊病:
1. IComputerFactory超级工厂的createMouse(Brand b)和createKeyBoard(Brand b),并不是两个子Factory(MouseF actory和KeyBoardFactory)的共同特性,所以这是一个不成功的抽象;
2. IComputerFactory超级工厂本身完成了子工厂的整合,这里因为抽象不成功间接又造成了拆分;
所以抽象工厂模式并非都适用多产品族的情况。这时候多个抽象工厂IMouseFactory、IKeyBoardFactory...反而层次更加清晰。
这也是为何抽象工厂模式多常用于对数据库中的表项进行修改(关联性强)。