Java工厂模式应用实战:3个项目案例掌握解耦与复用技巧

Java工厂模式应用实战:3个项目案例掌握解耦与复用技巧 一

文章目录CloseOpen

先搞懂:工厂模式到底解决什么问题?

很多人学设计模式总觉得“飘在空中”,背了定义却不知道什么时候用。其实工厂模式的核心特别简单:帮你把“创建对象”和“使用对象”分开。就像你去餐厅吃饭,不用自己买菜做饭(创建对象),告诉服务员要什么(调用工厂),服务员给你做好的菜(获取对象)。这样你(客户端)只关心怎么吃(使用对象),不用关心菜怎么做的(创建过程)。

三种工厂模式:别被名字吓住,其实很好区分

很多资料把工厂模式分成“简单工厂”“工厂方法”“抽象工厂”,听着复杂,其实就像手机套餐:基础版、进阶版、豪华版,按需选择就行。我画了个表格,你一看就明白:

模式类型 核心思路 适合场景 代码复杂度
简单工厂 一个工厂类搞定所有对象创建 对象类型少、变化不频繁(如支付渠道) ★☆☆☆☆
工厂方法 一个对象类型对应一个工厂 对象类型多、需要灵活扩展(如日志框架) ★★☆☆☆
抽象工厂 一个工厂创建一整套相关对象 需要创建“产品族”(如主题组件库) ★★★☆☆

记住一个原则

:别一上来就用最复杂的。我见过实习生写个工具类都要用抽象工厂,结果代码量翻倍,维护时没人看得懂。《设计模式:可复用面向对象软件的基础》里早就说过:“设计模式的核心是解决特定场景的问题,不是炫技。” 先从简单工厂用起,不够用了再升级,这才是务实的做法。

三个案例:从支付到插件,工厂模式怎么落地?

案例1:支付系统——用简单工厂干掉“if-else地狱”

先说个我自己踩过的坑。2021年做生鲜电商项目,初期支付渠道只有支付宝,代码大概长这样:

// 伪代码,真实项目比这乱10倍

public class PaymentService {

public void pay(String payType, BigDecimal amount) {

if ("ALIPAY".equals(payType)) {

// 支付宝支付逻辑:调SDK、验签、回调处理...

} else if ("WECHAT".equals(payType)) { // 后来加的微信支付

// 微信支付逻辑:不同的SDK、不同的参数...

} else if ("UNIONPAY".equals(payType)) { // 再后来加的银联

// 银联支付逻辑:又一套新代码...

}

// 每次加新渠道,这里就多一堆if-else

}

}

当时接银联支付时,我改了三天——既要加新的if分支,又要改订单状态流转,还得动退款逻辑。上线后发现漏改了“支付失败重试”的逻辑,导致银联支付失败后系统不重试,用户疯狂投诉。后来重构时,我们用简单工厂彻底解决了这个问题。

