真刀实枪之工厂方法模式
- 从女娲造人开始说起
- 东汉《风俗通》记录了这样一则神话故事:“开天辟地,未有人民,女娲抟黄土做人。”开天辟地之初,大地上并没有生物,只有苍茫大地,纯粹而洁净的自然环境,寂静而又寂寞,于是女娲决定创造一个新物种,即人类来增加世界的繁荣。
- 别忘了女娲是神仙,没有办不到的事情,造人的过程是这样的:首先,女娲采集黄土捏成人的形状,然后放到八卦炉中烧制,最后放到大地上生长,工艺过程没有错,但是意外会随时发生:
- 第一次烤泥人,感觉应该烤熟了,往地上一放,哇,没烤熟!于是一个白人诞生了!
- 第二次烤泥人,上一次没烤熟,这次多烤了一会儿,放到世上一看,嘿,熟过头了,黑人诞生了!
- 第三次烤泥人,一边烧制,一边查看,直到表皮微黄,嘿,刚刚好,于是黄色人种出现了!
- 分析
- 造人中涉及的对象
- 女娲----场景
- 八卦炉----工厂
- 三种肤色不同的人----产品
- 类图
- 代码
//Human public interface Human{ //肤色 public void getColor(); //说话 public void talk(): }
//BlackHuman class BlackHuman implements Human{ public void getColor(){ System.out.println("黑色人种的皮肤颜色是黑色的!"); } public void talk(){ System.out.println("黑人会说话,一般人听不懂!"); } }
//YelloHuman class YelloHuman implements Human{ public void getColor(){ System.out.println("黄色人种的皮肤颜色是黄色的!"); } public void talk(){ System.out.println("黄人会说话,一般是双字节的!"); } }
//WhiteHuman class WhiteHuman implements Human{ public void getColor(){ System.out.println("白色人种的皮肤颜色是白色的!"); } public void talk(){ System.out.println("白人会说话,一般是单字节的!"); } }
- 所有人种定义完毕,下一步就是定义八卦炉,女娲给八卦炉下达什么生产命令呢? 应该是“给我生产一个黑色人种”,而不是“给我生产一个会跑会走,会说,皮肤是黑色的人种。”,因为这样的命令增加了交流的成本,作为一个生产的管理者,只要知道生产什么就可以了,而不需要具体信息。这就解释createHuman方法的参数为什么是Class了。
public abstract class AbstractHumanFactory{ public abstract <T extends Human> T createHuman(Class<T> c); } //注意:这里的泛型表示为两层含义 //1. 必须是Class类型 //2. 必须是Human的实现类
- 八卦炉
public class HumanFactory extends AbstracterHumanFactory{ public <T extends Human> T createHuman(Class<T> c){ //定义一个生产的人种 Human human = null; try{ //生产一个人种 huam=(T)Class.forName(c.getName()).newInstance(); }catch(Exception e){ System.out.println("人种生产错误!"); } return (T)human; } }
- 人种有了,八卦炉也有了,剩下的工作就是女娲采集黄土,然后命令八卦炉进行生产了。下面是女娲类
public class NvWa{ public static void main(Stringa[] args){ //声明阴阳八卦炉 AbstracterHumanFactory YinYangLu=new HumanFactory(); //造白人 Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class); whiteHuamn.getColor(); whiteHuman.talk(); //造黑人 Human blackHuman = YinYangLu.createHuman(BlackHuman.class); blackHuman.getColor(); blackHuman.talk(); //造黄人 Human yellowHuman = YinYangLu.createHuman(YellowHuman.class); yellowHuman.getColor(); yellowHuman.talk(); } }
- 造人中涉及的对象
- 工厂方法的定义
- Define an interface for creating an object,but let subclass decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类)
-
工厂方法的通用类图
-
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事务最抽象的定义,Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的,工厂方法模式的变种较多。
-
通用源码
//抽象产品类 public abstract class Product{ //产品类的公共方法 public void method1(){ //业务逻辑处理 } //抽象方法 public abstract void method2(); }
//具体产品类 public class ConcreteProduct1 extends Product{ public void method2(){ //业务逻辑处理 } } public class ConcreteProduct2 extends Product{ public void method2(){ //业务逻辑处理 } } ...
//抽象工厂类 public abstract class Creator{ //创建一个产品对象 public abstract <T extends Product> T createProduct(Class<T> c); }
//具体工厂类 public class ConcreteCreator extends Creator{ public <T extends Product> T createProduct(Class<T> c){ Product product=null; try{ product = (Product)Class.forName(c.getName).newInstance(); }catch(Exception e){ //异常处理 } return product; } }
//场景类 public class Client { public class void main(String[] args){ Creator creator=new ConcreteCreator(); Product product=creator.createProduct(ConcreteProduct1.class); //继续业务处理 } }
-
- 工厂方法模式的应用
- 优点
1. 良好的封装性,代码结构清晰 2. 扩展性优秀 3. 屏蔽产品类 4. 解耦框架
- 使用场景
1. 所有在需要生成对象的地方 2. 灵活的,可扩展的框架(POP3,IMAP,HTTP协议) 3. 异构项目中--WebService与一个非java的项目交互 4. 测试驱动开发的框架--JMock和EasyMock--测试A类,就需要把相关联的B类也创建出来,我们可以使用工厂方法把B类虚拟出来,比避免A类与B类的耦合
- 优点
-
工厂方法的扩展
-
1.缩小为简单工厂模式
public class HumanFactory { public static <T extends Human> T createHuman(Class<T> c){ //定义一个生产的人种 Human human = null; try{ //生产一个人种 huam=(T)Class.forName(c.getName()).newInstance(); }catch(Exception e){ System.out.println("人种生产错误!"); } return (T)human; } }
-
2.升级为多个工厂类
- 问题:当项目复杂时,初始化对象是一件十分耗精力的事,而且所有的产品都放到一个工厂类中区创建会使代码结构不清晰。
- 解决:每个产品对应一个创造者
-
3.代替单例模式
public class Singleton{ private Singleton(){ } public void doSomething(){ } }
public class SingletonFactory{ private static Singleton singleton; static{ try{ Class cl=Class.forName(Singleton.class.getName()); //获得无参构造 Constructor constructor = cl.getDeclaredConstructor(); //设置无参构造是可访问的 constructor.setAccessible(true); singleton=(Singleton)constructor.newInstance(); }catch(Exception e){ //异常处理 } public static Singleton getSingleton(){ return singleton; } } }
-
4.延迟初始化
- 一个对象被消费完毕后,并不立刻释放,工厂类保存其初始状态,等待再次被使用
-
通用类图
-
延迟加载框架可以限制其实例的个数;可以通过延迟记载降低对象的产生和销毁带来的复杂性。
-
代码
public class ProductFactory { private static final Map<String,Product> peMap=new HashMap(); public static synchronized Product createProduct (String type) throws Exception{ Product product=null; //如果Map中已经有这个对象 if(prMap.containsKey(type)){ product=prMap.get(type); }else{ if(type.equals("Product1")){ product=new ConvreteProduct1(); }else{ product=new ConvreteProduct2(); } //同时把这个对象放入Map prMap.put(type,product); } return product; } }
-
最佳实践
- 工厂方法模式可以与其他模式混合使用(模板方法模式,单例模式,原型模式等),变化出无穷的优秀设计,这也正是软件设计和开发的乐趣所在。
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正。