每天认识一个设计模式 - 工厂模式:代码世界的“流水线“是怎样炼成的?

一、前言

你是否经历过这样的场景?

  • 代码中遍布 new XXX(),每次新增类型都要在几十处地方"打补丁"

  • 对象创建逻辑像藤蔓般缠绕在业务代码中,测试时举步维艰

  • 想为MySQL/MongoDB切换数据源时,发现要修改上百个构造函数

这正是对象创建失控的典型症状——就像让每个工人都自己炼钢打铁造零件,流水线自然会陷入混乱。

而工厂模式的精妙之处,在于将"对象制造权"交给专业的流水线:
✅ 创建逻辑集中化:把易变的构建过程装进保险箱
✅ 解耦实战利器:业务代码不再与具体类绑死
✅ 扩展性革命:新增产品类型无需修改客户端代码

在Java生态中,Spring的BeanFactory管理着百万级对象的诞生,Executors掌控线程池的生死轮回,这些工业级实践都在诠释工厂模式的价值。

今天我们将化身代码世界的"福耀玻璃",从简单作坊到智能工厂,层层拆解:
1️⃣ 流水线搭建的底层逻辑(设计原理)
2️⃣ 不同生产车间的运作模式(代码实现)
3️⃣ 规避流水线故障的秘籍(生产环境技巧)

准备好见证对象制造的艺术了吗?让我们推开工厂模式的大门——

 二、工厂模式的基础介绍

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一,它提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。

工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。

通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。

2.1.工厂模式家族族谱

从图中我们不难了解,工厂模式的核心主要包含四个部分:

  • 产品(Product):是具体产品的抽象基类或接口,定义了产品的通用属性和方法。像具体的手机、衣服等商品,都继承或实现 “Product” 所规定的特性。
  • 创建者(Creator):这是一个抽象接口,其中定义的 “factoryMethod ()” 抽象方法用于创建产品,它不涉及具体产品的创建细节。
  • 具体创建者(ConcreteCreatorA、ConcreteCreatorB ):继承自 “Creator” 接口,实现 “factoryMethod ()” 方法,负责按照特定逻辑创建具体的产品对象。
  • 客户端(Client):是使用产品的部分,通过调用 “Creator” 的 “factoryMethod ()” 方法获取产品,无需关心产品的创建过程。

客户端调用 “Creator” 的 “factoryMethod ()” 方法请求创建产品,“Creator” 本身不创建产品,而是由具体创建者类(“ConcreteCreatorA” 或 “ConcreteCreatorB” )实现该方法,按照各自的逻辑创建具体产品(“ConcreteProductA” 或 “ConcreteProductB”),并返回给客户端使用。 

而客户端只需通过工厂进行调用即可,无需知道具体的创建细节。

2.2.工厂再常见开发框架中的使用

为了帮助大家更好认识工厂模式在实际开发中的应用,我们可以先从经常会使用的开发框架中了解一下。

Spring 框架的 BeanFactory​

在 Spring 框架中,BeanFactory 是其核心容器之一。它就像一个强大的工厂,负责创建和管理各种 Bean 对象。通过配置文件或注解等方式,Spring 根据定义的规则,使用 BeanFactory 创建出所需的对象,并提供给应用程序使用。

例如,在一个大型企业级应用中,众多的业务服务对象、数据访问对象等都由 BeanFactory 统一创建和管理,使得应用的依赖关系更加清晰,对象的创建和维护变得更加高效和可维护。​

JDK 中的 Calendar.getInstance ()​

JDK 中的 Calendar 类的getInstance()方法也是工厂模式的典型应用。当我们调用Calendar.getInstance()时,实际上是通过工厂方法获取一个 Calendar 对象。

由于 Calendar 类是一个抽象类,无法直接实例化,getInstance()方法根据系统默认时区等条件,返回一个具体子类(如 GregorianCalendar)的实例,隐藏了对象创建的复杂细节,方便开发者使用。​

日志框架的 LoggerFactory​

在日志框架中,LoggerFactory 用于创建各种日志记录器(Logger)。不同的日志框架(如 Log4j、SLF4J 等)都有自己的 LoggerFactory 实现。