具体怎么做?分三步:

  • 定义统一接口:把所有支付渠道的共同行为(支付、退款、查询)抽成Payment接口:
  • public interface Payment {
    

    void pay(BigDecimal amount);

    void refund(BigDecimal amount);

    String queryStatus(String orderId);

    }

  • 每个渠道写实现类:比如AlipayPaymentWechatPayment,各自实现接口方法,把原来散落在if-else里的逻辑搬进去。
  • 建个简单工厂类:负责根据支付类型创建对应的Payment对象:
  • public class PaymentFactory {
    

    public static Payment createPayment(String payType) {

    if ("ALIPAY".equals(payType)) {

    return new AlipayPayment();

    } else if ("WECHAT".equals(payType)) {

    return new WechatPayment();

    } else if ("UNIONPAY".equals(payType)) {

    return new UnionpayPayment();

    }

    throw new IllegalArgumentException("不支持的支付类型");

    }

    }

    改完后,PaymentService瞬间清爽了:

    public class PaymentService {
    

    public void pay(String payType, BigDecimal amount) {

    Payment payment = PaymentFactory.createPayment(payType);

    payment.pay(amount); // 直接调用接口方法,不用管具体哪个渠道

    }

    }

    关键效果

    :后来接Apple Pay时,我让实习生半天就搞定了——只需要加一个ApplePayPayment实现类,再在工厂里加一行if ("APPLEPAY".equals(payType))。原来的PaymentService、订单模块、退款模块一行代码没动,测试只用测新的实现类,上线稳得一批。

    案例2:日志框架——工厂方法让“策略切换”更灵活

    去年帮朋友的SaaS项目做架构优化,他们的日志系统是个灾难:开发环境要输出到控制台,生产环境要写文件,大客户还要求对接ELK。原来的代码把三种日志逻辑混在一起,改个日志级别要重启服务,客户天天催着优化。

    这时候简单工厂就不够用了——因为日志的“创建逻辑”本身也很复杂(比如文件日志要指定路径、按大小切割;ELK日志要配IP和端口),全堆在一个工厂类里会变成新的“大泥球”。我们用工厂方法模式重构后,问题迎刃而解。

    核心思路

    :把“创建日志对象”这个行为也抽象成接口,每个日志类型对应一个工厂。就像开奶茶店,总部定好“做奶茶”的规矩(LogFactory接口),具体怎么做(水果茶工厂、奶茶工厂)交给分店。

    // 
  • 日志接口
  • public interface Log {

    void info(String message);

    void error(String message);

    }

    //

  • 日志工厂接口(关键!工厂方法的核心)
  • public interface LogFactory {

    Log createLog();

    }

    //

  • 具体工厂和实现类
  • public class ConsoleLog implements Log { ... } // 控制台日志实现

    public class ConsoleLogFactory implements LogFactory {

    @Override

    public Log createLog() {

    return new ConsoleLog(); // 创建控制台日志对象

    }

    }

    public class FileLog implements Log { ... } // 文件日志实现

    public class FileLogFactory implements LogFactory {

    @Override

    public Log createLog() {

    // 文件日志的复杂创建逻辑:检查路径、创建文件夹、设置编码...

    return new FileLog();

    }

    }

    用的时候,根据配置动态选择工厂:

    // 从配置文件读日志类型(开发环境配CONSOLE,生产配FILE)
    

    String logType = Config.getProperty("log.type");

    LogFactory factory;

    if ("CONSOLE".equals(logType)) {

    factory = new ConsoleLogFactory();

    } else {

    factory = new FileLogFactory();

    }

    Log log = factory.createLog();

    log.info("系统启动完成"); // 不管哪种日志,调用方式都一样

    朋友后来反馈

    :加ELK日志时,他自己花了两小时就搞定了——新建ElkLogElkLogFactory,改一下配置文件,完全不用动原来的日志调用代码。现在客户想换日志类型,改个配置重启就行,再也不用求着研发改代码了。

    案例3:插件化开发——抽象工厂搞定“产品族”创建

    最后说个复杂点的场景。前阵子帮一个IDE插件团队做架构,他们要开发支持“主题切换”的插件:用户选“浅色主题”,按钮、面板、字体都变成浅色风格;选“深色主题”,整套UI组件跟着变。如果用工厂方法,得为每个组件(按钮、面板、字体)建一套工厂,太麻烦了。这时候抽象工厂模式就派上用场了。

    抽象工厂的核心是“创建一整套相关对象”。比如主题工厂,不仅要创建按钮,还要创建面板和字体,而且这些对象必须匹配(浅色主题的按钮+浅色面板)。

    具体实现

    // 
  • 定义组件接口(产品族)
  • public interface Button { void render(); } // 按钮

    public interface Panel { void paint(); } // 面板

    public interface Font { void setSize(int size); } // 字体

    //

  • 抽象工厂接口(定义创建产品族的方法)
  • public interface ThemeFactory {

    Button createButton();

    Panel createPanel();

    Font createFont();

    }

    //

  • 具体主题工厂(每个工厂创建一整套匹配的组件)
  • public class LightThemeFactory implements ThemeFactory {

    @Override

    public Button createButton() { return new LightButton(); } // 浅色按钮

    @Override

    public Panel createPanel() { return new LightPanel(); } // 浅色面板

    @Override

    public Font createFont() { return new LightFont(); } // 浅色字体

    }

    public class DarkThemeFactory implements ThemeFactory {

    @Override

    public Button createButton() { return new DarkButton(); } // 深色按钮

    @Override

    public Panel createPanel() { return new DarkPanel(); } // 深色面板

    @Override

    public Font createFont() { return new DarkFont(); } // 深色字体

    }

    用的时候,选好主题工厂,一次性拿到整套组件:

    ThemeFactory factory = new DarkThemeFactory(); // 用户选了深色主题
    

    Button btn = factory.createButton();

    Panel panel = factory.createPanel();

    Font font = factory.createFont();

    // 渲染UI:按钮、面板、字体都是深色风格,不会混搭

    为什么这么做?

    假设不用抽象工厂,你可能在代码里写new LightButton()+new DarkPanel(),结果界面一半浅色一半深色,丑到用户投诉。抽象工厂保证了“组件成套出现”,这就是它解决的核心问题。

    这个案例里,团队后来要加“高对比度主题”,只需要新建HighContrastThemeFactory和对应的组件实现类,原来的UI渲染代码一行没动,插件体积还减少了15%——因为组件创建逻辑被统一管理了。

    你看,工厂模式其实一点都不抽象。记住一句话:当你发现代码里到处都是new对象,或者一堆if-else判断“创建哪种对象”时,就是工厂模式该出场的时候了。你可以先从项目里的支付、日志、缓存这些“多实现场景”入手,试试用简单工厂重构,改完后你会发现,代码清爽了,加功能也快了。

    对了,如果你试了这些方法,或者在项目里遇到了工厂模式的坑,欢迎回来留言告诉我——比如“用简单工厂重构了文件上传模块,上线后bug少了一半”,或者“抽象工厂用错了,越改越复杂”。咱们一起聊聊怎么把设计模式用得更顺手!


    你肯定遇到过这种情况:项目里到处都是new XXX(),一开始还好,后来要改个对象的创建逻辑,比如给构造函数加个参数,结果发现十几个地方都用了new XXX(),每个地方都得改一遍。我之前带实习生做文件上传功能,他直接在控制器、服务层、工具类里到处new FileUploader(),后来产品说要加“上传进度显示”,得改FileUploader的构造函数,结果光找这些new的地方就花了一下午,改完还漏了两个,上线后用户反馈上传进度不显示,半夜爬起来改bug。这就是直接new对象的坑——创建逻辑散得到处都是,改一处动全身。

    工厂模式就不一样了,它把所有创建对象的活儿都交给工厂管,你用对象的时候,直接跟工厂说“我要个文件上传器”,工厂就给你做好的。就像你点外卖,不用知道厨师怎么炒菜,只要告诉平台要什么菜就行。上次我们重构文件上传模块,把FileUploader的创建逻辑抽到UploaderFactory里,控制器、服务层都改成UploaderFactory.getUploader()。后来加“断点续传”功能,只需要在工厂里加个if判断,返回新的BreakpointUploader,原来的代码一行没动,测试时实习生都惊了:“这就完了?不用改控制器吗?” 你看,解耦就是这么实在——创建归创建,使用归使用,各干各的活儿,改的时候自然就不乱了。

    其实解耦最大的好处不是写代码的时候省事儿,是维护的时候少头疼。之前团队有个老项目,里面new了上百个OrderProcessor,每个地方传的参数还不一样。后来要加“订单超时自动取消”,得改OrderProcessor的构造函数,结果三个同事改了一周,还因为有人漏改了定时任务里的new,导致超时订单没取消,被运营追着骂。后来用工厂模式重构,把所有OrderProcessor的创建都放OrderFactory里,再改构造函数,只需要改工厂里的一行代码。现在团队新人接手,不用看懂所有OrderProcessor的创建细节,调用工厂就行,连文档都省了一半。你试试就知道,代码里少了那些newnew去的,改起来心里都踏实多了。


    三种工厂模式该怎么选?

    可以根据对象类型数量和变化频率决定:简单工厂适合1-3种对象类型、变化不频繁的场景(如支付渠道初期);工厂方法适合对象类型较多或需要频繁扩展的场景(如日志框架多实现);抽象工厂适合需要创建“配套对象组”的场景(如主题组件库)。优先从简单工厂开始,不够用再升级,避免过度设计。

    工厂模式和直接new对象有什么区别?

    核心区别是“解耦”:直接new对象时,客户端要知道对象的创建细节(如构造参数、依赖关系),新增类型需修改客户端代码;工厂模式下,客户端只通过工厂获取对象,创建逻辑被封装在工厂中,新增类型只需扩展工厂或实现类,原有代码无需改动,大幅降低维护成本。

    使用工厂模式会增加代码量吗?

    短期会增加:需要定义接口、实现类和工厂类,代码文件变多。但长期收益明显:新增功能时无需修改旧代码,减少bug;对象创建逻辑集中管理,后期维护更清晰。我曾带团队用工厂模式重构支付模块,初期多写了10个类,但后续6次新增支付渠道,每次开发时间从3天缩短到2小时,整体效率提升70%。

    什么时候不适合用工厂模式?

    两种情况 直接new:一是对象创建逻辑极简单(如无参构造、一行代码完成),用工厂反而增加复杂度;二是对象类型固定且永不扩展(如工具类单例)。《Clean Code》中提到:“设计模式是解决问题的工具,不是必须遵守的规则”,过度使用会让代码变得冗余难懂。

    工厂模式和依赖注入(DI)冲突吗?

    不冲突,反而能配合使用。工厂模式解决“对象创建逻辑封装”,依赖注入解决“对象依赖管理”。例如Spring框架中,@Bean注解本质就是工厂方法,负责创建对象;而@Autowired则是依赖注入,负责将工厂创建的对象注入到需要的地方。实际项目中,我常结合Spring的@Configuration(工厂)和@Autowired(注入),既解耦又简化依赖管理。

    0
    显示验证码
    没有账号?注册  忘记密码?