四、 简单工厂模式
1 基本介绍
简单工厂模式(Simple Factory Pattern),又称 静态工厂方法模式(Static Factory Method Pattern),是一种 创建型 设计模式。它通过一个专门的 工厂类 来 负责创建其他类的实例,这些被创建的实例通常都具有 共同的父类或接口。
注意:简单工厂模式虽然 不属于GOF(四人组)提出的 23 种经典设计模式之一,但它仍然是一种 常见且有用的 设计模式,特别适用于一些 简单的创建对象 的场景。它就像被移出太阳系八大行星的矮行星——冥王星。
2 案例
案例中的类如下所示:
- 饮品类:
Drink
抽象类,它有一个抽象方法drink()
,并有两个子类:Tea
类和Coffee
类。 - 饮品工厂类:
DrinkFactory
类有一个静态方法,能够根据饮品的类型生产对应的饮品。 - 客户端类:
Client
类中使用了饮品工厂,创建了不同类型的饮品。
2.1 Drink 抽象类
public abstract class Drink { // 饮品的抽象类
public abstract void drink(); // 喝饮品
protected String name; // 饮品的名称
public Drink(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
2.2 Tea 类
public class Tea extends Drink { // 茶饮品
public Tea(String name) {
super(name);
}
@Override
public void drink() {
System.out.println("你喝了[" + super.name + "]这个茶饮品");
}
}
2.3 Coffee 类
public class Coffee extends Drink { // 咖啡饮品
public Coffee(String name) {
super(name);
}
@Override
public void drink() {
System.out.println("你喝了[" + super.name + "]这个咖啡饮品");
}
}
2.4 DrinkFactory 类
public class DrinkFactory { // 饮品工厂
/**
* 根据 饮品类型 创建 指定名称 的饮品
* @param drinkType 饮品类型
* @param drinkName 指定名称
* @return 创建的饮品
*/
public static Drink createDrink(String drinkType, String drinkName) {
Drink drink = null;
switch (drinkType) {
case "tea": // 如果是茶
drink = new Tea(drinkName); // 则创建 茶饮品 的对象
break;
case "coffee": // 如果是咖啡
drink = new Coffee(drinkName); // 则创建 咖啡饮品 的对象
break;
default: // 什么也不需要做,等待返回 null
}
return drink;
}
}
2.5 Client 类
public class Client { // 使用 饮品工厂创建具体饮品 的客户端
public static void main(String[] args) {
Drink tea = DrinkFactory.createDrink("tea", "铁观音");
tea.drink();
Drink coffee = DrinkFactory.createDrink("coffee", "拿铁");
coffee.drink();
}
}
2.6 Client 类运行结果
你喝了[铁观音]这个茶饮品
你喝了[拿铁]这个咖啡饮品
2.7 总结
如果想要再添加一种饮品,则只需要再写一个 Drink
的子类,并在 DrinkFactory
类的 createDrink()
方法的 switch
语句中再添加一个分支即可。从而 在一定程度上 增强了系统的扩展性。
Client
类不需要知道具体的饮品是如何创建的,即使 DrinkFactory
类的 createDrink()
方法被修改了,也不会影响 Client
类已有的代码。从而 减少了 客户端 与 具体饮品 的耦合。
3 各角色之间的关系
3.1 角色
3.1.1 Product ( 抽象产品 )
该角色负责 定义 所有具体产品的 共性(包括 属性 和 方法),是所有具体产品的 父类 或 被实现的接口。在本案例中,Drink
抽象类扮演了这个角色。
3.1.2 ConcreteProduct ( 具体产品 )
该角色负责 实现 抽象产品所定义的 方法。在本案例中,Tea
类和 Coffee
类都扮演了这个角色。
3.1.3 Factory ( 工厂 )
该角色负责 实现创建 所有 具体产品的内部逻辑,并 对外提供一个用于创建产品对象的接口。在本案例中,DrinkFactory
类扮演了这个角色。
3.1.4 Client ( 客户端 )
该角色负责 使用 工厂创建具体产品。在本案例中,Client
类扮演了这个角色。
3.2 类图
在本类图中,Product
是 抽象类,实际上,Product
还可以是 接口,它的作用是:记录具体产品的共性。
注意:一般来说,Factory
类的 createProduct()
方法是 静态的,这就是 静态工厂方法模式 的出处。
4 注意事项
- 不适合复杂系统:对于 产品类型较多 或 创建逻辑复杂 的系统,简单工厂模式可能不是最佳选择。在这种情况下,可以考虑使用 工厂方法模式 或 抽象工厂模式 等更灵活的设计模式。
- 难以测试:由于工厂类通常包含多个分支语句来创建不同类型的对象,所以测试人员需要 确保每个分支都被正确测试,以确保工厂类的正确性。
- 扩展具体产品:每当需要添加新的产品时,都需要修改工厂类中的代码(如增加新的分支语句)。
- 工厂类的职责:确保工厂类的职责保持 简单 和 清晰。工厂类应该 只负责创建对象,而不应该包含其他复杂的业务逻辑。
- 考虑使用接口:在可能的情况下,将抽象产品角色定义为 接口 而不是 抽象类,可以提高系统的灵活性和扩展性。在 Java 中,一个类只能继承一个父类,但能实现多个接口。
- 配置和依赖注入:在某些情况下,可以考虑使用 配置和依赖注入(DI)来替代简单工厂模式。通过配置和 DI,可以在不修改代码的情况下更改对象的创建方式,从而提高系统的灵活性和可维护性。
5 在源码中的使用
在 JDK 中,简单工厂模式被广泛应用于各种类的设计中。例如,java.util.Calendar
类就使用了简单工厂模式。通过 Calendar.getInstance()
静态方法,可以获取到当前默认时区和默认语言环境的 Calendar
对象。这个方法的内部调用了 createCalendar()
方法,createCalendar()
方法使用了简单工厂模式,如下所示:
// 在 java.util.Calendar 类中
public static Calendar getInstance() {
Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
// 注意:在以下的代码中使用到了简单工厂模式
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
cal = switch (caltype) {
case "buddhist" -> new BuddhistCalendar(zone, aLocale);
case "japanese" -> new JapaneseImperialCalendar(zone, aLocale);
case "gregory" -> new GregorianCalendar(zone, aLocale);
default -> null;
};
}
}
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
6 优缺点
优点:
- 降低系统的耦合度:客户端无需知道具体产品的 创建细节,只需传入参数即可获得所需对象。
- 增加系统的部分扩展性:如果想要新增一个产品,则只需在工厂类中增加这个产品创建的代码,但不能扩展过多的产品,否则会导致工厂类难以维护。
缺点:
- 违背开闭原则:增加新产品时需要修改工厂类中创建产品的方法,而不是新增一个方法,违背了 开闭原则(对扩展开放,对修改关闭)。
- 产品类型过少:当产品类型较多时,工厂类的代码会变得很复杂,其职责会过于沉重,难以维护。
- 创建逻辑过于简单:当产品的创建逻辑很复杂时,工厂类的代码会变得很复杂,难以维护。
7 使用场景
7.1 对象的 创建逻辑相对简单
- 场景:对象的 创建逻辑相对简单,没有太多复杂的 依赖关系 或 初始化过程。
- 示例:如创建一个图形绘制工具,可以绘制不同类型的图形(如圆形、矩形等),每种图形的创建逻辑相对简单,且不需要复杂的依赖关系。
7.2 对象的 创建过程相对稳定
- 场景:对象的 创建过程相对稳定,不会经常变化,从而减少对工厂类的修改,方便管理和维护。
- 实例:在数据库连接管理中,如果数据库类型相对稳定(如 MySQL、Oracle 等),可以使用简单工厂模式根据配置或用户输入的数据库类型来创建相应的数据库连接对象。
7.3 需要根据 不同条件 创建 不同实例
- 场景:在需要 根据不同的条件或参数来创建不同类型的对象 时,简单工厂模式可以根据传入的参数 动态地 创建并返回相应的对象实例。
- 实例:日志记录器可以根据用户的选择或配置来创建不同类型的日志记录器对象(如文件日志记录器、数据库日志记录器等),并将日志记录到不同的目标中。
7.4 减少 客户端代码 与 具体类 的耦合
- 场景:简单工厂模式通过将对象的创建逻辑 封装 在工厂类中,客户端只需要通过工厂类来获取所需的对象,而不需要知道对象的具体创建过程,从而 减少了 客户端代码 与 具体类 之间的 耦合。
- 实例:在文件解析器中,根据文件类型(如 XML 文件、JSON 文件等)来创建相应的文件解析器对象,客户端只需要通过工厂类传入文件类型即可获取相应的解析器对象,而不需要关心具体的解析逻辑。
7.5 产品类型较少 且 创建逻辑简单
- 场景:简单工厂模式适用于 产品类型相对较少 且 创建逻辑相对简单 的场景。如果 产品类型较多 或 创建逻辑复杂,则 只能 使用更灵活的 工厂方法模式 或 抽象工厂模式。
- 实例:如一个简单的文具工厂,只需要生产铅笔、钢笔和毛笔等少数几种产品,且每种产品的创建逻辑相对简单。
7.6 总结
综上所述,简单工厂模式适用于对象 创建逻辑简单、创建过程稳定、需要根据不同条件创建不同实例 以及 减少客户端代码与具体类耦合 的场景。然而,简单工厂模式也存在一些 局限性,如 工厂类职责过重、不利于系统扩展 等,因此在具体使用时,需要根据实际情况决定是否使用简单工厂模式。
8 总结
简单工厂模式是一种 创建型 设计模式,它通过一个工厂类来创建具体产品,这些具体产品通常具有共同的抽象产品作为父类或接口。
简单工厂模式将具体产品的创建逻辑 封装 在工厂类中,客户端无需知道具体产品的类名,只需要知道相应的参数即可,从而 降低了 客户端与具体产品类之间的 耦合度。
简单工厂模式要求 具体产品的种类不多,并且具体产品的 创建逻辑比较简单。
原文地址:https://blog.csdn.net/qq_61350148/article/details/140593914
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!