它根据配置信息创建不同级别的日志记录器,开发者只需通过 LoggerFactory 获取日志记录器,而无需关心日志记录器具体的创建过程,提高了日志管理的便利性和灵活性,使得日志记录功能在不同的应用场景中能够轻松适配。

2.3.工厂模式的类型

1、简单工厂模式(Simple Factory Pattern)

  • 简单工厂模式不是一个正式的设计模式,但它是工厂模式的基础。它使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。

2、工厂方法模式(Factory Method Pattern)

  • 工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。

3、抽象工厂模式(Abstract Factory Pattern)

  • 抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。

下面让我们仔细看一下每种工厂模式的使用方式~ 

三、 简单工厂模式

简单工厂模式提供了一种创建对象的方式,将对象的创建和使用分离。通过一个工厂类来负责创建不同类型的对象,客户端只需要向工厂类请求所需的对象,而不需要知道对象的具体创建过程。这种模式可以降低代码的耦合度,提高代码的可维护性和可扩展性。

假设我们正在开发一个简单的披萨店应用程序,披萨店可以制作不同类型的披萨,如芝士披萨(CheesePizza)、蔬菜披萨(VeggiePizza)等。我们将使用简单工厂模式来实现披萨的创建过程。

  • Pizza 类:是抽象类,包含了披萨的基本方法。
  • CheesePizza 和 VeggiePizza 类:继承自 Pizza 类,实现了具体的披萨制作方法。
  • PizzaFactory 类:负责创建不同类型的披萨对象,与 Pizza 类之间是创建关系。
  • PizzaStore 类:使用 PizzaFactory 类来订购披萨,与 PizzaFactory 类之间是使用关系。

1.Pizza类定义了披萨的基本方法

// 定义披萨抽象类
abstract class Pizza {
    public abstract void prepare();
    public abstract void bake();
    public abstract void cut();
    public abstract void box();
}    

2.CheesePizza 和 VeggiePizza 类 

// 具体的芝士披萨类
class CheesePizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("Preparing Cheese Pizza");
    }

    @Override
    public void bake() {
        System.out.println("Baking Cheese Pizza");
    }

    @Override
    public void cut() {
        System.out.println("Cutting Cheese Pizza");
    }

    @Override
    public void box() {
        System.out.println("Boxing Cheese Pizza");
    }
}  
// 具体的蔬菜披萨类
class VeggiePizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("Preparing Veggie Pizza");
    }

    @Override
    public void bake() {
        System.out.println("Baking Veggie Pizza");
    }

    @Override
    public void cut() {
        System.out.println("Cutting Veggie Pizza");
    }

    @Override
    public void box() {
        System.out.println("Boxing Veggie Pizza");
    }
}      

均继承自 Pizza 抽象类,实现了具体的披萨制作过程。

3.PizzaFactory 类:简单工厂模式的核心 

// 披萨工厂类
class PizzaFactory {
    public static Pizza createPizza(String type) {
        if ("cheese".equalsIgnoreCase(type)) {
            return new CheesePizza();
        } else if ("veggie".equalsIgnoreCase(type)) {
            return new VeggiePizza();
        }
        return null;
    }
}    

 它包含一个静态方法 createPizza,根据传入的披萨类型创建相应的披萨对象。

4.PizzaStore 类:代表披萨店

// 披萨店类
class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza = PizzaFactory.createPizza(type);
        if (pizza != null) {
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } else {
            System.out.println("Sorry, we don't have that type of pizza.");
        }
        return pizza;
    }
}    

 通过调用 PizzaFactory 的 createPizza 方法来订购披萨,并调用披萨的制作方法。

5.简单测试

// 主类,用于测试
public class Main {
    public static void main(String[] args) {
        PizzaStore store = new PizzaStore();
        store.orderPizza("cheese");
        System.out.println();
        store.orderPizza("veggie");
    }
}    

通过简单工厂模式,我们将披萨的创建过程封装在 PizzaFactory 类中,使得 PizzaStore 类只需要关心如何使用披萨,而不需要关心披萨的具体创建过程。这样可以提高代码的可维护性和可扩展性,如果需要添加新的披萨类型,只需要在 PizzaFactory 类中添加相应的创建逻辑即可。

