你的位置:首页 > ASP.net教程

[ASP.net教程]设计模式(四):工厂方法模式(解析设计原则)


一、概述

  工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

二、解决问题

   通常我们需要一个对象的时候,会想到使用new来创建对象

      Tea tea = new MilkTea(); //使用了接口,代码更有弹性,体现设计原则“对接口编程,而不是对实现编程”

  当我们需要多个对象的时候,”对接口编程“的原则似乎还能派上用场   

 

Tea tea;if("milk".equals(type)){	tea = new MilkTea();}else if("coffee".equals(type)){	tea = new CoffeeTea();}else if("lemon".equals(type)){	tea = new LemonTea();}

   这里也体现了“对接口编程”的好处,运行时决定要实例化哪个对象,让系统具备了弹性,但对于扩展性方面,我们就不敢恭维了。看以上代码,当我们要新增对象或者要扩展时,不得不打开这份代码进行检查和修改。通常这样的修改过的代码会造成部分系统更难维护和更新,而且也容易犯错。

  为了有良好的扩展性,我们想到了另外一个设计原则把变化的代码从不变化的代码中分离出来

  假设我们要开一家烧饼店,我们每天会做各种口味的烧饼出售,做烧饼的程序包括准备原材料、和面、烘烤、切片、装盒。

  

//烧饼店public class ShaobingStore {    public Shaobing orderShaobing(String type){        Shaobing shaobing = null;        if("onion".equals(type)){             //洋葱烧饼             shaobing = new OnionShaobing();        }else if("sour".equals(type)){             //酸菜烧饼             shaobing = new SourShaobing();        }else if("beef".equals(type)){             //牛肉烧饼             shaobing = new BeefShaobing();        }        //以上代码会发生改变,当洋葱烧饼不再出售时,我们会把创建洋葱的代码删除,我们可能会新增新口味的烧饼          else if("pork".equals(type)){             //牛肉烧饼             shaobing = new PorkShaobing();        }            //对于制作烧饼的程序中,以下这些步骤是不变的        if(shaobing != null){            shaobing.prepare();            shaobing.cut();            shaobing.bake();             shaobing.box();        }        return shaobing;    }           }

  对于上面代码,我们使用”分离变化“的原则,把创建烧饼代码封装到一个类中,我们把它叫做工厂类,里面专门一个方法用来创建烧饼,如下所示:

