工厂方法模式
GOF关于工厂方法的概念
1.1意图
定义一个用于创建对象的接口,让子类来决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
1.2 别名
虚构造器(Virtual Constructor)
1.3 实用性
在同时满足下列情况下可以使用Factory Method模式:
- 当一个类不知道他所必须创建的类的对象的时候;
- 当一个类希望由它的子类来指定他所创建的对象的时候;
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。(后半句看不太明白,查了下原文,我个人理解的意思是“创建对象时,我们不必关心具体是由哪个子类来创建的”)
1.4 结构
1.5 参与者
- Product:定义工厂方法所创建的对象的接口。
- ConcreteProduct:实现Product接口。
- Creator:1. 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。 2. 可以调用工厂方法以创建一个Product对象。
- ConcreteCreator:重定义工厂方法以返回一个ConcreteProduct
代码示例
人是铁,饭是钢。就简单举个一日三餐的例子,结构图如下:
代码如下:
1: public interface Dinner
2: {
3: /**
4: * 准备
5: * @see [类、类#方法、类#成员]
6: */
7: public void comprepare();
8:
9: /**
10: * 享用
11: * @see [类、类#方法、类#成员]
12: */
13: public void enjoy();
14:
15: /**
16: * 收拾
17: * @see [类、类#方法、类#成员]
18: */
19: public void clear();
20: }
1: public class Breakfast implements Dinner
2: {
3: /**
4: * 面包
5: */
6: private String bread;
7:
8: /**
9: * 牛奶
10: */
11: private String milk;
12:
13: @Override
14: public void clear()
15: {
16: System.out.println("收拾早餐,还剩:" + this.milk + "和" + this.bread);
17: }
18:
19: @Override
20: public void comprepare()
21: {
22: this.milk = "一杯牛奶";
23: this.bread = "两个面包";
24: System.out.println("准备早餐:" + this.milk + "和" + this.bread);
25: }
26:
27: @Override
28: public void enjoy()
29: {
30: this.milk = "半杯牛奶";
31: this.bread = "一个面包";
32: System.out.println("享用早餐ing。。");
33: }
34:
35: }
1: public class Lunch implements Dinner
2: {
3: /**
4: * 肉
5: */
6: private String meat;
7:
8: /**
9: * 米饭
10: */
11: private String rice;
12:
13: @Override
14: public void clear()
15: {
16: System.out.println("收拾午餐,还剩:" + this.meat + "和" + this.rice);
17: }
18:
19: @Override
20: public void comprepare()
21: {
22: this.meat = "两盘牛肉";
23: this.rice = "两碗米饭";
24: System.out.println("准备午餐:" + this.meat + "和" + this.rice);
25: }
26:
27: @Override
28: public void enjoy()
29: {
30: this.meat = "半盘牛肉";
31: this.rice = "半碗米饭";
32: System.out.println("享用午餐ing。。");
33: }
34: }
1: public class Supper implements Dinner
2: {
3: /**
4: * 粥
5: */
6: private String congee;
7:
8: /**
9: * 炒青菜
10: */
11: private String greens;
12:
13: @Override
14: public void clear()
15: {
16: System.out.println("收拾晚餐,还剩:" + this.congee + "和" + this.greens);
17: }
18:
19: @Override
20: public void comprepare()
21: {
22: this.congee = "两碗粥";
23: this.greens = "一盘炒青菜";
24: System.out.println("准备晚餐:" + this.congee + "和" + this.greens);
25: }
26:
27: @Override
28: public void enjoy()
29: {
30: this.congee = "半碗粥";
31: this.greens = "半盘炒青菜";
32: System.out.println("享用晚餐ing。。");
33: }
34: }
1: public class DinnerFactory
2: {
3: /**
4: * 正餐工厂方法
5: * @return
6: * @see [类、类#方法、类#成员]
7: */
8: public Dinner dinnerFactory()
9: {
10: //缺省做午餐
11: return new Lunch();
12: }
13:
14: public void newDinner()
15: {
16: Dinner dinner = dinnerFactory();
17: dinner.comprepare();
18: dinner.enjoy();
19: dinner.clear();
20: }
21: }
1: public class BreakfastFactory extends DinnerFactory
2: {
3: @Override
4: public Dinner dinnerFactory()
5: {
6: return new Breakfast();
7: }
8: }
1: public class LunchFactory extends DinnerFactory
2: {
3: @Override
4: public Dinner dinnerFactory()
5: {
6: return new Lunch();
7: }
8:
9: }
1: public class SupperFactory extends DinnerFactory
2: {
3: @Override
4: public Dinner dinnerFactory()
5: {
6: return new Supper();
7: }
8:
9: }
1: public class Client
2: {
3:
4: /**
5: * 测试
6: * @param args
7: * @see [类、类#方法、类#成员]
8: */
9: public static void main(String[] args)
10: {
11: DinnerFactory dinnerFactory = new DinnerFactory();
12: DinnerFactory breakfastFactory = new BreakfastFactory();
13: DinnerFactory lunchFactory = new LunchFactory();
14: DinnerFactory supperFactory = new SupperFactory();
15: Dinner dinner;
16:
17: //测试缺省状况下
18: dinner = dinnerFactory.dinnerFactory();
19: dinner.comprepare();
20: dinner.enjoy();
21: dinner.clear();
22:
23: System.out.println("--------------------------------------");
24:
25: //测试早餐
26: dinner = breakfastFactory.dinnerFactory();
27: dinner.comprepare();
28: dinner.enjoy();
29: dinner.clear();
30:
31: System.out.println("--------------------------------------");
32:
33: //测试newDinner方法
34: breakfastFactory.newDinner();
35: System.out.println("--------------------------------------");
36: lunchFactory.newDinner();
37: System.out.println("--------------------------------------");
38: supperFactory.newDinner();
39: }
40: }
运行结果如下
1: 准备午餐:两盘牛肉和两碗米饭
2: 享用午餐ing。。
3: 收拾午餐,还剩:半盘牛肉和半碗米饭
4: --------------------------------------
5: 准备早餐:一杯牛奶和两个面包
6: 享用早餐ing。。
7: 收拾早餐,还剩:半杯牛奶和一个面包
8: --------------------------------------
9: 准备早餐:一杯牛奶和两个面包
10: 享用早餐ing。。
11: 收拾早餐,还剩:半杯牛奶和一个面包
12: 准备午餐:两盘牛肉和两碗米饭
13: 享用午餐ing。。
14: 收拾午餐,还剩:半盘牛肉和半碗米饭
15: 准备晚餐:两碗粥和一盘炒青菜
16: 享用晚餐ing。。
17: 收拾晚餐,还剩:半碗粥和半盘炒青菜
工厂方法模式的应用
优点:
1. 良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,减少模块间的耦合。
2. 工厂方法模式的扩展性非常优秀。假如我们需要多吃一顿,只要修改具体的工厂类或者新增一个工厂类即可。
3. 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不表,系统中的上层模块就不要发生变化,因为产品类的实例化工作是由工厂类负责,一个产品对象具体由哪一个产品生成是由工厂类决定的。
使用场景
首先,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。
其次,需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,三个具体的产品类(也就是连接方式)进行不同的实现,再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。
再次,工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java的项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等,从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
最后,可以使用在测试驱动开发的框架下,例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,该使用场景已经弱化了,读者可以在遇到此种情况时直接考虑使用JMock或 EasyMock。
引用自这里。
参数化工厂方法和简单工厂
参数化工厂方法顾名思义,传入一个参数,通过参数来让工厂知道需要返回什么样的具体产品。根据上面一日三餐的例子,具体改变如下:
试想,当一个模块只需要一个工厂的时候,显然没有必要使用抽象类或者借口来声明工厂方法,再通过继承使子类定义或者重定义工厂方法。只需要使用静态方法来直接定义工厂方法就可以了,具体类图如下:
代码如下:
1: public class DinnerFactory
2: {
3: /**
4: * 正餐工厂方法
5: * @return
6: * @see [类、类#方法、类#成员]
7: */
8: public static Dinner dinnerFactory(String type)
9: {
10: if("breakfast".equals(type))
11: {
12: return new Breakfast();
13: }
14: if("lunch".equals(type))
15: {
16: return new Lunch();
17: }
18: if("supper".equals(type))
19: {
20: return new Supper();
21: }
22: return null;
23: }
24: }
简单工厂是工厂方法模式的弱化,因为简单,所以被称为简单工厂模式(Simple Factory Pattern),也叫做静态工厂模式。在实际项目中,采用该方法的案例还是比较多的,其缺点是工厂类的扩展比较困难,不符合开闭原则,但它仍然是一个非常实用的设计模式。
Client类和newDinner方法
再来仔细研究下工厂方法模式的类图,会发现2个问题:
1. 在Creator类中除了工厂方法还有一个方法:AnOperation(),并做了注释,调用了工厂方法。
2. 没有发现其他模式类图中经常出现的Client类,也就是产品的使用者。
我没有准确的答案,在GOF关于Creator类的描述中,第二条是:可以调用工厂方法以创建一个Product对象。再看GOF关于Application和Document的例子,AnOperation方法其实是个模板方法,其中调用了工厂方法并且做了其他的一些行为。再结合GOF反复说明:“工厂方法通常在TemplateMethod中被调用”。
于是我这样理解:一个产品必须有对其使用者,也就是Client类。如果没有的话,那么我只能认为有其他的类达到了Client相同的作用。看一下工厂方法中的Creator类中的AnOperation方法,不正是产品Product的使用者吗?Factory Method突出的是对Procuct创建,所以在结构图中,没有出现额外的Client类,也不需要出现。
所以我模仿的在工厂类DinnerFactory中写了newDinner方法来封装整个吃饭的过程,至于Creator类为什么要在自己内部调用工厂方法,原因我依然不清楚,但是从最后的结果来看,把产品Product的行为都封装在Creator类里,代码结构上清晰,扩展性好,代码量减少。
欢迎大家提出建议,共同学习。