重生之我在水果店卖水果-Java设计模式之简单工厂、工厂方法和抽象工厂模式

772 阅读14分钟

众所周知,现实生活中工厂是用于生产各种产品的地方,而在程序设计中,工厂为生产实例的地方。我们平时在Java创建对象时都需要通过new来创建一个类型的实例。

不过在一个复杂的系统里面,创建的对象也需要根据外界需求来确定。

我们从一个例子开始:现在我们需要在水果店供应水果,有西瓜、橙子、苹果,水果店会根据顾客的口味喜好来供应不同的水果。

这里我们建立一个水果抽象类,并使用不同的水果具体类型继承它,并定义一个枚举作为参数:

2024年10月13日171055.png

首先是水果抽象类:

package com.gitee.swsk33.simplefactory.model.prototype;

import lombok.Data;

/**
 * 水果抽象类
 */
@Data
public abstract class Fruit {

	/**
	 * 水果名
	 */
	private String name;

}

然后是具体水果,首先是西瓜:

package com.gitee.swsk33.simplefactory.model;

import com.gitee.swsk33.simplefactory.model.prototype.Fruit;
import lombok.Data;

/**
 * 西瓜类
 */
@Data
public class Watermelon extends Fruit {

	/**
	 * 是否保熟
	 */
	private boolean cooked;

}

然后是苹果:

package com.gitee.swsk33.simplefactory.model;

import com.gitee.swsk33.simplefactory.model.prototype.Fruit;
import lombok.Data;

/**
 * 苹果类
 */
@Data
public class Apple extends Fruit {

	/**
	 * 甜度
	 */
	private int sweetness;

}

最后是橙子:

package com.gitee.swsk33.simplefactory.model;

import com.gitee.swsk33.simplefactory.model.prototype.Fruit;
import lombok.Data;

/**
 * 橙子类
 */
@Data
public class Orange extends Fruit {

	/**
	 * 酸度
	 */
	private int acidity;

}

口味枚举类型作为参数:

package com.gitee.swsk33.simplefactory.param;

/**
 * 客户口味参数
 */
public enum Flavor {
	/**
	 * 甜
	 */
	SWEET,
	/**
	 * 酸甜
	 */
	SWEET_ACID,
	/**
	 * 酸
	 */
	ACID
}

好了,现在水果店要卖水果了,建立水果店作为主类,如下:

// 来了个顾客
Customer customer = new Customer();
customer.setFlavor(Flavor.SWEET_ACID);
if (customer.getFlavor() == Flavor.SWEET) {
	Watermelon watermelon = new Watermelon();
	// ...
} else if (customer.getFlavor() == Flavor.SWEET_ACID) {
	Apple apple = new Apple();
	// ...
} else if (customer.getFlavor() == Flavor.ACID) {
	Orange orange = new Orange();
	// ...
}

看起来是个简单的条件判断,但是这样有两个主要问题:

  1. 代码重复:其余水果店或者其余卖水果的商店也要这么写
  2. 耦合度高:水果类被很大程度地包含至水果店类,只要修改水果类,商店类也要被全部修改

耦合度:某个类的代码包含了其它类的逻辑,包含的多耦合度就高,也容易互相影响。

因此我们需要使用工厂模式来解决这个问题。

1,简单工厂模式

(1) 定义和组成

简单工厂模式是一种创建型设计模式,通常用于创建一个系列的不同具体类型的对象,该设计模式使用工厂类封装了对参数的解析与具体类型的创建的过程,将创建过程与客户端解耦合。

在简单工厂模式中,有下列组成部分:

  • 工厂类(Factory):提供一个静态方法,根据给定的参数返回不同的产品对象
  • 抽象产品类(Abstract Product):所有产品的基类或接口,表示要生产的一系列对象的抽象
  • 具体产品类(Concrete Product):实现具体产品的不同类型

(2) 实现简单工厂

在上述“水果供应”的例子中,我们已经将各种水果抽象为一个接口或者是抽象类,这个时候我们只需新建一个水果工厂类并生产它们即可,做类图如下:

2024年10月13日172115.png

