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版
相关推荐
小飞悟2 分钟前
浏览器和服务器是怎么“认出你”的?揭秘 Cookie 认证
后端·node.js
一名用户3 分钟前
unity实现梦日记式传送组件
后端·c#·unity3d
hai99long8 分钟前
DTP 模型:分布式事务处理的经典架构模型
后端
魔镜魔镜_谁是世界上最漂亮的小仙女9 分钟前
java-web开发
java·后端·架构
摘星编程10 分钟前
深入理解责任链模式:从HTTP中间件到异常处理的实战应用
http·设计模式·中间件·责任链模式·实战应用
雷渊31 分钟前
微服务中为什么要设计不同的服务和不同的数据对象,体现了一个什么样的设计思想?
后端
无奈何杨1 小时前
CoolGuard风控中新增移动距离和移动速度指标
前端·后端
程序员爱钓鱼1 小时前
Go语言泛型-泛型约束与实践
前端·后端·go
寻月隐君1 小时前
保姆级教程:Zsh + Oh My Zsh 终极配置,让你的 Ubuntu 终端效率倍增
linux·后端·命令行
程序员爱钓鱼1 小时前
Go语言泛型-泛型对代码结构的优化
后端·google·go