简单工厂模式在一定程度上实现了对象创建和使用的分离,让代码结构更加清晰,维护和扩展也相对方便。但它并非完美无缺,存在一些明显的不足。

从代码的灵活性角度来看,简单工厂模式的工厂类PizzaFactory承担了过多的职责。

createPizza方法中,使用大量的条件判断语句来决定创建哪种披萨对象。一旦需要添加新的披萨类型,就必须修改PizzaFactory类的代码。这违背了软件开发中的开闭原则,即软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。每次新增披萨类型都去修改工厂类,可能会引入新的错误,影响到已有的功能。

从可维护性方面考虑,随着项目规模的扩大,条件判断语句会越来越复杂。在实际应用中,如果有几十种甚至上百种披萨类型,createPizza方法的代码会变得冗长且难以阅读,维护成本急剧上升。

真正的工厂模式(工厂方法模式)则能更好地解决这些问题,它是更符合面向对象编程(OOP)理念的设计模式。

四、工厂模式

工厂模式构建了一个用于创建对象的接口,具体要实例化哪个类则交由子类来确定。简单来说,工厂方法模式把对象创建的任务往后推给了子类,这样做实现了对象创建和使用这两个环节的分离。好处显而易见,代码在维护时会更轻松,而且后期要扩展新功能也会更加容易。​

在简单工厂模式里,有一个工厂类负责创建所有对象,当要添加新对象时,就得修改这个工厂类的代码,可维护性和扩展性欠佳。

而工厂方法模式中,每个具体产品都有对应的工厂子类,创建对象的逻辑分散在各个子类中。新增产品时,只需添加对应的工厂子类,不用改动已有代码,在可维护性和可扩展性上更胜一筹 。

这里我们继续以披萨店为例,不过这次披萨店要在不同的地区开设分店,每个地区的披萨口味有所不同。我们将使用工厂方法模式来实现不同地区披萨的创建过程。 

  • Pizza 类:是抽象类,包含了披萨的基本方法。
  • NYCheesePizza 和 ChicagoCheesePizza 类:继承自 Pizza 类,实现了不同地区风格的芝士披萨的制作方法。
  • PizzaStore 类:是抽象类,定义了订购披萨的方法和抽象的创建披萨方法。
  • NYPizzaStore 和 ChicagoPizzaStore 类:继承自 PizzaStore 类,分别负责创建纽约风格和芝加哥风格的披萨。

 1.Pizza 类与之前一致

2. NYCheesePizza 和 ChicagoCheesePizza 类逻辑一致

 3.PizzaStore类:代表披萨店

// 披萨店抽象类
abstract class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        if (pizza != null) {
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } else {
            System.out.println("Sorry, we don't have that type of pizza.");
        }
        return pizza;
    }

    protected abstract Pizza createPizza(String type);
}    

这里就开始有所不同了,简单工厂模式只有一个具体的工厂类,负责所有产品的创建。而工厂方法模式引入了抽象的PizzaStore类,定义了抽象的createPizza方法,将创建产品的逻辑延迟到具体的子类中实现。

4.PizzaStore子类实现

// 芝加哥披萨店类
class ChicagoPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if ("cheese".equalsIgnoreCase(type)) {
            return new ChicagoCheesePizza();
        }
        return null;
    }
}    
// 纽约披萨店类
class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if ("cheese".equalsIgnoreCase(type)) {
            return new NYCheesePizza();
        }
        return null;
    }
}    

工厂方法模式有多个具体的工厂子类(NYPizzaStoreChicagoPizzaStore),每个子类负责创建特定风格的披萨。当需要新增一种地区风格的披萨店时,只需创建一个新的具体工厂子类,实现createPizza方法,而无需修改现有的工厂类代码,符合开闭原则。

5.测试

// 主类,用于测试
public class Main {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        nyStore.orderPizza("cheese");

        System.out.println();

        PizzaStore chicagoStore = new ChicagoPizzaStore();
        chicagoStore.orderPizza("cheese");
    }
}    

