基本概念

工厂方法:一种创建型设计模式,它定义了一个创建对象的接口(工厂方法),但将对象的实际创建工作推迟到子类中。核心思想是“定义接口,由子类决定创建哪个具体类的实例”。

核心结构

  1. 抽象工厂(creator):声明工厂方法接口,该方法返回抽象产品。

  2. 具体工厂(Concrete Creator):实现工厂方法,返回具体产品实例。

  3. 抽象产品(Product):定义产品的公共接口。

  4. 5.具体产品(Concrete Product):实现抽象产品接口的具体类。

抽象工厂:一种创建型设计模式,它能帮你创建一系列相关的对象,而无需明确指定具体的类。该模式将对象的创建和使用分离开来,使得系统更具可扩展性和可维护性。

核心结构

  1. 抽象工厂(Abstract Factory):声明创建多个产品的方法接口。

  2. 具体工厂(Concrete Factory):实现抽象工厂接口,创建具体产品族。

  3. 抽象产品(Abstract Product):定义各产品的公共接口。

  4. 具体产品(Concrete Product):实现抽象产品接口的具体类。

示例说明

场景一:假设有一家披萨店,需要根据不同的披萨类型制作披萨:

1
2
3
4
5
6
7
8
9
 public final Pizza orderPizza(String type) {
// 判断披萨类型,实例化不同的类
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}

这个时候可以根据设计原则【封装变化】,将Pizza pizza = new Pizza();抽离,建立一个简单的披萨工厂。

1
2
3
4
5
6
7
8
9
10
public interface Pizza {

void prepare();

void bake();

void cut();

void box();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SimplePizzaFactory {

public static Pizza createPizza(String type) {

Pizza pizza = null;
switch (type) {
case "cheese":
pizza = new CheesePizza();
break;
case "pepperoni":
pizza = new PepperoniPizza();
break;
case "clam":
pizza = new ClamPizza();
break;
case "veggie":
pizza = new VeggiePizza();
break;
default:
System.out.println("Invalid type of pizza");
}
return pizza;
}
}

1
2
3
4
5
6
// 抽离实例化过程
public final Pizza orderPizza(String type) {
Pizza pizza = SimplePizzaFactory.createPizza(type);
...
return pizza;
}

好处:在有更多披萨类加入时,只需要修改简单工厂。

注意:简单工厂的实例化方法也可声明为静态方法,称为静态工厂,达到不需要实例化的方式创建一个对象,缺点是不能子类化并修改创建方法的行为。但简单工厂并不是一个设计模式,更像一种编程习惯。

场景二:由于披萨店的生意越来越好,需要更多加盟商,但是每个地方都有每个地方的口味,因此在同一个加盟模式下需要根据地区风味实现不同风味披萨的制作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class PizzaStore {

// 将createPizza方法作为一个抽象方法,交由子类的店去实现不同地区风味披萨的制作,当然披萨的制作步骤确保是一致的
public final Pizza orderPizza(String type) {
Pizza pizza = this.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}

// 工厂方法处理对象的创建,并将对象的创建封装到子类中,使得超类中的客户代码从子类的对象创建代码中解耦,达到松耦合的设计
abstract Pizza createPizza(String type);
}

比如纽约地区的披萨店

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class NYStylePizzaStore extends PizzaStore{

// NYCheesePizza、NYPepperoniPizza类作为具体的披萨类实现了Pizza
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
switch (type) {
case "cheese":
pizza = new NYCheesePizza();
break;
case "pepperoni":
pizza = new NYPepperoniPizza();
break;
case "clam":
pizza = new NYClamPizza();
break;
case "veggie":
pizza = new NYVeggiePizza();
break;
default:
System.out.println("Invalid type of pizza");
}
return pizza;
}
}

这也就达到了哪种披萨的创建交由了子类,但不是说由子类决定制作什么风味的披萨,这是由用户选择哪个地区的披萨店决定的,因而新在地区的加盟店只需要继承PizzaStore类实现createPizza方法即可。

1
2
3
4
5
6
7
8
9
public class Test {

public static void main(String[] args) {
PizzaStore pizzaStore = new NYStylePizzaStore();

Pizza pizza = pizzaStore.orderPizza("cheese");
System.out.println("Ordered pizzaInfo: " + pizza);
}
}

这就是工厂方法模式,通过每一个工厂,工厂方法模式封装具体对象类型的实例化,作为创建型模式的设计方法,抽象的Creator类提供了一个带有创建对象方法(也被称为“工厂方法”)的接口。在抽象的Creator类中实现的任何其他方法,都是用来操作工厂方法生产的产品,只有子类真正实现工厂方法并创建产品。

