首页 > 技术文章 > 工厂模式

zhangxiangguo 原文

工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

     工厂模式在《Java与设计模式》中分为3类:简单工厂模式工厂方法模式抽象工厂模式。GoF(GoF,“四人帮”,又称Gang of Four,即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)在《设计模式中》将工厂模式分为两类:工厂方法模式和抽象工厂模式。将简单工厂模式视为工厂方法模式的一种特例,两者归为一类。下面我们以工厂模式在《Java与设计模式》中的分类为例进行讲解。


一、简单工厂模式

   简单工厂模式又称静态工厂方法模式,它属于创建型模式。在简单工厂模式中,可以根据自变量的不同返回不同类的实例。简单工厂模式专门定义一个类负责创建其他类的实例,被创建的实例通常都具有共同的父类。

    简单工厂模式组成如下:

  • 工厂类角色:这角色是工厂方法模式的核心,含有与应用紧密相关的商业逻辑。工厂类在客户端的直接调用下创建产品对象,它由一个具体java类实现。
  • 抽象产品角色:这是工厂方法模式的所创建的对象的父类,或者它们共同拥有的接口。抽象产品角色可以用一个java类接口或者java抽象类实现。
  • 具体产品角色:工厂方法模式所创建的任何对象都是这个角色的实例,具体产品角色由一个具体java类实现。

   

那么简单工厂模式怎么用呢?我来举个例子吧,我想这个比讲一大段理论上的文字描述要容易理解的多!下面就来给那个暴发户治病: P 
在使用了简单工厂模式后,现在暴发户只需要坐在车里对司机说句:"开车"就可以了。来看看怎么实现的:
//抽象产品角色
public interface Car{ 
public void drive(); 
}
 
//具体产品角色
public class Benz implements Car{ 
public void drive() { 
System.out.println("Driving Benz "); 
} 
}
 
public class Bmw implements Car{ 
public void drive() { 
System.out.println("Driving Bmw "); 
} 
} 
。。。(奥迪我就不写了:P)
 
//工厂类角色
public class Driver{
 
//工厂方法
//注意 返回类型为抽象产品角色
public static Car driverCar(String s)throws Exception {
 
//判断逻辑,返回具体的产品角色给Client 
if(s.equalsIgnoreCase("Benz")) return new Benz(); 
else if(s.equalsIgnoreCase("Bmw")) 
return new Bmw();
 
...... 
else throw new Exception(); 
。。。

   

如果将所有的类放在一个文件中,请不要忘记只能有一个类被声明为public。 程序中类之间的关系如下:

clip_image004

这便是简单工厂模式了。下面是其好处:

首先,使用了简单工厂模式后,我们的程序不在"有病",更加符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责"消费"产品(正如暴发户所为)。
下面我们从开闭原则上来分析下简单工厂模式。当暴发户增加了一辆车的时候,只要符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了。那么对于产品部分来说,它是符合开闭原则的--对扩展开放、对修改关闭;但是工厂部分好像不太理想,因为每增加一辆车,都要在工厂类中增加相应的商业逻辑和判断逻辑,这显自然是违背开闭原则的。
对于这样的工厂类(在我们的例子中是为司机师傅),我们称它为全能类或者上帝类。
我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝类坏了,进而累坏了我们可爱的程序员:( 
正如我前面提到的简单工厂模式适用于业务将简单的情况下。而对于复杂的业务环境可能不太适应阿。这就应该由工厂方法模式来出场了!!

   简单工厂模式适用于如下环境

  • 工厂类负责创建的对象比较少
  • 客户端只知道传入工厂类的参数,对于如何创建对象则不关心

 


二、工厂方法模式

         工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式中集中工厂方法上的压力可以由工厂方法中不同的工厂子类来分类。它的组成如下:

  • 抽象工厂角色:这是工厂方法的核心,它与应用程序无关,是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象或者接口实现。
  • 具体工厂角色:含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
  • 抽象产品角色:是具体产品继承的父类或者实现的接口。在java中一般由抽象类或者接口实现。
  • 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体类实现。

来用类图来清晰的表示下的它们之间的关系:    

在以下情况可以使用工厂方法模式: 

  • 一个类不知道它所需要的对象的类在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道具体创建具体产品的工厂类。
  • 一个类通过其子类指定创建那个对象在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类确定具体要创建的对象。在运行程序时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

      将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名储存在配置文件或数据库中。

我们还是老规矩使用一个完整的例子来看看工厂模式各个角色之间是如何来协调的。话说暴发户生意越做越大,自己的爱车也越来越多。这可苦了那位司机师傅了,什么车它都要记得,维护,都要经过他来使用!于是暴发户同情他说:看你跟我这么多年的份上,以后你不用这么辛苦了,我给你分配几个人手,你只管管好他们就行了!于是,工厂方法模式的管理出现了。代码如下:
//抽象产品角色,具体产品角色与简单工厂模式类似,只是变得复杂了些,这里略。

//抽象工厂角色
public interface Driver{ 
public Car driverCar(); 
} 
public class BenzDriver implements Driver{ 
public Car driverCar(){ 
return new Benz(); 
} 
} 
public class BmwDriver implements Driver{ 
public Car driverCar() { 
return new Bmw(); 
} 
} 
......//应该和具体产品形成对应关系,这里略... 
//有请暴发户先生
public class Magnate 
{ 
public static void main(String[] args) 
{ 
try{ 
Driver driver = new BenzDriver();
 
Car car = driver.driverCar(); 
car.drive(); 
}catch(Exception e) 
{ } 
} 
} 
 

工厂方法使用一个抽象工厂角色作为核心来代替在简单工厂模式中使用具体类作为核心。让我们来看看工厂方法模式给我们带来了什么?使用开闭原则来分析下工厂方法模式。当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。看来,工厂方法模式是完全符合开闭原则的!
使用工厂方法模式足以应付我们可能遇到的大部分业务需求。但是当产品种类非常多时,就会出现大量的与之对应的工厂类,这不应该是我们所希望的。所以我建议在这种情况下使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。
当然特殊的情况,就要特殊对待了:对于系统中存在不同的产品树,而且产品树上存在产品族,那么这种情况下就可能可以使用抽象工厂模式了。


三、抽象工厂模式

  抽象工厂模式和工厂方法模式的区别在于需要创建对象的复杂程度。而且抽象工厂模式是三个模式中最为抽象、最具一般性的模式。

   抽象工厂模式的用意是为客户端提供一个接口,可以创建多个产品族(指位于不同产品等级结构中功能相关联的产品组成的家族)中的对象产品对象。

   抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂模式都实现了抽象工厂中定义的那些公共接口,因此只需要改变具体实例,就可以在某种程度上改变整个软件系统的行为。

   当一个产品族中的多个对象被设计成一起工作时,抽象工厂模式能够保证客户端始终只使用同一个产品族中的对象。

   抽象工厂适用于以下环境:

  1. 一个系统不应该依赖于产品类实例如何被创建、组合和表达的细节,这时于所有类型的工厂模式都是重要的。
  2. 系统中有多于一个的产品族,而每次只使用其中某一产品族。
  3. 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
  4. 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

推荐阅读