可见在这个示例中,我们的组成部分:

  • 工厂类:FruitFactory
  • 抽象产品类:Fruit
  • 具体产品类:WatermelonAppleOrange

我们来实现水果工厂类,这就是简单工厂模式最核心的部分了:

package com.gitee.swsk33.simplefactory.factory;

import com.gitee.swsk33.simplefactory.model.Apple;
import com.gitee.swsk33.simplefactory.model.Orange;
import com.gitee.swsk33.simplefactory.model.Watermelon;
import com.gitee.swsk33.simplefactory.model.prototype.Fruit;
import com.gitee.swsk33.simplefactory.param.Flavor;

/**
 * 水果工厂类
 */
public class FruitFactory {

	/**
	 * 根据口味生产相应的水果
	 *
	 * @param flavor 传入口味枚举参数
	 * @return 对应的水果
	 */
	public static Fruit get(Flavor flavor) {
		Fruit fruit = null;
		switch (flavor) {
			case SWEET -> {
				Watermelon watermelon = new Watermelon();
				watermelon.setName("西瓜");
				watermelon.setCooked(true);
				fruit = watermelon;
			}
			case SWEET_ACID -> {
				Apple apple = new Apple();
				apple.setName("苹果");
				apple.setSweetness(100);
				fruit = apple;
			}
			case ACID -> {
				Orange orange = new Orange();
				orange.setName("橙子");
				orange.setAcidity(12);
				fruit = orange;
			}
		}
		return fruit;
	}

}

然后在水果店类中,我们来试一下:

package com.gitee.swsk33.simplefactory;

import com.gitee.swsk33.simplefactory.factory.FruitFactory;
import com.gitee.swsk33.simplefactory.model.prototype.Fruit;
import com.gitee.swsk33.simplefactory.param.Flavor;

/**
 * 水果店类,卖水果
 */
public class Store {

	public static void main(String[] args) {
		// 来了个顾客
		Fruit getFruit = FruitFactory.get(Flavor.SWEET_ACID);
		System.out.println("得到水果:" + getFruit.getName());
	}

}

结果:

image-20241013172536102

可见简单工厂模式也是根据外部需求来创建对象,只不过现在职责更加明确,创建对象的任务以及需求判断,全部交给了工厂。水果店只需告诉工厂客人要吃什么,并从工厂取得相应水果给客人,而不需要知道具体要什么水果

此外,简单工厂模式使用了向上转型将所有返回的水果对象进行了泛化,在从工厂类获取水果时,我们无需关心要生产具体什么样的水果,而在客户端获取对象使用时,可以进一步地进行向下转型操作。

由于所有的水果都有同一个属性“水果名”,因此水果使用抽象类表示,抽象类放不同水果共有的属性。否则可以使用接口表示水果,根据实际情况而定。

(3) 使用反射改造工厂类

可见在工厂类中,我们使用了判断的方式,来决定到底创建哪一种水果,那么如果水果的具体类型非常多的话,就会导致工厂变得复杂。

那么能否直接根据客户需要的类型,创建对应的水果呢?事实上,借助反射就可以轻松实现。

我们修改水果工厂类如下:

package com.gitee.swsk33.simplefactoryreflect.factory;

import com.gitee.swsk33.simplefactoryreflect.model.prototype.Fruit;

/**
 * 水果工厂类
 */
public class FruitFactory {

	/**
	 * 根据口味生产相应的水果,使用反射替换条件判断
	 *
	 * @param fruitType 传入具体需要的水果类型
	 * @return 对应的水果
	 */
	public static Fruit get(Class<? extends Fruit> fruitType) {
		Fruit fruit = null;
		try {
			// 使用传入类型的构造器创建对象
			fruit = fruitType.getDeclaredConstructor().newInstance();
		} catch (Exception e) {
			System.out.println("创建水果出错!");
			e.printStackTrace();
		}
		return fruit;
	}

}

这里不再使用口味枚举作为参数,而是之间传入水果类型,借助类型Class对象的getDeclaredConstructor().newInstance()方法,实例化一个具体的水果对象即可。

