抽象工厂模式:产品族与产品等级
抽象工厂模式,说白了就是解决「一系列相关对象」的创建问题。我刚开始接触设计模式时,觉得工厂方法已经够用了,直到遇到一个跨平台UI库的需求——Windows下的按钮、文本框、下拉框是一套,Mac下的又是另一套。这时候我才意识到,光有工厂方法还不够。
产品族与产品等级
先理清两个概念:产品等级和产品族。这俩词听着抽象,我举个例子你就明白了。
假设我们在做一个游戏,里面有不同风格的UI主题:
- 产品等级:按钮、文本框、下拉框——这是「同一类产品」的不同变体
- 产品族:暗黑风格、明亮风格、赛博朋克风格——这是「同一套风格」下的不同产品
你看,按钮这个产品等级,在暗黑风格下是黑色按钮,在明亮风格下是白色按钮。而暗黑风格这个产品族,包含了暗黑按钮、暗黑文本框、暗黑下拉框。
核心要点:抽象工厂解决的是「产品族」的创建问题,工厂方法解决的是「产品等级」的创建问题。
我在项目中遇到过这样一个场景:一个报表系统需要支持PDF导出和Excel导出。PDF导出需要PDFHeader、PDFBody、PDFFooter;Excel导出需要ExcelHeader、ExcelBody、ExcelFooter。这就是典型的产品族问题——每个导出格式就是一个产品族,而Header、Body、Footer就是产品等级。
抽象工厂结构
抽象工厂的结构其实不复杂,我画个图你就明白了。
从图上你能看到:抽象工厂定义了一组创建方法,每个具体工厂负责生产一个完整的产品族。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框架里到处都是抽象工厂的影子。最典型的就是BeanFactory和ApplicationContext。
你想想看,BeanFactory就是一个抽象工厂,它定义了获取Bean的接口。而ClassPathXmlApplicationContext、AnnotationConfigApplicationContext这些就是具体工厂,它们各自用不同的方式创建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