工厂方法模式
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类里,代码结构上清晰,扩展性好,代码量减少。
欢迎大家提出建议,共同学习。
 
                    
                



 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号