主类水果店如下:

package com.gitee.swsk33.simplefactoryreflect;

import com.gitee.swsk33.simplefactoryreflect.factory.FruitFactory;
import com.gitee.swsk33.simplefactoryreflect.model.Watermelon;
import com.gitee.swsk33.simplefactoryreflect.model.prototype.Fruit;

/**
 * 水果店类,卖水果
 */
public class Store {

	public static void main(String[] args) {
		// 来了个顾客
		Fruit getFruit = FruitFactory.get(Watermelon.class);
		System.out.println("得到水果类型:" + getFruit.getClass().getSimpleName());
	}

}

结果:

image-20241013173208406

可见,使用反射避免了冗长的类型判断或者参数解析操作,进一步简化了工厂类创建对象的逻辑,但是反射也会导致程序性能大幅降低,在创建大量对象的场景下,使用反射的速度会慢于使用判断的速度。

2,工厂方法模式

(1) 定义和组成

工厂方法模式是一种创建型设计模式,它是基于简单工厂模式的一个改造,与简单工厂模式不同,工厂方法模式将对象的创建延迟到子类,通过定义一个创建对象的接口来让子类决定实例化哪个具体类,它解决了简单工厂模式中工厂类职责过重的问题。

工厂方法模式由下列部分组成:

  • 抽象产品类(Abstract Product):所有产品的基类或接口,表示一系列产品
  • 具体产品类(Concrete Product):具体的不同产品
  • 抽象工厂类(Abstract Factory):声明一个工厂抽象方法,由子类实现具体的产品创建
  • 具体工厂类(Concrete Factory):实现工厂方法,返回具体的产品对象

(2) 实现工厂方法

现在我们将上述生产水果的示例修改成工厂方法模式,现在我们需要定义一个抽象工厂接口,表示创建水果的方法,然后为每一个具体的水果创建一个具体工厂,每个具体工厂只负责生产一个水果,做类图如下:

2024年10月13日173822.png

可见我们将生产水果这个操作抽离了出来,并由各个不同的工厂实现生产不同的具体水果,在这里:

  • 抽象工厂类(接口):FruitFactory
  • 具体工厂类:WatermelonFactoryAppleFactoryOrangeFactory
  • 抽象产品类:Fruit
  • 具体产品类:WatermelonAppleOrange

抽象工厂接口如下:

package com.gitee.swsk33.factorymethod.factory;

import com.gitee.swsk33.factorymethod.model.prototype.Fruit;

/**
 * 水果抽象工厂类
 */
public interface FruitFactory {

	/**
	 * 根据口味生产相应的水果
	 *
	 * @return 对应的水果
	 */
	Fruit get();

}

然后是具体的工厂,首先是西瓜工厂:

package com.gitee.swsk33.factorymethod.factory.impl;

import com.gitee.swsk33.factorymethod.factory.FruitFactory;
import com.gitee.swsk33.factorymethod.model.Watermelon;
import com.gitee.swsk33.factorymethod.model.prototype.Fruit;

/**
 * 生产西瓜的具体工厂
 */
public class WatermelonFactory implements FruitFactory {

	@Override
	public Fruit get() {
		Watermelon watermelon = new Watermelon();
		watermelon.setName("西瓜");
		watermelon.setCooked(true);
		return watermelon;
	}

}

然后是苹果工厂:

package com.gitee.swsk33.factorymethod.factory.impl;

import com.gitee.swsk33.factorymethod.factory.FruitFactory;
import com.gitee.swsk33.factorymethod.model.Apple;
import com.gitee.swsk33.factorymethod.model.prototype.Fruit;

/**
 * 生产苹果的具体工厂
 */
public class AppleFactory implements FruitFactory {

	@Override
	public Fruit get() {
		Apple apple = new Apple();
		apple.setName("苹果");
		apple.setSweetness(12);
		return apple;
	}

}

最后是橙子工厂:

package com.gitee.swsk33.factorymethod.factory.impl;

import com.gitee.swsk33.factorymethod.factory.FruitFactory;
import com.gitee.swsk33.factorymethod.model.Orange;
import com.gitee.swsk33.factorymethod.model.prototype.Fruit;