package factorymethod.pattern;public class SimpleShaobingFactory {	public Shaobing createShaobing(String type){		Shaobing shaobing = null;		if("onion".equals(type)){			//洋葱烧饼			shaobing = new OnionShaobing();		}else if("sour".equals(type)){			//酸菜烧饼			shaobing = new SourShaobing();		}else if("beef".equals(type)){			//牛肉烧饼			shaobing = new BeefShaobing();		}		return shaobing;	}}

  改进后的烧饼店如下:

package factorymethod.pattern;//烧饼店public class ShaobingStore { public Shaobing orderShaobing(String type){	 Shaobing shaobing = null;	 	 shaobing = factory.createShaobing(type);	 //对于制作烧饼的程序中,以下这些步骤是不变的	 if(shaobing != null){  	 		 shaobing.prepare();		 shaobing.cut();		 shaobing.bake();		 shaobing.box();	  }	   	  return shaobing; }  SimpleShaobingFactory factory; public ShaobingStore(SimpleShaobingFactory factory){   this.factory = factory; }}

如上图所示,不管以后烧饼的口味怎么变,烧饼店的代码都不用变了,要扩展或者修改烧饼,我们只要更改创建烧饼的工厂类,也就是SimpleShaobingFactory 类,这就解开了烧饼店和烧饼的耦合,体现了对扩展开放,对修改关闭的设计原则。

  其实上面的改进方案使用了一个没有被真正冠名的设计模式简单工厂模式,其类图如下所示:

 

 从上面的类图来看,如果我们的烧饼店开在不同的地方,不同地方对洋葱烧饼,酸菜烧饼要求的口味不一样,北方人喜欢放辣椒,南方人喜欢清淡的,我们的烧饼店该怎么开呢?这就是工厂方法模式要帮我们解决的问题,工厂方法模式让类把实例化推迟到子类,让子类决定实例化的类是哪一个,将产品的“实现”从“使用”中解耦出来,让系统同时具备了弹性和扩展性。简单工厂不够弹性,不能改变正在创建的产品(同一种类型的只有一个,拿洋葱烧饼来说,全国各地的口味一样,没有辣与不辣的区分了)

三、结构类图

     

四、成员角色

   抽象创建者(Creator):定义了创建对象模板,实现了所有操纵产品的方法,除了工厂方法。具体创建者必须继承该类,实现工厂方法。

  具体创建者(ConcreteCreator):继承抽象创建者,实现工厂方法,负责创建产品对象。

  抽象产品(Product):定义了产品的共用资源,提供给子类继承使用,某些方法可以做成抽象方法,强制子类实现。

  具体产品(ConcreteProduct):继承自抽象产品,实现父类的抽象方法,也可以覆盖父类的方法,从而产生各种各类的产品。

五、应用实例  

  下面还是以开烧饼店为例,介绍如何在广州和长沙开烧饼店,卖适合当地风味的烧饼,而且烧饼的种类和名称一样。

  首先抽象烧饼店,也就是Creator

package factorymethod.pattern;public abstract class ShaobingStore {	public Shaobing orderShaobing(String type){		Shaobing shaobing = createShaobing(type);				shaobing.prepare();		shaobing.cut();		shaobing.bake();		shaobing.box();				return shaobing;	}		//未实现的工厂方法	public abstract Shaobing createShaobing(String type);		}

  第二步,创建抽象烧饼,也就是Product

package factorymethod.pattern;public abstract class Shaobing {	//烧饼名称	public String name;	//烧饼用的配料	public String sauce;	//面团	public String dough;		public void prepare(){		System.out.println("Prepareing " + name);		//和面		System.out.println("Kneading dough...");		//加配料		System.out.println("加配料:" + sauce);	}		//烤烧饼	public void bake(){		System.out.println("Bake for 25 minutes at 350C");	}		//切面团	public void cut(){		System.out.println("Cutting the dough into fit slices");	}		//打包	public void box(){		System.out.println("Place shaobing into official box");	}}

  第三步、创建广州风味的烧饼(加番茄酱的洋葱烧饼和牛肉烧饼),对应ConcreteProduct

package factorymethod.pattern;public class GZOnionShaobing extends Shaobing{	public GZOnionShaobing(){		name = "广州的洋葱烧饼";		//配料		sauce = "番茄酱";	}}

  

package factorymethod.pattern;public class GZBeefShaobing extends Shaobing{	public GZBeefShaobing(){		name = "广州的牛肉烧饼";		//配料		sauce = "番茄酱";	}}

  第四步、创建长沙风味的烧饼(加辣椒酱的洋葱烧饼和牛肉烧饼),对应ConcreteProduct

package factorymethod.pattern;public class CSOnionShaobing extends Shaobing{	public CSOnionShaobing(){		name = "长沙洋葱烧饼";		//配料		sauce = "辣椒酱";	}}

  

package factorymethod.pattern;public class CSBeefShaobing extends Shaobing{	public CSBeefShaobing(){		name = "长沙牛肉烧饼";		//配料		sauce = "辣椒酱 ";	}}

  第五步、创建广州烧饼店,对应ConcreteCreator

package factorymethod.pattern;//广州烧饼店public class GZShaobingStore extends ShaobingStore{	@Override	public Shaobing createShaobing(String type) {		Shaobing shaobing = null;		if("onion".equals(type)){			shaobing = new GZOnionShaobing();		}else if("beef".equals(type)){			shaobing = new GZBeefShaobing();		}				return shaobing;	}}

  第六步、创建长沙烧饼店,对应ConcreteCreator

package factorymethod.pattern;//长沙烧饼店public class CSShaobingStore extends ShaobingStore{	@Override	public Shaobing createShaobing(String type) {		Shaobing shaobing = null;		if("onion".equals(type)){			shaobing = new CSOnionShaobing();		}else if("beef".equals(type)){			shaobing = new CSBeefShaobing();		}				return shaobing;	}}

  第七步、测试售出名字相同但风味不一样的烧饼

package factorymethod.pattern;public class TestShaobingStore {	public static void main(String[] args){		//在广州开一个烧饼店		ShaobingStore gzStore = new GZShaobingStore();		//售出一个洋葱烧饼		gzStore.orderShaobing("onion");		System.out.println("----------------------");		//在长沙开一个烧饼店		ShaobingStore csStore = new CSShaobingStore();		//售出一个洋葱烧饼		csStore.orderShaobing("onion");	}}

  运行结果:

六、工厂方法特有的设计原则

  如果我们之间在烧饼店中直接实例化一个烧饼,这种设计师依赖具体类的,类图如下:

  

这种依赖具体类设计,扩展性、弹性、维护性都比较差。如果将实例化的代码独立出来,使用工厂方法,我们将不再依赖具体类了,请看如下类图:

这就是我们要讲的依赖倒置原则:要依赖抽象,不要依赖具体类。用依赖倒置原则设计的系统,使得对象的实现从使用中解耦,对象的使用是在Creator,实现却在ConcreteCreator中,Creator只有Product的引用,Creator与ConcreteProduct松耦合,这种设计很强的扩展性、弹性和可维护性。

  设计中使用以来倒置原则方法:

  1、变量不可以持有具体类的引用(就是不能使用new,使用工厂方法)

  2、不要让类派生自具体类(继承抽象或者实现接口)

  3、不要覆盖基类中已实现的方法

 

七、优点和缺点

  1、优点

  (1)、符合“开闭”原则,具有很强的的扩展性、弹性和可维护性。扩展时只要添加一个ConcreteCreator,而无须修改原有的ConcreteCreator,因此维护性也好。解决了简单工厂对修改开放的问题。

  (2)、使用了依赖倒置原则,依赖抽象而不是具体,使用和实现松耦合。

  (3)、客户只需要知道所需产品的具体工厂,而无须知道具体工厂的创建产品的过程,甚至不需要知道具体产品的类名。

  2、缺点

  (1)、一个具体产品对应一个类,当具体产品过多时会使系统类的数目过多,增加系统复杂度。

  (1)、每增加一个产品时,都需要一个具体类和一个具体创建者,使得类的个数成倍增加,导致系统类数目过多,复杂性增加。

  (2)、对简单工厂,增加功能修改的是工厂类;对工厂方法,增加功能修改的是客户端。

八、使用场合

  1、当需要一个对象时,我们不需要知道该对象所对应的具体类,只要知道哪个具体工厂可以生成该对象,实例化这个具体工厂即可创建该对象。

  2、类的数目不固定,随时有新的子类增加进来。

  3、定义一个创建对象接口,由子类决定要实例化的类是哪一个;客户端可以动态地指定工厂子类创建具体产品。