在使用工厂方法模式时,要始终遵循开闭原则进行扩展。当有新的产品类型或新的工厂需求时,优先通过创建新的具体产品类和具体工厂子类来实现,避免修改现有的抽象工厂类和具体工厂子类的代码。这样可以保证系统的稳定性和可维护性,降低引入新错误的风险。

尽管工厂方法模式在扩展性上优于简单工厂模式,但它也存在一定的局限性。工厂方法模式主要关注单一产品等级结构的创建,即每个工厂子类只负责创建一种类型的产品。

然而,在实际的软件开发中,我们经常会遇到需要创建一系列相关产品的情况。例如,披萨店除了制作不同风格的披萨,还需要搭配不同的饮料和小吃,并且不同地区的披萨店提供的饮料和小吃组合也不同。

在这种情况下,工厂方法模式就显得力不从心了,因为它无法方便地处理多个产品等级结构的创建。抽象工厂模式则可以很好地解决这个问题。这就涉及到多个相关产品的创建,即产品族的概念

五、抽象工厂模式 

抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。通过抽象工厂模式,我们可以轻松地创建一组相关的产品,并且可以在不同的工厂子类中实现不同的产品组合。

  • 与简单工厂模式的 UML 图相比,抽象工厂模式的 UML 图更加复杂,包含了多个产品等级结构(披萨和饮料)以及抽象工厂类和具体工厂子类。简单工厂模式通常只有一个工厂类与多个产品类相连。
  • 与工厂方法模式的 UML 图相比,抽象工厂模式不仅有多个具体工厂子类负责创建不同风格的产品,而且每个具体工厂子类可以创建多个相关产品(产品族),而工厂方法模式主要处理单一产品等级结构。

1. Pizza 抽象类与前面一致

2. NYCheesePizza 类与ChicagoCheesePizza 类

也与之前一致,具体产品类,继承自 Pizza 抽象类 

3.Drink 抽象类 

// 定义饮料抽象类
abstract class Drink {
    public abstract void serve();
}

这是另一个抽象产品类,在抽象工厂模式里代表饮料这一产品等级结构。它定义了饮料的基本方法 serve,表示提供饮料的操作。不同类型的具体饮料类需要继承该抽象类并实现这个方法。 

4.Cola 类和OrangeJuice 类 

// 可乐类,作为纽约风格搭配的饮料
class Cola extends Drink {
    @Override
    public void serve() {
        System.out.println("Serving Cola");
    }
}
// 橙汁类,作为芝加哥风格搭配的饮料
class OrangeJuice extends Drink {
    @Override
    public void serve() {
        System.out.println("Serving Orange Juice");
    }
}

 均属于具体产品类,继承自 Drink 抽象类。

5.PizzaFactory 抽象类

// 抽象披萨工厂类
abstract class PizzaFactory {
    public abstract Pizza createPizza(String type);
    public abstract Drink createDrink();
}

抽象工厂类,是抽象工厂模式的核心。它定义了创建披萨和饮料这两个相关产品的抽象方法,具体的工厂子类需要实现这些方法来创建特定风格的披萨和饮料组合。通过这种方式,抽象工厂模式保证了所创建的产品之间的相关性和一致性。

6.NYPizzaFactory 类与 ChicagoPizzaFactory 类

// 纽约披萨工厂类
class NYPizzaFactory extends PizzaFactory {
    @Override
    public Pizza createPizza(String type) {
        if ("cheese".equalsIgnoreCase(type)) {
            return new NYCheesePizza();
        }
        return null;
    }

    @Override
    public Drink createDrink() {
        return new Cola();
    }
}
// 芝加哥披萨工厂类
class ChicagoPizzaFactory extends PizzaFactory {
    @Override
    public Pizza createPizza(String type) {
        if ("cheese".equalsIgnoreCase(type)) {
            return new ChicagoCheesePizza();
        }
        return null;
    }

    @Override
    public Drink createDrink() {
        return new OrangeJuice();
    }
}

具体工厂类,继承自 PizzaFactory 抽象类。它实现了创建产品的具体逻辑,在抽象工厂模式中,具体工厂负责创建具体的产品族,NYPizzaFactory 就负责创建纽约风格的披萨 - 可乐产品族。

