疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java设计模式 - 工厂模式详解


 

   

  每次写博客都不知道要怎么引入要写的主题,挺头疼的一件事。今天就直接开门见山,今天要学的就是工厂模式,工厂就是批量生产制造东西的地方。在这里,工厂就是批量生产对象的地方。

  学习书上的例子

  假如你现在有一个披萨店PizzaStore,店里有各种各样的pizza,现在你需要根据订单生产pizza,可能会这样做

  public Pizza orderPizza(String type) {

  Pizza pizza = null;

  if (type.equals("cheese"))

  pizza = new CheesePizza();

  else

  pizza = new GreekPizza();

  }

  将具体需要哪种pizza直接放在orderPizza方法中,那需要增加新的pizza呢……好吧,这里先跳过,先看一下如果使用简单工厂要怎么做。(书上说简单工厂不算一个“真正的”模式,更像一种编程习惯,好吧,这个我们就不纠结了)需要一个专门生产pizza的类SimplePizzaFactory,把生产pizza的任务就交给它了,就像这样:

  /**

  * 专门生产pizza的工厂

  */

  public class SimplePizzaFactory {

  public static Pizza createPizza(String type) {

  Pizza pizza = null;

  if (type.equals("cheese"))

  pizza = new CheesePizza();

  else

  pizza = new GreekPizza();

  return pizza;

  }

  }

  好了,得到了想要的pizza,现在接着完成pizza的订单,还需要这样的几个步骤:准备prepare(), 烘烤bake(), 切片cut(), 装盒box(). 这几个方法对所有pizza都是一样的, 所以需要一个pizza的抽象类,这几个方法可以直接在这个抽象类中实现

  public abstract class Pizza {

  public void prepare() {

  System.out.println("prepare");

  description();

  }

  public void bake() {

  System.out.println("bake");

  }

  public void cut() {

  System.out.println("cut");

  }

  public void box() {

  System.out.println("box");

  }

  public abstract void description();//描述pizza

  }

  具体的pizza(如cheesePizza)继承这个Pizza这个类,在description方法中添加自己的描述

  public class CheesePizza extends Pizza {

  @Override

  public void description() {

  System.out.println("default NY Cheese Pizza");

  }

  }

  其他的我就不写了,现在可以用工厂生产pizza了,这时候orderPizza方法大概就是这样的

  public class PizzaStore {

  public Pizza orderPizza(String type) {

  Pizza pizza = SimplePizzaFactory.createPizza(type);

  pizza.prepare();

  pizza.bake();

  pizza.cut();

  pizza.box();

  return pizza;

  }

  }

  你可能会问,这不就是把问题从一个地方搬到了另一个地方吗,问题依然存在啊。是的,我开始也是这样想的,但是仔细想想,这样还是有好处的,如果说其他的类中也需要生产pizza呢?难道你还要在把生产pizza的代码写一遍?用书上表达意思就是简单工厂SimplePizzaFactory的客户可能还有其他的,就是说在其他的类中也需要生产pizza。所以说需要依据具体情况创建对象时简单工厂是一个好的选择。

  书上把工厂模式分成了3类(我这里把简单工厂也当作一类),分别是简单工厂,工厂方法和抽象工厂,简单工厂上面已经介绍了,然后看一下工厂方法和抽象工厂

  工厂方法

  现在pizza店的生意比较好,准备开加盟店了,在纽约和芝加哥等地方开一些加盟店,就有了NYPizzaStore和ChicagoPizzaStore等,可是由于地域差异造成了人们的口味也不同,就是说同一种pizza的风味都不一样,比如纽约的cheesePizza的饼要厚一些,而芝加哥的cheesePizza的饼要薄一些。这样就不能使用简单工厂呢?先不作回答,接着往下看,由于各个地方的pizza的风味不一样,所以让他们自己制造自己的pizza,就在PizzaStore中写一个抽象的制作pizza的方法

  public abstract Pizza createPizza(String type);

  然后在orderPizza中调用这个方法就可以得到想要的pizza,原来的简单工厂就可以替换为

  Pizza pizza = createPizza(type);

  具体的pizza店(如NYPizzaStore和ChiragoPizzaStore)继承自抽象的pizza店PizzaStore,然后实现各自制作pizza的方法createPizza,比如像这样

  NYPizzaStore

  //默认为纽约风味的pizza

  @Override

  public Pizza createPizza(String type) {

  Pizza pizza = null;

  if (type.equals("cheese"))

  pizza = new CheesePizza();

  else

  pizza = new GreekPizza();

  return pizza;

  }

  ChiragoPizzaStore

  @Override

  public Pizza createPizza(String type) {

  Pizza pizza = null;

  if (type.equals("cheese")) {

  pizza = new ChicagoCheesePizza();

  }

  else

  pizza = new ChicagoGreekPizza();

  return pizza;

  }

  可以测试一下

  ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore();

  chicagoPizzaStore.orderPizza("cheese");

  可以看到,不同pizza店生产不同的pizza,都是依靠public abstract Pizza createPizza(String type);这个方法,能够生产不同的pizza,就像一个工厂一样。所以把这个方法称为工厂方法。工厂方法都是要返回一个具体的产品的。

  工厂方法模式的定义

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

  还没有完,可以看到,2个具体的pizza店都是负责创建pizza的,于是我就想能不能把这2个pizza店创建pizza的动作交给一个简单工厂来处理,就像这样

  public class NoSimplePizzaFactory {

  //通过pizza店clazz来区分地域

  public static Pizza createPizza(Class clazz, String type) {

  if (clazz == null)

  throw new NullPointerException("address不能为空");

  if (StringUtils.isNullOrEmpty(type))

  throw new NullPointerException("type不能为空");

  if (clazz.getSimpleName().equals(NYPizzaStore.class.getSimpleName())) {

  if (type.equals("cheese"))

  return new CheesePizza();

  else

  return new GreekPizza();

  } else if (clazz.getSimpleName().equals(ChicagoPizzaStore.class.getSimpleName())) {

  if (type.equals("cheese"))

  return new ChicagoCheesePizza();

  else

  return new ChicagoGreekPizza();

  } else

  return null;

  }

  }

  于是在具体的pizza店中调用制作pizza的方法就可以得到pizza了

  NoSimplePizzaFactory.createPizza(getClass(), type);

  再来看一下刚才提出的问题:能不能在抽象类PizzaStore中使用简单工厂来制作各地区不同的pizza?答案是可以的,因为我们现在有了一个简单工厂NoSimplePizzaFactory(简单工厂不简单啊),它可以生产各地区不同的pizza。所以来一个重载的orderPizza方法或把原来的orderPizza方法修改一下,就像这样

  public Pizza orderPizza(Class clazz, String type) {

  Pizza pizza = NoSimplePizzaFactory.createPizza(clazz, type);

  pizza.prepare();

  pizza.bake();

  pizza.cut();

  pizza.box();

  return pizza;

  }

  这样就可以制作不同地区不同的pizza了。

  现在pizza店的生意越来越好了,随机而来的问题是制作pizza的原料供不应求,而且各个地区的pizza所用的原料还可能不同,如图

  为了解决原料供不应求和各地制作pizza所用原料不同的问题,我们可以在每个地区都建一座原料工厂。来供应本地区pizza店所需要的原料。新建一个抽象的原料工厂PizzaIngredientFactory,里面有制造原料的方法,比如:

  Dough createDough();

  Sauce createSauce();

  这个工厂是一个接口,然后2个具体的原料工厂ChicagoPizzaIngredientFactory和NYPizaaIngredientFactory实现它,不同的工厂制作不同的原料,

  ChicagoPizzaIngredientFactory

  @Override

  public Dough createDough() {

  return new ChicagoDough();

  }

  @Override

  public Sauce createSauce() {

  return new ChicagoSauce();

  }

  NYPizaaIngredientFactory

  @Override

  public Dough createDough() {

  return new NYDough();

  }

  @Override

  public Sauce createSauce() {

  return new NYSauce();

  }

  现在在pizza中加入这些原料,也就是在Pizza类中加入原料属性

  Dough dough; //面团

  Sauce sauce; //酱料

  大家还可以自己添加其他的原料属性,这些原料是工厂生产的,所以把工厂也放在Pizza类中

  private PizzaIngredientFactory ingredientFactory;

  public Pizza(PizzaIngredientFactory ingredientFactory) {

  this.ingredientFactory = ingredientFactory;

  }

  然后在具体的pizza中添加对应的构造方法,然后在准备prepare阶段生产原料,所以prepare方法修改一下

  public void prepare() {

  System.out.println("prepare");

  description();

  if (ingredientFactory != null) {

  dough = ingredientFactory.createDough();

  sauce = ingredientFactory.createSauce();

  System.out.println(dough.getName());

  System.out.println(sauce.getName());

  }

  }

  然后在简单工厂NoSimplePizzaFactory中就可以使用原料工厂了,然后在该类中引入原料工厂

  private static PizzaIngredientFactory chicagoPizzaIngredientFactory = new ChicagoPizzaIngredientFactory();

  private static PizzaIngredientFactory nyPizaaIngredientFactory = new NYPizaaIngredientFactory();

  然后创建pizza是注入工厂,修改其中的即可

  if (clazz.getSimpleName().equals(NYPizzaStore.class.getSimpleName())) {

  if (type.equals("cheese"))

  return new CheesePizza();

  else

  return new GreekPizza(nyPizaaIngredientFactory);

  测试一下,你会发现可以通过工厂获得不同的pizza。本例只为说明工厂模式,所以还有不足之处,例如,一旦确定了pizza的区域和种类,通过orderPizza方法得到的pizza就是确定的,并不能在动态的改变制作pizza的原料,我的思路:只要控制原料工厂就可以在制作pizza时动态的改变原料。

  从本例可以看到,pizza原料工厂就是一个抽象工厂

  抽象工厂模式的定义

  提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

  工厂方法是不是潜伏在抽象工厂里面

  可以看到PizzaIngredientFactory接口中有2个制造原料的抽象方法,这2个方法的作用是生产原料,所以这2个是工厂方法。

  小结

  简单工厂专职创建对象

  工厂方法是一个生产产品的抽象方法,具体生产什么产品由具体的子类决定

  抽象工厂用于制造产品的“家族”,具体产品家族由具体工厂决定,抽象工厂里的方法通常是工厂方法