设计模式笔记-工厂模式(忙碌的果园)

Java中的工厂设计模式是核心设计模式之一,工厂模式主要是为创建对象提供接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。 工厂模式在业界主要有三种形式,分别是:

  • 简单工厂(Simple Factory),又称静态工厂模式(static factory method)
  • 工厂方法(Factory Method)
  • 抽象工厂(Abstract Factory)

这三个模式都是创建型 这个这三个模式名字看着挺像,概念上容易让人比较混淆。本文就介绍一下这三种模式到底有什么区别。

简单工厂

场景

我们在新建一个对象的时候最常见的是去new一个对象,但有时候:

  1. 我们并不想知道这个这个对象是怎么构造;
  2. 我们只想使用这个对象的父类引用;

举个栗子,我们在果园(FruitFacotry)里,果园里有各种各样的水果。我们想吃水果,但我并不想知道这些水果是怎么长出来的。也就是说做为一个果园的客户以及饥渴的吃货,我不关心你的水果是怎么长得,我上来就要吃你一个苹果一个橘子。 这个场景的代码是这样的:

1
2
3
4
5
6
7
8
public class ClientOfFruitFacotry {
public static void main(String[] args){
Fruit apple = FruitFacory.create("APPLE");
apple.beEat();
Fruit orange = FruitFacory.create("ORANGE");
orange.beEat();
}
}

实现

那他是怎么实现的呢?非常简单我们可以这样定义FruitFactory:

1
2
3
4
5
6
7
8
public class FruitFacotry {
public static Fruit create (String fruitName) throws Exception {
if (fruitName.equalsIgnoreCase("APPLE"))
return new Apple();
else if (fruitName.equalsIgnoreCase("ORANGE"))
return new Orange();
else throw new Exception();
}

物品间的关系也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//抽象产品
public interface Fruit{
public void beEat();
}
//具体产品
public class Apple implements Fruit{
public void beEat(){
System.out.println("eat apple");
}
}
public class Orange implements Fruit{
public void beEat(){
System.out.println("eat orange");
}
}

小结

有人会说你的这个类中会有大量的if-else语句。我们可以通过反射,hashmap,枚举等方法对这进行优化。 如果你的代码中有许多对象有着相同的父类,同时你在使用这些对象时常常用到父类的方法,那么你需要使用这种方法。这样做的好处显而易见,果园负责生成果子,客户负责消费果子,客户不需要知道果子是怎么长的,这样就实现了职责的分离。 但是也会有坏处。假设你想新加一种水果,那么你必须修改FruitFacotry这个类,这样显然违反了开闭原则。同时这个类中集合了所有的水果的创造逻辑。可想而知对于新水果的加入,果园是很被动的,如果果园中种植的水果种类会越来越多。同时简单工程的静态方法也使得它不能够被子类继承,所以所有担子都得自己挑了。这可吧果园累坏了,也累坏了我们这些吃水果的人。 这个时候工厂模式就出现了,人们想了个办法,不把所有的水果都放到一个果园中。把它按种类分出来,苹果园种植苹果,橘子园种植橘子。让具体的种植方式分配的各自的果园中。如果将来想种梨,我们就直接在拓展出一个梨园,完全不会改变苹果园,橘子园的任何东西。

工厂方法

工厂方法有称虚构造器(Virtual Constructor,在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

意图

定义一个创建对象的工厂父类公共接口,让工厂子类来决定具体怎样构造对象。 工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。 正如之前的类比,不把所有的水果都放到一个果园中。把它按种类分出来,苹果园种植苹果,橘子园种植橘子。让具体的种植方式分配的各自的果园中。如果将来想种梨,我们就直接在拓展出一个梨园,完全不会改变苹果园,橘子园的任何东西。各种水果的果园负责生产这类水果。代码如下:

果园的工厂类

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface FruitFacotory {
Fruit create();
}
public class AppleFacotry implements FruitFactory{
public Apple create(){
return new Apple();
}
}
public class OrangeFacotry implements FruitFactory{
public Orange create(){
return new Orange();
}
}

优点

如之前所说,使用工厂方法模式在加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合开闭原则(对拓展开放,对修改关闭)。 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。--只需要知道工厂类的类名就行 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。

缺点

在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

拓展

  1. 使用多个工厂方法:在抽象工厂角色中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,以满足对不同的产品对象的需求。比如:

    1
    2
    3
    4
    5
    6
    7
    8
    public class AppleFacotry implements FruitFactory{
    public Apple createHongFuShi(){
    return new Apple("红富士");
    }
    public Apple createGuoGuang(){
    return new Apple("国光");
    }
    }

  2. 产品对象的重复使用:工厂对象将已经创建过的产品保存到一个集合(如数组、List等)中,然后根据客户对产品的请求,对集合进行查询。如果有满足要求的产品对象,就直接将该产品返回客户端;如果集合中没有这样的产品对象,那么就创建一个新的满足要求的产品对象,然后将这个对象在增加到集合中,再返回给客户端。

  3. 多态性的丧失和模式的退化:如果工厂仅仅返回一个具体产品对象,便违背了工厂方法的用意,发生退化,此时就不再是工厂方法模式了。一般来说,工厂对象应当有一个抽象的父类型,如果工厂等级结构中只有一个具体工厂类的话,抽象工厂就可以省略,也将发生了退化。当只有一个具体工厂,在具体工厂中可以创建所有的产品对象,并且工厂方法设计为静态方法时,工厂方法模式就退化成简单工厂模式。

抽象工厂我们将在下一篇文章中介绍。

延伸阅读

请我喝杯咖啡吧!