尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

工厂模式深度解析:从设计原理到Spring实战应用

工厂模式深度解析:从设计原理到Spring实战应用 1. 面试官为什么总爱问工厂模式如果你参加过几次Java或后端开发的面试大概率会被问到设计模式而工厂模式几乎是必考题。为什么面试官对它情有独钟我面过不少人也被人面过总结下来就三点第一它太常用了从JDK到Spring再到各种中间件工厂模式无处不在是考察候选人是否具备“读源码”能力的基础门槛。第二它理解门槛适中不像抽象工厂那么绕也不像单例那么简单正好能区分出“背概念”和“真理解”的候选人。第三它能引出很多延伸问题比如和Spring IoC容器的关系、各种工厂变体的区别、设计原则的体现是一个绝佳的“话题起点”。很多朋友在准备时往往只记住了“简单工厂、工厂方法、抽象工厂”这三个名词和UML图但一到追问细节就露怯。比如Spring的BeanFactory是哪种工厂为什么Calendar.getInstance()也被称为工厂工厂模式和建造者模式到底怎么选这篇文章我就结合自己十多年的开发、面试和带团队的经验把工厂模式从“面试八股”还原成“工程利器”让你不仅能在面试中对答如流更能真正在项目中用得恰到好处。2. 从“new”的困境到工厂的诞生一个真实场景的推演要理解工厂模式我们得先回到问题的源头为什么我们不全用new来创建对象直接new不是最简单吗我们来看一个我早期在电商项目中遇到的真实案例。当时我们需要对接多个第三方物流公司比如顺丰、中通、圆通来计算运费。最初代码长这样public class ShippingService { public BigDecimal calculateFee(String logisticsCompany, String orderId) { LogisticsCalculator calculator null; if (SF.equals(logisticsCompany)) { calculator new SfLogisticsCalculator(); } else if (ZTO.equals(logisticsCompany)) { calculator new ZtoLogisticsCalculator(); } else if (YTO.equals(logisticsCompany)) { calculator new YtoLogisticsCalculator(); } // ... 后续计算逻辑 return calculator.calculate(orderId); } }这段代码在只有两三家物流公司时还能忍受。但很快问题接踵而至违反开闭原则每增加一家新物流公司比如极兔我都要修改这个if-else堡垒ShippingService这个核心业务类被频繁改动风险极高。职责过重ShippingService既要负责核心的运费计算逻辑又要负责具体物流计算器的创建违反了单一职责原则。难以测试我想单元测试ShippingService的计算逻辑但因为它内部直接new了具体的物流计算器导致我无法轻松地注入一个Mock对象进行隔离测试。代码重复项目其他地方比如订单状态跟踪模块也需要根据物流公司创建对应的跟踪器对象又得把这段if-else复制一遍。这就是“new”的困境创建逻辑与使用逻辑强耦合。工厂模式要解决的正是将对象的创建过程封装起来让使用方Client无需关心对象的具体实现类只需通过一个统一的“工厂”接口来获取对象。注意这里的关键转变是思维上的——从“我知道我要创建什么具体类”转变为“我知道我需要一个具备某种能力的对象至于它具体是谁由工厂告诉我”。这是理解所有工厂变体的基础。3. 简单工厂快速解耦的实用主义方案面对上面的问题最直接的改进就是引入一个专门的类来负责创建LogisticsCalculator对象。这个类就是简单工厂Simple Factory它也叫静态工厂因为其创建方法通常是静态的。我们把创建逻辑从ShippingService中抽离出来// 简单工厂类 public class LogisticsCalculatorFactory { public static LogisticsCalculator createCalculator(String company) { if (SF.equals(company)) { return new SfLogisticsCalculator(); } else if (ZTO.equals(company)) { return new ZtoLogisticsCalculator(); } else if (YTO.equals(company)) { return new YtoLogisticsCalculator(); } throw new IllegalArgumentException(Unsupported logistics company: company); } } // 使用方变得非常简洁 public class ShippingService { public BigDecimal calculateFee(String logisticsCompany, String orderId) { LogisticsCalculator calculator LogisticsCalculatorFactory.createCalculator(logisticsCompany); return calculator.calculate(orderId); } }简单工厂的核心价值与面试要点价值实现了对象的创建和使用分离ShippingService现在只依赖工厂和抽象接口不再依赖任何具体实现类上面的问题2、3、4都得到了缓解。面试常问“简单工厂是23种设计模式之一吗”答案是否定的。在GoF的经典著作中并没有“简单工厂”这个独立模式它更像是一种编程习惯或技巧。但这不影响它的广泛使用和考察价值。优点结构简单易于理解对于创建逻辑不复杂、产品类型固定的场景它是性价比最高的解耦方案。缺点它并没有完全解决开闭原则的问题。新增产品类型如极兔时我们仍然需要修改LogisticsCalculatorFactory类的createCalculator方法。只不过修改点从业务类集中到了工厂类破坏性降低了。实操心得简单工厂非常适合在项目初期或内部工具类中使用。例如JDK中的Calendar.getInstance()、NumberFormat.getNumberInstance()都是简单工厂的体现。它们根据本地化环境等参数返回不同的具体实现。在面试中能举出这些JDK中的例子会让面试官觉得你有很好的知识迁移能力。4. 工厂方法模式将“选择权”下放彻底拥抱扩展当产品家族变得庞大或者我们希望将产品的创建延迟到子类时简单工厂的集中式处理就会显得笨重。这时工厂方法模式Factory Method Pattern就该登场了。它的定义是定义一个用于创建对象的接口但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。听起来有点绕我们继续用物流的例子改造。假设现在业务扩展了我们不仅有“国内物流”还有“跨境物流”两者的计算器创建逻辑和依赖的资源完全不同。强行用一个简单工厂会使得方法内部if-else膨胀且难以维护。工厂方法模式的实现抽象产品LogisticsCalculator不变。具体产品SfCalculatorZtoCalculator等不变。抽象工厂定义一个创建产品的抽象方法。具体工厂每个具体工厂负责创建一种具体产品。// 1. 抽象工厂接口 public interface LogisticsCalculatorFactory { LogisticsCalculator createCalculator(); } // 2. 具体工厂类每个负责创建一种产品 public class SfCalculatorFactory implements LogisticsCalculatorFactory { Override public LogisticsCalculator createCalculator() { // 这里可以包含复杂的SF计算器初始化逻辑比如加载专属配置、建立连接池等 return new SfLogisticsCalculator(); } } public class ZtoCalculatorFactory implements LogisticsCalculatorFactory { Override public LogisticsCalculator createCalculator() { // ZTO的特定初始化 return new ZtoLogisticsCalculator(); } } // 3. 使用方现在它依赖工厂接口 public class DomesticShippingService { private LogisticsCalculatorFactory factory; // 通过构造器或Setter注入工厂 public DomesticShippingService(LogisticsCalculatorFactory factory) { this.factory factory; } public BigDecimal calculateFee(String orderId) { // 使用工厂创建产品完全不知道具体产品类 LogisticsCalculator calculator factory.createCalculator(); return calculator.calculate(orderId); } } // 4. 客户端代码决定使用哪个工厂通常由Spring等容器完成 public class Client { public static void main(String[] args) { // 创建顺丰工厂 LogisticsCalculatorFactory factory new SfCalculatorFactory(); // 将工厂注入服务 DomesticShippingService service new DomesticShippingService(factory); service.calculateFee(order123); } }工厂方法模式的核心价值与面试要点价值完美符合开闭原则。现在要新增一个“极兔”计算器我只需要新增JituLogisticsCalculator和JituCalculatorFactory两个类。原有的SfCalculatorFactory、ZtoCalculatorFactory和DomesticShippingService都不需要做任何修改。系统的可扩展性得到了质的提升。与简单工厂的对比这是面试高频题。简单工厂把创建逻辑放在一个类里做集中判断参数化创建工厂方法则是将创建逻辑分散到各个平行的工厂子类中由客户端决定使用哪个子类。工厂方法更符合“单一职责”和“开闭原则”。Spring中的体现Spring Framework的BeanFactory本身就是工厂模式思想的集大成者。而更具体的ApplicationContext可以看作是BeanFactory的增强版工厂。当我们使用Bean注解在一个配置类中定义方法时这个方法就是一个“工厂方法”Spring会调用它来创建和返回Bean的实例。踩坑实录我曾见过有团队过度使用工厂方法为每一个简单的、无状态的DAO或Service都创建一个对应的工厂接口和实现类导致项目里工厂类爆炸维护成本陡增。切记设计模式是工具不是信仰。如果产品的创建逻辑非常简单就是一个简单的new且未来不太可能变化或扩展直接new或者用简单工厂是更务实的选择。工厂方法模式适用于“产品创建过程复杂且不同产品创建逻辑差异大”的场景。5. 抽象工厂模式应对“产品家族”的复杂性工厂方法模式处理的是单一产品等级结构的创建问题。但如果我们的系统需要创建多个相互关联或依赖的产品对象即一个“产品家族”工厂方法就显得力不从心了。这时就需要抽象工厂模式Abstract Factory Pattern。举个例子我们上面的物流系统需要升级。一个完整的物流方案不仅包括“运费计算器”Calculator还包括“物流面单生成器”WaybillGenerator和“物流状态追踪器”Tracker。并且顺丰、中通、圆通各自都有自己的一套实现。我们不可能让客户端去分别创建顺丰的计算器、中通的面单生成器这会导致对象不匹配。抽象工厂模式的定义提供一个接口用于创建相关或依赖对象的家族而无需指定它们具体的类。// ---------- 抽象产品族 ---------- public interface Calculator { /* ... */ } public interface WaybillGenerator { /* ... */ } public interface Tracker { /* ... */ } // ---------- 抽象工厂 ---------- public interface LogisticsFactory { Calculator createCalculator(); WaybillGenerator createWaybillGenerator(); Tracker createTracker(); } // ---------- 具体产品族顺丰套件 ---------- public class SfCalculator implements Calculator { /* ... */ } public class SfWaybillGenerator implements WaybillGenerator { /* ... */ } public class SfTracker implements Tracker { /* ... */ } // ---------- 具体工厂顺丰工厂 ---------- public class SfLogisticsFactory implements LogisticsFactory { Override public Calculator createCalculator() { return new SfCalculator(); } Override public WaybillGenerator createWaybillGenerator() { return new SfWaybillGenerator(); } Override public Tracker createTracker() { return new SfTracker(); } } // ---------- 具体产品族中通套件 ---------- 类似略 // ---------- 客户端使用 ---------- public class LogisticsClient { private Calculator calculator; private WaybillGenerator generator; private Tracker tracker; public LogisticsClient(LogisticsFactory factory) { // 通过一个工厂创建出一套兼容的产品对象 this.calculator factory.createCalculator(); this.generator factory.createWaybillGenerator(); this.tracker factory.createTracker(); } public void processOrder(String order) { BigDecimal fee calculator.calculate(order); String waybill generator.generate(order); tracker.track(waybill); // ... 使用这套兼容的对象协同工作 } }抽象工厂模式的核心价值与面试要点价值保证产品家族的兼容性。客户端代码LogisticsClient只与抽象工厂和抽象产品交互。它不需要知道创建的是顺丰套件还是中通套件但它能确保得到的计算器、面单生成器和追踪器一定是来自同一家物流公司可以无缝协作。这对于需要“成套”使用对象的UI主题切换、跨平台应用开发如一套代码生成Windows和Mac控件等场景至关重要。与工厂方法的对比这是另一个面试必问题。工厂方法关注单一产品的创建而抽象工厂关注系列产品的创建。抽象工厂的接口里通常有多个工厂方法每个方法创建一个不同类型的产品。可以理解为抽象工厂是工厂方法模式的升级版用于更复杂的对象创建场景。缺点难以支持新种类的产品。如果要在产品家族中增加一个新成员比如新增一个InsuranceProvider保险提供者就需要修改LogisticsFactory接口以及所有它的实现类SfLogisticsFactoryZtoLogisticsFactory等这违反了开闭原则。因此抽象工厂模式适用于产品家族结构稳定不会频繁新增产品种类的场景。个人经验在业务系统中纯正的抽象工厂模式使用频率低于工厂方法。但在框架和中间件层面它非常常见。例如在Java数据库连接中java.sql.Connection就是一个抽象工厂它可以创建StatementPreparedStatementCallableStatement等一套相关的数据库操作对象而具体是哪个数据库的实现MySQL PostgreSQL由驱动决定。6. 面试深度追问与实战辨析掌握了三种工厂的基本形态面试官通常会从两个方向深入追问1. 与其他模式的对比2. 在主流框架中的应用。6.1 工厂模式 vs. 建造者模式这也是一个高频混淆点。两者都用于创建对象但侧重点不同。工厂模式关注的是对象的整体创建尤其是将创建过程封装起来隐藏具体类型。客户端说“我要一个A类型对象”工厂就给一个创建好的、完整的A对象。产品通常是“标准品”。建造者模式关注的是对象的组装过程特别是当对象构造过程复杂且需要分步骤、个性化配置时。客户端通过指挥者Director或直接使用建造者一步步设置属性最后构建出一个对象。产品通常是“定制品”。一个简单类比工厂模式去麦当劳点一个“巨无霸套餐”后厨工厂按标准流程做好整套给你。建造者模式去Subway点一个三明治你告诉店员建造者要全麦面包、加烤鸡胸肉、不要洋葱、多加生菜、配蛋黄酱……最后得到你定制的那一个。在代码中如果对象的构造参数很多且可选或者构造过程有严格的顺序要求建造者模式尤其是Lombok的Builder比工厂模式更合适。6.2 Spring框架中的工厂模式Spring的核心——IoC容器本质上就是一个超级工厂。它管理的BeanFactory和ApplicationContext是工厂模式的终极实践。BeanFactory是Spring容器的基础接口定义了最基本的工厂方法getBean()这就是一个工厂方法模式的体现。ApplicationContext作为BeanFactory的子接口是更高级的容器它在工厂方法的基础上集成了更多企业级功能事件、国际化等。FactoryBean这是一个特殊的接口。如果一个Bean实现了FactoryBean那么从容器中获取这个Bean时得到的不是它本身而是它getObject()方法返回的对象。这是一种更灵活的工厂模式常用于集成第三方库如MyBatis的SqlSessionFactoryBean或创建复杂对象。静态工厂方法 实例工厂方法在Spring XML配置或Java Config (Configuration)中你可以指定一个静态方法或一个Bean的实例方法来创建另一个Bean这分别是简单工厂和工厂方法模式在Spring配置中的直接应用。在面试中如果能主动将Spring的IoC原理和工厂模式联系起来并清晰地说出BeanFactory、FactoryBean的区别绝对是加分项。6.3 简单工厂的“配置化”升级在实际项目中我们如何让简单工厂也能符合开闭原则答案是结合反射和配置。这是很多中高级面试会问到的实践。我们可以将“物流公司编码”与“具体计算器类名”的映射关系放到配置文件如properties YAML或数据库中。工厂类读取配置利用反射动态创建实例。// config.properties sfcom.xxx.logistics.SfLogisticsCalculator ztocom.xxx.logistics.ZtoLogisticsCalculator ytocom.xxx.logistics.YtoLogisticsCalculator // 升级版的简单工厂 public class LogisticsCalculatorFactory { private static MapString, Class? extends LogisticsCalculator cachedMap new HashMap(); static { // 启动时加载配置初始化映射缓存 Properties props loadProperties(); for (String key : props.stringPropertyNames()) { String className props.getProperty(key); Class? clazz Class.forName(className); cachedMap.put(key, clazz.asSubclass(LogisticsCalculator.class)); } } public static LogisticsCalculator createCalculator(String company) { Class? extends LogisticsCalculator clazz cachedMap.get(company); if (clazz null) { throw new IllegalArgumentException(Unsupported company: company); } try { // 通过反射创建实例这里假设都有无参构造 return clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(Failed to create calculator for company, e); } } }这样新增物流公司时我们只需要在配置文件中加一行映射然后新增对应的计算器类工厂类代码无需重新编译和部署。这本质上是将变化转移到了外部配置是一种非常实用的“开闭原则”实现方式。当然它牺牲了编译期的类型安全检查需要良好的异常处理和类加载机制来保障。7. 如何在项目中正确选用与落地理论懂了面试能说了但回到日常开发到底该怎么用我总结了一个简单的决策流对象创建是否简单且唯一如果是直接new。别为了模式而模式。是否需要隔离创建逻辑方便测试和替换如果是引入简单工厂。这是性价比最高的解耦。未来是否会频繁增加新的产品类型且各产品创建逻辑复杂如果是使用工厂方法模式。将变化封装在各自的具体工厂里。是否需要创建一整套相互关联的产品对象产品族如果是考虑抽象工厂模式。产品的构建是否需要复杂的、分步骤的装配过程如果是考虑建造者模式。落地时的注意事项命名规范工厂类名最好以Factory结尾方法名常用createXXX()getInstance()makeXXX()等保持团队统一。依赖注入在现代Spring Boot项目中工厂本身也常常被注册为Bean通过Autowired注入到使用方。具体工厂的实现可以通过ConditionalOnProperty等条件注解来动态选择这比在代码中写if-else或switch更优雅。不要滥用如果一个工厂类里面只有一个if-else创建两个对象并且三年都没变过那么它可能带来的抽象收益小于其增加的复杂度。时刻权衡模式的收益与成本。工厂模式不是银弹但它为我们提供了一种至关重要的设计思维依赖倒置。高层模块不应该依赖低层模块二者都应该依赖其抽象。工厂模式正是通过依赖抽象接口将高层模块业务逻辑从低层模块具体对象创建的泥潭中解放出来的经典实践。理解它用好它你的代码离“高内聚、低耦合”的目标就更近了一步。
返回列表