抽象工厂模式:产品族与产品等级

抽象工厂模式,说白了就是解决「一系列相关对象」的创建问题。我刚开始接触设计模式时,觉得工厂方法已经够用了,直到遇到一个跨平台UI库的需求——Windows下的按钮、文本框、下拉框是一套,Mac下的又是另一套。这时候我才意识到,光有工厂方法还不够。

产品族与产品等级

先理清两个概念:产品等级产品族。这俩词听着抽象,我举个例子你就明白了。

假设我们在做一个游戏,里面有不同风格的UI主题:

  • 产品等级:按钮、文本框、下拉框——这是「同一类产品」的不同变体
  • 产品族:暗黑风格、明亮风格、赛博朋克风格——这是「同一套风格」下的不同产品

你看,按钮这个产品等级,在暗黑风格下是黑色按钮,在明亮风格下是白色按钮。而暗黑风格这个产品族,包含了暗黑按钮、暗黑文本框、暗黑下拉框。

核心要点:抽象工厂解决的是「产品族」的创建问题,工厂方法解决的是「产品等级」的创建问题。

我在项目中遇到过这样一个场景:一个报表系统需要支持PDF导出和Excel导出。PDF导出需要PDFHeader、PDFBody、PDFFooter;Excel导出需要ExcelHeader、ExcelBody、ExcelFooter。这就是典型的产品族问题——每个导出格式就是一个产品族,而Header、Body、Footer就是产品等级。

抽象工厂结构

抽象工厂的结构其实不复杂,我画个图你就明白了。

AbstractFactory +createProductA() +createProductB() ConcreteFactory1 +createProductA() +createProductB() ConcreteFactory2 +createProductA() +createProductB() AbstractProductA AbstractProductB ProductA1 ProductA2 ProductB1 ProductB2 产品族1 产品族2

从图上你能看到:抽象工厂定义了一组创建方法,每个具体工厂负责生产一个完整的产品族。ConcreteFactory1生产的是ProductA1和ProductB1,它们属于同一个产品族;ConcreteFactory2生产的是ProductA2和ProductB2,属于另一个产品族。

代码实现上,抽象工厂通常是一个接口或抽象类:

// 抽象工厂
public interface UIFactory {
    Button createButton();
    TextField createTextField();
    CheckBox createCheckBox();
}

// 具体工厂 - 暗黑风格
public class DarkUIFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new DarkButton();
    }
    @Override
    public TextField createTextField() {
        return new DarkTextField();
    }
    @Override
    public CheckBox createCheckBox() {
        return new DarkCheckBox();
    }
}

// 具体工厂 - 明亮风格
public class LightUIFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new LightButton();
    }
    @Override
    public TextField createTextField() {
        return new LightTextField();
    }
    @Override
    public CheckBox createCheckBox() {
        return new LightCheckBox();
    }
}

个人经验:抽象工厂的接口设计要「稳」。一旦发布,新增产品等级(比如加个Slider)就需要修改所有具体工厂。我习惯在接口设计阶段多花点时间,把未来可能扩展的产品等级都考虑进去。

Spring中的抽象工厂

Spring框架里到处都是抽象工厂的影子。最典型的就是BeanFactoryApplicationContext

你想想看,BeanFactory就是一个抽象工厂,它定义了获取Bean的接口。而ClassPathXmlApplicationContextAnnotationConfigApplicationContext这些就是具体工厂,它们各自用不同的方式创建Bean实例。

// 抽象工厂
public interface BeanFactory {
    Object getBean(String name);
    <T> T getBean(Class<T> requiredType);
    boolean containsBean(String name);
    // ... 其他方法
}

// 具体工厂 - XML配置
public class ClassPathXmlApplicationContext implements BeanFactory {
    @Override
    public Object getBean(String name) {
        // 从XML配置中读取Bean定义并创建
        return createBeanFromXml(name);
    }
}

// 具体工厂 - 注解配置
public class AnnotationConfigApplicationContext implements BeanFactory {
    @Override
    public Object getBean(String name) {
        // 从注解中读取Bean定义并创建
        return createBeanFromAnnotation(name);
    }
}

我记得有一次排查Spring启动慢的问题,发现就是因为某个具体工厂实现里做了大量不必要的初始化。抽象工厂的好处就在这里——你可以替换具体工厂而不影响客户端代码。

避坑指南:我曾经在项目中过度使用抽象工厂,把每个产品族都抽象化,结果导致工厂类爆炸。抽象工厂适合产品族数量稳定、产品等级也稳定的场景。如果产品族经常变化,或者产品等级经常增减,抽象工厂反而会成为负担。

Spring的FactoryBean接口也是一个很好的例子。它允许你自定义Bean的创建逻辑,本质上就是抽象工厂模式的一种变体。当你需要创建复杂对象时,实现FactoryBean接口,Spring容器就会自动调用你的工厂方法。

public class MyComplexBeanFactory implements FactoryBean<MyComplexBean> {
    @Override
    public MyComplexBean getObject() throws Exception {
        // 复杂的创建逻辑
        MyComplexBean bean = new MyComplexBean();
        bean.setupComplexDependencies();
        bean.initializeExpensiveResources();
        return bean;
    }
    
    @Override
    public Class<?> getObjectType() {
        return MyComplexBean.class;
    }
    
    @Override
    public boolean isSingleton() {
        return true;
    }
}

抽象工厂模式的核心价值在于「保证产品族内对象的一致性」。你从暗黑工厂拿到的按钮、文本框、复选框,它们风格统一,不会出现暗黑按钮配明亮文本框这种尴尬情况。

嗯,最后说一句:抽象工厂不是银弹。我见过不少团队一上来就搞抽象工厂,结果项目迭代半年后,产品族和产品等级都变了,抽象工厂反而成了重构的障碍。设计模式的选择,永远要基于你对业务变化的预判。


公众号:蓝海资料掘金营,微信deep3321