7.PizzaStore 类 

// 披萨店类
class PizzaStore {
    private PizzaFactory factory;

    public PizzaStore(PizzaFactory factory) {
        this.factory = factory;
    }

    public void orderMeal(String pizzaType) {
        Pizza pizza = factory.createPizza(pizzaType);
        Drink drink = factory.createDrink();

        if (pizza != null) {
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } else {
            System.out.println("Sorry, we don't have that type of pizza.");
        }

        drink.serve();
    }
}

这是客户端类,依赖于抽象工厂 PizzaFactory。在其构造函数中传入具体的工厂对象,在 orderMeal 方法里,调用传入工厂的 createPizza 和 createDrink 方法来创建披萨和饮料,然后执行披萨的制作流程和饮料的提供操作。通过这种方式,客户端与具体的产品创建过程解耦,只需要与抽象工厂交互,提高了代码的可维护性和可扩展性。

8.测试

// 主类,用于测试
public class Main {
    public static void main(String[] args) {
        // 创建纽约披萨工厂
        PizzaFactory nyFactory = new NYPizzaFactory();
        PizzaStore nyStore = new PizzaStore(nyFactory);
        nyStore.orderMeal("cheese");

        System.out.println();

        // 创建芝加哥披萨工厂
        PizzaFactory chicagoFactory = new ChicagoPizzaFactory();
        PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
        chicagoStore.orderMeal("cheese");
    }
}

这里我们创建了纽约风格和芝加哥风格的披萨工厂对象,然后分别创建对应的披萨店对象,调用 orderMeal 方法来订购芝士披萨套餐,不同的产品就可以通过抽象工厂完成需求了。

但在此需要注意,抽象工厂模式由于涉及到创建多个相关产品对象,在性能方面可能会面临挑战。比如在创建产品族时,可能会涉及大量对象的初始化、资源的加载等操作,若处理不当,容易造成性能瓶颈。所以在使用抽象工厂模式时,要充分考量性能因素,比如可以考虑使用单例模式管理工厂实例

六、工厂模式选择与避坑

在实际编程中,选择合适的工厂模式至关重要。我们可以借助如下决策树来理清思路:​

当我们面临创建对象的需求时,首先判断对象类型是否单一。若对象类型单一,简单工厂模式是不错的选择,它能通过一个工厂类创建特定类型的对象,实现基本的对象创建封装。

若对象类型不单一,进一步思考是否需要产品族支持。如果需要,抽象工厂模式可以创建一系列相关产品对象,适合复杂的产品族场景。

若不需要产品族支持,工厂方法模式能将对象创建逻辑延迟到具体的子类工厂中,增强了扩展性和灵活性。

但是也需要注意,在使用工厂模式时,我们需要注意以下常见陷阱,并采取正确的姿势来规避:​

陷阱现象​

正确姿势​

工厂类变成上帝类​

遵循单一职责原则拆分工厂,让每个工厂类只负责创建特定类型或相关类型的对象,避免一个工厂类承担过多的创建职责​

参数爆炸的 create 方法​

使用 Builder 模式包装参数,将复杂的参数组合通过 Builder 对象进行构建,使得创建方法的参数更加简洁明了​

过度抽象导致复杂度​

遵循 YAGNI(You Aren't Gonna Need It)原则,保持适度简单。不要过度抽象工厂模式,只在必要时进行抽象,避免引入不必要的复杂性​

七、总结

“好的工厂不是生产最多的对象,而是让对象的生产变得透明”,这句话道出了工厂模式的精髓。软件开发中,高效获取对象才是重点。

有了工厂模式,依赖对象的模块只需与工厂交互,如同车间从物资部门拿取零部件,无需关心生产细节。这一解耦方式让稳定的业务逻辑免受冲击,提升了系统的可维护性与扩展性。

希望阅读这篇文章后,大家能将这种设计模式更好的运用到开发工作中~

往期相关文章

每天认识一个设计模式 - 单例模式:独一无二的对象管家

每天认识一个设计模式 - 代理模式:程序世界的 “代言人”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深情不及里子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值