/**
 * 生产橙子的具体工厂
 */
public class OrangeFactory implements FruitFactory {

	@Override
	public Fruit get() {
		Orange orange = new Orange();
		orange.setName("橙子");
		orange.setAcidity(5);
		return orange;
	}

}

在客户端调用工厂,获取对应水果时,只需先实例化对应的具体工厂,然后生产水果:

package com.gitee.swsk33.factorymethod;

import com.gitee.swsk33.factorymethod.factory.FruitFactory;
import com.gitee.swsk33.factorymethod.factory.impl.AppleFactory;
import com.gitee.swsk33.factorymethod.model.prototype.Fruit;

/**
 * 水果店类,卖水果
 */
public class Store {

	public static void main(String[] args) {
		// 需要对应的水果时,实例化一个具体工厂即可
		// 比如说现在要苹果
		FruitFactory factory = new AppleFactory();
		Fruit fruit = factory.get();
		System.out.println("得到水果:" + fruit.getName());
	}

}

结果:

image-20241013174359107

3,抽象工厂模式

(1) 定义和组成

上面简单工厂模式和工厂方法模式只是涉及到一类产品的生产,那多类产品怎么办呢?

譬如说水果店现在开始卖果茶了,我们还需要新建一个果茶工厂,并和水果店联系起来吗?当然不是。这会导致水果店和各个工厂耦合度太紧,不利于扩展。

对于生产多个产品系列的情况,我们就要用到抽象工厂模式了!抽象工厂模式是一种创建型设计模式,用于创建一组相关或互相依赖的对象,而无需指定它们的具体类。

与简单工厂和工厂方法模式的区别在于:简单工厂和工厂方法模式仅能够创建一个产品系列(只包含一个抽象产品),而抽象工厂模式可以创建多个产品系列(包含多个抽象产品)。

在讲解抽象工厂模式之前需要了解两个概念:

  • 产品族:也就是一个系列的产品,上面的西瓜、苹果、橙子就属于一个产品族
  • 产品等级结构:一个产品可以衍生其相关的一系列产品,例如西瓜可以做成西瓜汁,那么西瓜和西瓜汁就属于西瓜这个产品结构

我们来看个图:

image.png

可见纵坐标是产品族,也就是我们所说的一系列产品,横坐标表示一个产品的产品等级结构。

那么在抽象工厂模式中,有下列组成部分:

  • 多个抽象产品类(Abstract Product A, Abstract Product B):各种产品的抽象定义,一个产品族对应一个抽象类
  • 具体产品类(Concrete Product):实现具体产品的多个种类和系列
  • 抽象工厂类(Abstract Factory):声明创建产品族中每种产品的抽象方法
  • 具体工厂类(Concrete Factory):具体的工厂,实现抽象工厂,一个具体用于创建一个产品族的具体产品

(2) 实现抽象工厂

假设现在水果店要生产水果和水果饮料两大类产品,水果中包含上述的西瓜、苹果和橙子,而水果饮料包含西瓜汁、苹果汁和橙子汁,这里可见:

  • 水果和水果饮料是两大产品族
  • 每个具体的水果,具体的果汁是两大产品族下具体的一个产品

对于每一个产品族,我们都需要建立一个具体的工厂来生产这个产品族的具体产品,每个具体工厂都实现抽象工厂接口,做类图如下:

2024年10月13日180023.png

可见:

  • 抽象产品类:FruitDrink
  • 具体产品类:
    • Fruit产品族:WatermelonAppleOrange
    • Drink产品族:WatermelonDrinkAppleDrinkOrangeDrink
  • 抽象工厂类(接口):FruitFoodFactory
  • 具体工厂类:FruitFactoryDrinkFactory

需要注意的是,上述还有一个FruitFoodFactoryBuilder类是用于根据产品族类型获取对应的具体工厂的类,这个类不是标准的抽象工厂模式的组成部分。

好的,现在先新增一个Type枚举,表示产品族类型:

package com.gitee.swsk33.abstractfactory.param;