类图如下:

工厂方法模式

从这里可以看到当实例化一个披萨对象时,PizzaStore创建的对象类型为Pizza,而不是具体的NYCheesePizza,也就是说PizzaStore依赖于抽象的Pizza,这就是依赖倒置原则(依赖抽象,不依赖于具体类),因而设计一个类可以考虑先从顶端开始设计,然后往下到具体类,或者考虑可以抽象出什么。

场景三:现在由于各个加盟店使用的原料优劣不统一,为了确保所有加盟店使用高质量的原料,需要建造一家生产原料的工厂,由工厂专门配送原料。但是各个地区口味不一致,相同的原料也有不同口味,所以需要定义一个抽象工厂接口,该接口负责创建所有原料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 原料
public interface Dough {

}

// 纽约地区风味的原料
public class NYDough implements Dough{


public NYDough()
{
System.out.println("NYDough");
}
}

// 抽象工厂:每个区域的原料工厂实现此接口
public interface PizzaIngredientFactory {

Dough createDough();

Sauce createSauce();

Cheese createCheese();

Veggies[] createVeggies();

Pepperoni createPepperoni();

Clams createClam();
}
1
2
3
4
5
6
7
8
9
// 各个区域的工厂实现
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
@Override
public Dough createDough() {
return new NYDough();
}

...
}

修改Pizza类,加入原料属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class Pizza {

String name;

Dough dough;

Sauce sauce;

Cheese cheese;

Veggies veggies[];

Pepperoni pepperoni;

Clams clam;


abstract public void prepare();


void bake() {
System.out.println("Bake for 25 minutes at 350");
}

void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}

void box() {
System.out.println("Place pizza in official PizzaStore box");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

有了抽象的Pizza类之后,可以发现各个不同区域同类Pizza比如CheesePizza差别在于原料的不同,因此我们不再需要为此不同区域实现不同风味的Pizza类,直接根据注入原料工厂类决定,其他如ClamPizza也类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CheesePizza extends Pizza {

// 原料工厂
PizzaIngredientFactory ingredientFactory;

public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}

@Override
public void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
// 在这里只要准备正确的原料,制作对应Pizza即可
cheese = ingredientFactory.createCheese();
System.out.println("Adding ingredients: ");
}
}

然后改造一下各个地区的加盟店吧,比如NYStylePizzaStore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NYStylePizzaStore extends PizzaStore{
@Override
Pizza createPizza(String type) {
Pizza pizza = null;

// 地区原料工厂实现
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

switch (type) {
case "cheese":
pizza = new CheesePizza(ingredientFactory);
break;
default:
System.out.println("Invalid type of pizza");
}

return pizza;
}
}

在场景三中,通过引入一个称为抽象工厂的新工厂类型,提供为披萨创建原料的方法,通过使用这个接口编写代码,把代码从实际创建产品的工厂解耦,不同地区可以替换不同的工厂实现获得不同的行为。这就是抽象工厂模式

类图如下:

抽象工厂模式类图

在抽象工厂可以看到如createDough()方法都被声明是抽象的,子类覆盖实现这就是工厂方法,抽象工厂方法经常用到工厂方法。

总结

工厂方法

优点

  • 符合开闭原则:新增产品时只需添加新的具体工厂类,无需修改现有代码。

  • 解耦对象创建和使用:客户端只需关心产品接口,无需了解具体实现。

缺点

  • 工厂子类过多:每新增一个产品,就需要增加一个具体工厂类,导致类数量膨胀。
  • 不支持多产品族:只能创建单一类型的产品。

抽象工厂

优点

  • 产品族一致性:确保客户端使用的产品属于同一产品族。
  • 更高的可扩展性:新增产品族时,只需添加新的具体工厂类。

缺点

  • 难以扩展新产品:如果需要新增一种产品,需要修改抽象工厂接口及其所有子类。
  • 实现复杂度高:需要定义多个抽象产品和具体产品类。

异同点

对比一下工厂方法和抽象工厂的异同点

工厂方法

  • 继承的方式创建对象:拓展一个类,并为工厂方法提供实现,延迟实例化到子类;
  • 单一产品;
  • 依赖具体工厂子类
  • 适合于当将创建对象的职责委托给多个具体工厂子类中的某一个时(如插件系统)

抽象工厂

  • 对象组合的方式,子类实现:提供一个抽象接口,子类定义如何实现,组合不同对象;
  • 产品族
  • 依赖抽象工厂和抽象产品
  • 适合于设计需要从多个系列产品中选择一个系列时(如跨平台 UI 组件、游戏角色工厂)。