1.工厂方法

工厂方法

工厂方法,又可以称为虚拟构造函数、Factory Method;其在父类中提供了一个创建对象的方法,允许子类决定实例化对象的类型。

结构实现

如上图所示:

  1. 抽象产品(Product):定义产品的接口。
  2. 具体产品(Concrete):返回具体类型。
  3. 抽象工厂类(Creator):定义一个接口,包含一个抽象的工厂方法
  4. 具体工厂(Concrete Creators):重写工厂方法,返回不同的对应类型

示例说明:

当我们实现跨平台UI组件开发时,需要保证在windows系统下的按钮再Linux系统下保持不变,就需要我们使用到工厂方法。

类图分析:

代码实现:

java 复制代码
package com.example.demo;

import org.junit.jupiter.api.Test;

/**
 * @title: APITest
 * @date: 2024/3/28 16:41
 * @description:
 */

// 抽象产品
interface Button {
    void click();
}

// 具体产品
class WindowsButton implements Button {
    @Override
    public void click() {
        System.out.println("Windows Button");
    }
}

class MacButton implements Button {
    @Override
    public void click() {
        System.out.println("Mac Button");
    }
}

// 抽象工厂
interface GUIFactory {
    Button createButton();
}

// 具体工厂
class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
}

class MacFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }
}

public class APITest {
    @Test
    public void test() {
        GUIFactory factory = new WindowsFactory();
        Button button = factory.createButton();
        button.click();
    }
}

这样,我们就简单实现了一个工厂模式。当然,具体的使用中并不会这么简单,接下来我们便以电商系统中常见的积分兑换礼品作为简单分析。

实战分析

假设系统进行礼品兑换时,可分为优惠卷、实物商品、第三方兑换卡三种礼品。

不使用设计模式实现

java 复制代码
 public AwardRes awardToUser(AwardReq req) {
        String reqJson = JSONObject.toJSONString(req);
        AwardRes awardRes = null;
        try {
            logger.info("奖品发放开始{}。req:{}", req.getuId(), reqJson);
            // 按照不同类型方法商品[1优惠券、2实物商品、3第三方兑换卡]
            if (req.getAwardType() == 1) {
                CouponService couponService = new CouponService();
                // 处理优惠卷业务
                awardRes = XXX;
            } else if (req.getAwardType() == 2) {
                GoodsService goodsService = new GoodsService();
                // 处理实物业务
                awardRes = XXX;
            } else if (req.getAwardType() == 3) {
                IQiYiCardService iQiYiCardService = new IQiYiCardService();
                // 处理第三方兑换卡业务
                awardRes = XXX;
            }
            logger.info("奖品发放完成{}。", req.getuId());
        } catch (Exception e) {
            logger.error("奖品发放失败{}。req:{}", req.getuId(), reqJson, e);
            awardRes = new AwardRes("0001", e.getMessage());
        }
        return awardRes;
    }

由以上代码可知,使用 if 判断虽然逻辑明了,并没有什么问题。 但是当奖品种类增加时,if 判断也要对应增加,这样会使得方法体行数增加,维护变得更加麻烦,总不能每次都要一个一个找过去吧。

使用工厂方法

经过以上分析,我们可以抽象产品接口(ISouvenirs ),而具体产品方法(CouponSouvenirsService,GoodsSouvenirsService,CardSouvenirsService)则对应不同的商品实现层。在使用工厂类实现统一返回。 如下:

java 复制代码
// 抽象产品如下:
public interface ISouvenirs{
    
    /**
    * 发送礼品
    */
    void sendSouvenirs(...params);
}

// 工厂类
public class StoreFactory{
    public ISouvenirs getSouvenirsService(Integer commodityType) {
        if (null == commodityType) return null;
        if (1 == commodityType) return new CouponSouvenirsService();
        if (2 == commodityType) return new GoodsSouvenirsService();
        if (3 == commodityType) return new CardSouvenirsService();
        throw new RuntimeException("不存在的礼品类型");
    }
}

// 实现
 public AwardRes awardToUser(AwardReq req) {
     StoreFactory storeFactory = new StoreFactory();
     ISouvenirs souvenirs = storeFactory.getCommodityService();
     souvenirs.sendSouvenirs(...params);
 }

由以上可知,每种奖品的实现都在自己的类中,修改/删除也不影响其他功能的测试。而后续有新增奖品种类时,我们只需要按图索翼,新增对应的Service层,可以减低维护的难度。

总结

经过以上几次总结,我们可以知道工厂方法模式并不复杂。

他避免工厂和产品的耦合,满足了单一原则,每一个业务逻辑实现都在自己所属类中完成。

但是这样子也会出现一个同样的问题,当有巨量的奖品类型时,工厂类中的 if 判断也会对应增加,从而使维护更难。因此这时需要搭配设计模式使用。

参考资料

  1. 设计模式 | 菜鸟教程 (runoob.com)
  2. 《深入设计模式》 PDF版
相关推荐
摇滚侠3 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯5 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友6 小时前
什么是断言?
前端·后端·安全
程序员小凯7 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
i学长的猫8 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
用户21411832636028 小时前
别再为 Claude 付费!Codex + 免费模型 + cc-switch,多场景 AI 编程全搞定
后端
茯苓gao8 小时前
Django网站开发记录(一)配置Mniconda,Python虚拟环境,配置Django
后端·python·django
Cherry Zack8 小时前
Django视图进阶:快捷函数、装饰器与请求响应
后端·python·django
爱读源码的大都督9 小时前
为什么有了HTTP,还需要gPRC?
java·后端·架构
码事漫谈9 小时前
致软件新手的第一个项目指南:阶段、文档与破局之道
后端