/**
 * 客户需求商品类型
 */
public enum Type {
	/**
	 * 水果
	 */
	FRUIT,
	/**
	 * 饮料
	 */
	DRINK
}

然后就是抽象出每个产品族,这里就不进行演示了,和抽象水果是一样的,按照类图很简单。

然后新建水果食品工厂的接口,即为我们的抽象工厂:

package com.gitee.swsk33.abstractfactory.factory;

import com.gitee.swsk33.abstractfactory.model.prototype.Drink;
import com.gitee.swsk33.abstractfactory.model.prototype.Fruit;
import com.gitee.swsk33.abstractfactory.param.Flavor;

/**
 * 水果食品工厂抽象
 */
public interface FruitFoodFactory {

	/**
	 * 获取水果
	 *
	 * @param flavor 口味枚举参数
	 * @return 根据顾客口味获得对应水果
	 */
	Fruit getFruit(Flavor flavor);

	/**
	 * 获取饮料
	 *
	 * @param flavor 口味枚举参数
	 * @return 根据顾客口味获得对应水果饮料
	 */
	Drink getDrink(Flavor flavor);

}

然后建立具体水果工厂和水果饮料工厂实现抽象工厂,首先是水果工厂:

package com.gitee.swsk33.abstractfactory.factory.impl;

import com.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
import com.gitee.swsk33.abstractfactory.model.Apple;
import com.gitee.swsk33.abstractfactory.model.Orange;
import com.gitee.swsk33.abstractfactory.model.Watermelon;
import com.gitee.swsk33.abstractfactory.model.prototype.Drink;
import com.gitee.swsk33.abstractfactory.model.prototype.Fruit;
import com.gitee.swsk33.abstractfactory.param.Flavor;

/**
 * 水果工厂类
 */
public class FruitFactory implements FruitFoodFactory {

	@Override
	public Fruit getFruit(Flavor flavor) {
		Fruit fruit = null;
		switch (flavor) {
			case SWEET -> {
				Watermelon watermelon = new Watermelon();
				watermelon.setName("西瓜");
				watermelon.setCooked(true);
				fruit = watermelon;
			}
			case SWEET_ACID -> {
				Apple apple = new Apple();
				apple.setName("苹果");
				apple.setSweetness(100);
				fruit = apple;
			}
			case ACID -> {
				Orange orange = new Orange();
				orange.setName("橙子");
				orange.setAcidity(12);
				fruit = orange;
			}
		}
		return fruit;
	}

	@Override
	public Drink getDrink(Flavor flavor) {
		return null;
	}

}

水果饮料工厂(果茶工厂):

package com.gitee.swsk33.abstractfactory.factory.impl;

import com.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
import com.gitee.swsk33.abstractfactory.model.AppleDrink;
import com.gitee.swsk33.abstractfactory.model.OrangeDrink;
import com.gitee.swsk33.abstractfactory.model.WatermelonDrink;
import com.gitee.swsk33.abstractfactory.model.prototype.Drink;
import com.gitee.swsk33.abstractfactory.model.prototype.Fruit;
import com.gitee.swsk33.abstractfactory.param.Flavor;

public class DrinkFactory implements FruitFoodFactory {

	@Override
	public Fruit getFruit(Flavor flavor) {
		return null;
	}

	@Override
	public Drink getDrink(Flavor flavor) {
		Drink drink = null;
		switch (flavor) {
			case SWEET -> {
				WatermelonDrink watermelonDrink = new WatermelonDrink();
				watermelonDrink.setName("西瓜汁");
				drink = watermelonDrink;
			}
			case SWEET_ACID -> {
				AppleDrink appleDrink = new AppleDrink();
				appleDrink.setName("苹果汁");
				drink = appleDrink;
			}
			case ACID -> {
				OrangeDrink orangeDrink = new OrangeDrink();
				orangeDrink.setName("橙汁");
				drink = orangeDrink;
			}
		}
		return drink;
	}

}

因为水果工厂不生产果茶但是水果工厂实现了水果类食物工厂的接口,因此也有getDrink方法,将其留空返回null即可,果茶工厂和水果工厂类似,同样是实现水果类食物工厂接口,并完成getDrink方法,但是把getFruit方法留空返回null

然后就是工厂构建类,用于根据产品族类型获取对应的具体工厂:

package com.gitee.swsk33.abstractfactory.factory.builder;

import com.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
import com.gitee.swsk33.abstractfactory.factory.impl.DrinkFactory;
import com.gitee.swsk33.abstractfactory.factory.impl.FruitFactory;
import com.gitee.swsk33.abstractfactory.param.Type;

import java.util.HashMap;
import java.util.Map;

/**
 * 工厂建造器
 */
public class FruitFoodFactoryBuilder {

	/**
	 * 存放不同类型对应的具体工厂的实例
	 */
	private static final Map<Type, FruitFoodFactory> FACTORY_MAP = new HashMap<>();

	static {
		// 初始化工厂列表
		FACTORY_MAP.put(Type.FRUIT, new FruitFactory());
		FACTORY_MAP.put(Type.DRINK, new DrinkFactory());
	}

	/**
	 * 根据客户的需求类型建造出对应的工厂
	 *
	 * @param type 抽象类型枚举参数
	 * @return 对应的工厂实例
	 */
	public static FruitFoodFactory getFactory(Type type) {
		return FACTORY_MAP.get(type);
	}

}

最后,在水果店只需要调用工厂构造类先构建对应工厂,再调用对应工厂对应方法生成产品即可。

package com.gitee.swsk33.abstractfactory;

import com.gitee.swsk33.abstractfactory.factory.FruitFoodFactory;
import com.gitee.swsk33.abstractfactory.factory.builder.FruitFoodFactoryBuilder;
import com.gitee.swsk33.abstractfactory.model.prototype.Drink;
import com.gitee.swsk33.abstractfactory.param.Flavor;
import com.gitee.swsk33.abstractfactory.param.Type;

/**
 * 商店类
 */
public class Store {

	public static void main(String[] args) {
		// 需要饮料、口味酸
		// 首先获取对应的工厂
		FruitFoodFactory factory = FruitFoodFactoryBuilder.getFactory(Type.DRINK);
		// 工厂生产物品
		Drink drink = factory.getDrink(Flavor.ACID);
		System.out.println("顾客购买:" + drink.getName());
	}

}

结果:

image.png

可见抽象工厂看起来麻烦,其实逻辑很清晰,主要可以总结为以下几个大步骤:

  1. 将每个产品族进行抽象并创建对应的具体类
  2. 建立抽象工厂
  3. 实现抽象工厂,每个具体实现的工厂负责生产一个产品族

可见抽象工厂模式,是先确定要生产的产品族并调用工厂建造器实例化对应的工厂,再确定要生产的具体产品利用得到的工厂生成对应的产品实例。

4,总结

无论是简单工厂模式、工厂方法模式还是抽象工厂模式,都封装了不同具体类型的产品的创建细节,这使得我们创建一个对象时,无需关心其具体类型以及其生产过程。

这三种模式各有其优缺点:

  • 简单工厂模式

    • 通过一个工厂类创建所有类型的对象
    • 简单易用,但不遵循开闭原则
  • 工厂方法模式

    • 将对象创建的职责延迟到子类,允许每个子类定义自己的产品创建逻辑
    • 遵循开闭原则,但每个产品都需要创建一个对应的工厂类
  • 抽象工厂模式

    • 用于创建一组相关或依赖的产品族
    • 更复杂,但能够确保产品族中的对象保持一致性

总而言之:

  • 工厂方法模式可以看作是对简单工厂模式的进一步抽象
  • 抽象工厂模式则进一步扩展了工厂方法模式,用于创建多个相关的产品族

简单工厂模式适合产品种类较少且变动不频繁的场景,而工厂方法模式和抽象工厂模式适合需要扩展产品种类的情况,尤其是抽象工厂模式更适用于创建复杂的产品族。

无论是那种工厂模式,在具体产品较多时,我们也可以使用反射来简化工厂类中创建产品的过程。

示例仓库地址: