工厂模式 vs 策略模式:设计模式中的 “创建者” 与 “决策者”

在日常工作里,需求变动或者新增功能是再常见不过的事情了。而面对这种情况时,那些耦合度较高的代码就会给我们带来不少麻烦,因为在这样的代码基础上添加新需求往往困难重重。为了保证系统的稳定性,我们在添加新需求时,最好避免直接修改前人编写的代码,否则可能会破坏原有的稳定结构。

接下来,我会为大家介绍两种非常实用的设计模式 ------ 工厂模式和策略模式。如果您已经对这两种设计模式了如指掌,那么可以直接跳过下面的介绍内容。

下文中出现的案例代码在:https://gitee.com/dingchen0000/blog-notes.git

工厂模式

介绍

在传统的编程方式里,当我们需要使用某个对象时,就会直接使用new关键字去创建它,就好像我们自己亲手打造一个工具,打造完成后就能马上使用。但这种方式在项目规模变大、逻辑变复杂后,会带来很多问题,而工厂模式的出现就是为了解决这些问题。

想象一下,我们在一个电子工厂里工作。你创造出了一个智能机器人,它有灵活的手臂(属性)和稳健的轮子(属性),能够高效地替你组装零件(方法)。在传统模式下,你创造出这个机器人后,就可以直接给它下达指令,让它开始工作。同样,你的同事创造了一台智能冰箱,它有超大的存储空间(属性)和智能的温度调节功能(方法),你的同事也能直接操作这台冰箱。

然而,如果有其他部门的同事想要使用你们创造的机器人和冰箱,就会变得很麻烦。他们需要分别找你和你的同事,经过你们的同意才能使用,这无疑增加了沟通成本和使用的复杂性。

为了解决这个问题,工厂引入了一个统一的管理部门(工厂模式)。你和你的同事把创造好的机器人和冰箱都交给这个管理部门,当其他部门的同事需要使用机器人或冰箱时,只需要向这个管理部门提出申请,管理部门就会根据需求提供相应的设备。这样一来,使用者不需要关心设备是如何制造出来的,也不需要和具体的创造者沟通,大大提高了使用效率,降低了各个部门之间的耦合度。

在代码的世界里也是一样。假设我们有不同的促销策略,比如打折、满减、赠品等。如果没有工厂模式,在需要使用这些策略时,我们就得在代码里到处使用new关键字来创建策略对象,这样会让代码变得混乱,而且一旦策略的创建逻辑发生变化,就需要修改大量的代码。而使用工厂模式,我们可以把这些策略对象的创建逻辑封装在一个工厂类里,当需要使用某个策略时,只需要向工厂类请求,由工厂类来创建并返回相应的对象,这样就实现了对象的创建和使用的分离,降低了代码的耦合度,提高了代码的可维护性和可扩展性。

非工厂案例

在传统编程中,当需要实现打折促销功能时,通常会在业务逻辑代码里直接创建并使用打折策略对象。下面结合你提供的代码片段,以 Java 为例说明传统做法:

传统的模式

java 复制代码
// 1. 定义促销策略接口
interface PromotionStrategy {
    double calculateDiscount(double orderAmount);
}

// 2. 实现具体策略类
class DiscountStrategy implements PromotionStrategy {
    @Override
    public double calculateDiscount(double orderAmount) {
        return orderAmount * 0.2; // 8折优惠
    }
}

class FullReduceStrategy implements PromotionStrategy {
    @Override
    public double calculateDiscount(double orderAmount) {
        return orderAmount >= 200 ? 50 : 0; // 满200减50
    }
}

// 3. 业务逻辑直接依赖具体策略
class OrderServiceWithoutFactory {
    public double calculateFinalPrice(double amount, String strategyType) {
        PromotionStrategy strategy;
        
        // 直接在业务逻辑中创建对象
        if ("DISCOUNT".equalsIgnoreCase(strategyType)) {
            strategy = new DiscountStrategy();
        } else if ("FULL_REDUCE".equalsIgnoreCase(strategyType)) {
            strategy = new FullReduceStrategy();
        } else {
            throw new IllegalArgumentException("未知策略类型");
        }
        
        return amount - strategy.calculateDiscount(amount);
    }
}

// 4. 客户端调用
public class NonFactoryDemo {
    public static void main(String[] args) {
        OrderServiceWithoutFactory service = new OrderServiceWithoutFactory();
        double finalPrice = service.calculateFinalPrice(300, "DISCOUNT");
        System.out.println("最终价格: " + finalPrice);
    }
}
  • 在非工厂模式中,对象的创建逻辑直接嵌入在业务代码里,这会导致以下几个严重的维护问题:

    代码分散问题

    在复杂系统中,对象创建可能散落在多个服务类、工具类甚至控制器中。例如:

    • 订单服务中直接new DiscountStrategy()
    • 营销活动模块中new FullReduceStrategy()
    • 定时任务里也可能创建相同对象

    当需要修改或扩展功能时,你需要:

    1. 找出所有创建该对象的地方
    2. 逐一修改,可能遗漏某些角落
    3. 承担引入新问题的风险

缺点:

依赖关系复杂

业务类不仅依赖抽象接口,还依赖具体实现类:

java 复制代码
public class OrderServiceWithoutFactory {
    public double calculatePrice(double amount) {
        // 直接依赖具体类!
        PromotionStrategy strategy = new DiscountStrategy(); 
        return strategy.calculate(amount);
    }
}

这种强依赖导致:

  • 新增策略时必须修改业务类代码
  • 策略类的构造函数变化(如增加参数)会影响所有调用处
  • 难以进行单元测试(需实例化真实对象而非模拟对象)

在非工厂模式里,直接在业务代码中「硬编码」创建对象,就像把钥匙藏在家里各个抽屉里 ------ 看起来方便,实际用的时候全是麻烦:

代码像撒豆子,改一处得翻遍全项目

想象你要给超市所有收银台的「打折功能」升级:

  • 原本「满减策略」的代码,可能藏在:
    • 收银台的结账程序里(new FullReduceStrategy()
    • 会员系统的积分兑换模块里(又一个new FullReduceStrategy()
    • 甚至后台定时计算报表的脚本里(再来一个new FullReduceStrategy()

当你想修改满减规则时

  • 得像侦探一样,把全项目里所有写着FullReduceStrategy的地方都找出来(可能漏找某个角落)
  • 每个地方都要改一遍代码(比如把「满 200 减 50」改成「满 300 减 80」)
  • 改完还得担心:有没有漏掉某个地方?改完其他功能会不会出错?
业务代码和具体实现「锁死」,牵一发而动全身

举个生活例子:

你开了家奶茶店,菜单上写着「招牌奶茶 = 红茶 + 奶精 + 珍珠」(业务逻辑),但你直接在菜单里写死了「用 A 牌红茶、B 牌奶精」(依赖具体实现类)。

问题来了

  • 新增口味麻烦:想推出「绿茶版奶茶」,必须把菜单上所有「红茶」字样都改成「绿茶」(新增策略必须改业务代码)。
  • 供应商换原料就崩溃:如果 A 牌红茶停产,你得把菜单上所有「A 牌红茶」换成「C 牌红茶」(策略类构造函数修改,所有调用处都得改)。
  • 没法模拟测试:想试试「用椰奶代替奶精」的效果,必须真的买椰奶回来试(单元测试时必须创建真实对象,没法用模拟数据)。

用代码举例就是:

java 复制代码
// 业务代码直接「点名」要某个具体实现类
public class 收银台 {
    public double 计算价格(double 金额) {
        // 直接「new」一个具体的「满减策略」,就像直接说「我要A牌红茶」
        优惠策略 策略 = new 满减策略(); 
        return 金额 - 策略.计算优惠(金额);
    }
}

这样写死的后果就是:

  • 想换「打折策略」?必须改这里的new 满减策略()new 打折策略()
  • 满减策略的构造函数需要传参(比如new 满减策略(200, 50))?所有用到它的地方都得跟着改参数。

工厂模式案例

java 复制代码
// 1. 定义促销策略接口(与非工厂模式相同)
interface PromotionStrategy {
    double calculateDiscount(double orderAmount);
}

// 2. 实现具体策略类(与非工厂模式相同)
class DiscountStrategy implements PromotionStrategy {
    @Override
    public double calculateDiscount(double orderAmount) {
        return orderAmount * 0.2; // 8折优惠
    }
}

class FullReduceStrategy implements PromotionStrategy {
    @Override
    public double calculateDiscount(double orderAmount) {
        return orderAmount >= 200 ? 50 : 0; // 满200减50
    }
}

// 3. 创建工厂类
class PromotionStrategyFactory {
    public static PromotionStrategy createStrategy(String strategyType) {
        if ("DISCOUNT".equalsIgnoreCase(strategyType)) {
            return new DiscountStrategy();
        } else if ("FULL_REDUCE".equalsIgnoreCase(strategyType)) {
            return new FullReduceStrategy();
        } else {
            throw new IllegalArgumentException("未知策略类型");
        }
    }
}

// 4. 业务逻辑通过工厂获取策略
class OrderServiceWithFactory {
    public double calculateFinalPrice(double amount, String strategyType) {
        // 通过工厂获取策略,不直接依赖具体类
        PromotionStrategy strategy = PromotionStrategyFactory.createStrategy(strategyType);
        return amount - strategy.calculateDiscount(amount);
    }
}

// 5. 客户端调用
public class FactoryDemo {
    public static void main(String[] args) {
        OrderServiceWithFactory service = new OrderServiceWithFactory();
        double finalPrice = service.calculateFinalPrice(300, "FULL_REDUCE");
        System.out.println("最终价格: " + finalPrice);
    }
}

工厂模式的好处显然就是

  1. 单一修改点:新增策略只需在工厂类中注册,无需修改业务代码
  2. 依赖倒置:业务类只依赖工厂和抽象接口,不依赖具体实现
  3. 代码复用:复杂的初始化逻辑只需在工厂中实现一次
  4. 统一管理:对象创建规则集中维护,便于新增功能和团队协作
  5. 可测试性:可以轻松替换工厂实现(如使用模拟工厂)进行单元测试

总结

非工厂模式就像把「建房子的图纸」和「搬砖的步骤」混在一起写:

  • 简单场景下看似省事,但项目变大后,代码会像乱成一团的毛线 ------
    • 改一个功能要挖地三尺找代码
    • 牵一发而动全身,改完一处崩十处
    • 想测试新功能,必须把真实对象全跑一遍

而工厂模式就像找了个「专业包工头」(工厂类)专门管搬砖,业务代码只需要告诉包工头「我要盖客厅还是卧室」,剩下的细节全由包工头处理 ------ 既干净又省心。

策略模式

介绍

策略模式是一种行为型设计模式,其核心思想是:

  • 封装算法族:将不同的算法(或策略)封装成独立的类,使它们可以相互替换。
  • 解耦算法与使用:让算法的变化独立于使用算法的客户端,从而提高代码的灵活性和可扩展性

打个比方

你去餐厅吃饭,菜单上有「糖醋排骨」「鱼香肉丝」「麻婆豆腐」等菜品(这就是不同的「策略」)。

  • 你不需要自己进厨房炒菜(不用关心具体怎么做菜),只需要告诉服务员「我要哪道菜」(调用策略)。
  • 服务员(相当于「上下文类」)会根据你的选择,通知厨房做对应的菜(切换策略)。

策略模式的核心就像这个过程:把不同的「做事方法」封装起来,需要时随时切换,而调用者不用知道具体怎么实现。

案例说明

先定义「优惠规则」的统一标准(策略接口)

就像餐厅菜单上写着「所有菜品都要能算出价格」,我们先定一个接口:

java 复制代码
public interface PromotionStrategy {
    double calculateDiscount(double orderAmount); // 不管怎么优惠,都要能算出优惠金额
}

作用:让所有优惠规则(打折、满减、赠品)都必须遵守这个「规矩」,方便后续统一管理。

每个优惠规则都是一个「独立菜品」(具体策略类)
  • 打折策略

    :相当于「糖醋排骨」,具体做法是「打 8 折」:

    java 复制代码
    public class DiscountStrategy implements PromotionStrategy {
        @Override
        public double calculateDiscount(double orderAmount) {
            return orderAmount * 0.2; // 直接算优惠金额
        }
    }
  • 满减策略

    :相当于「鱼香肉丝」,具体做法是「满 200 减 50」:

    java 复制代码
    public class FullReduceStrategy implements PromotionStrategy {
        @Override
        public double calculateDiscount(double orderAmount) {
            return orderAmount >= 200 ? 50 : 0; // 满足条件才优惠
        }
    }
  • 赠品策略:相当于「麻婆豆腐」,做法是「满 100 送赠品」(虽然不直接减钱,但也是一种策略):

    java 复制代码
    public class GiftStrategy implements PromotionStrategy {
        @Override
        public double calculateDiscount(double orderAmount) {
            if (orderAmount >= 100) {
                System.out.println("送你小风扇!"); // 执行赠品逻辑
            }
            return 0; // 金额不变
        }
    }

关键点:每个策略类都是「自包含」的,就像厨房的不同厨师各自负责一道菜,互相不干扰。

上下文类:相当于「服务员」,负责切换策略

以前没有上下文类时,你得自己去厨房点菜(业务代码直接调用策略类),现在有了服务员,你只需要告诉她:「我要吃糖醋排骨」(调用上下文类的方法,传入策略类型)。

java 复制代码
public class OrderContext {
    private PromotionStrategy currentStrategy; // 当前使用的策略(默认是空的)

    // 初始化时选一种策略(比如默认用打折)
    public OrderContext(PromotionStrategy strategy) {
        this.currentStrategy = strategy;
    }

    // 随时换策略!就像吃饭时突然想换菜,告诉服务员就行
    public void changeStrategy(PromotionStrategy newStrategy) {
        this.currentStrategy = newStrategy;
    }

    // 计算最终价格:交给当前策略去处理
    public double calculateFinalPrice(double orderAmount) {
        return orderAmount - currentStrategy.calculateDiscount(orderAmount);
    }
}

为什么需要上下文类?

  • 解耦调用逻辑:业务代码不用关心「怎么创建策略对象」,只需要告诉上下文「我要用哪个策略」。
  • 支持动态切换 :比如用户下单时先用「打折策略」,付款前突然发现有满减活动,直接调用changeStrategy切换即可,不用改核心计算逻辑。

策略模式+工厂模式

在实际开发的时候呀,很少会只用到一种设计模式,一般都是好几种设计模式一起用。咱们就拿这个优惠策略的例子来说吧。

这个系统里的优惠策略可不止一种哦。要是以后想再增加新的优惠策略,就会在 PromotionStrategyFactory 这个工厂里创建新的对象。比如说以后又有了新的优惠方式,也得在这个工厂里来创建对应的对象。这里用到了策略模式,只要新的优惠策略实现 PromotionStrategy 这个接口,把里面计算折扣优惠的方法重新写一下,然后在工厂里把创建这个新策略对象的逻辑加上就行。

下面是 PromotionStrategyFactory 这个工厂类的代码:

java 复制代码
@Component
public class PromotionStrategyFactory {

    public PromotionStrategy createStrategy(String strategyType) {
        // 根据传入的策略类型,用大写来判断
        switch (strategyType.toUpperCase()) {
            // 如果是 "DISCOUNT",就创建一个折扣策略对象
            case "DISCOUNT":
                return new DiscountStrategy();
            // 如果是 "FULL_REDUCE",就创建一个满减策略对象
            case "FULL_REDUCE":
                return new FullReduceStrategy();
            // 如果是 "GIFT",就创建一个赠品策略对象
            case "GIFT":
                return new GiftStrategy();
            // 如果传入的策略类型不认识,就抛出异常
            default:
                throw new IllegalArgumentException("未知策略类型: " + strategyType);
        }
    }
}

在控制器层,也就是和客户端交互的地方,代码是这样的:

java 复制代码
@RestController
@RequestMapping("/api/promotion")
public class PromotionController {

    // 自动注入订单上下文对象
    @Autowired
    private OrderContext orderContext;
    // 自动注入优惠策略工厂对象
    @Autowired
    private PromotionStrategyFactory strategyFactory;

    // 处理 GET 请求,计算优惠后的价格
    @GetMapping("/calculate")
    public ResponseEntity<Map<String, Object>> calculate(@RequestParam double orderAmount, @RequestParam String strategyType) {
        try {
            // 1. 从工厂获取对应的优惠策略实例
            PromotionStrategy strategy = strategyFactory.createStrategy(strategyType);
            // 2. 把获取到的策略应用到订单上下文中
            orderContext.changeStrategy(strategy);
            // 3. 调用订单上下文的方法,计算出最终的价格
            double finalPrice = orderContext.calculateFinalPrice(orderAmount);

            // 4. 把计算结果放到一个 Map 里,作为响应返回
            Map<String, Object> result = new LinkedHashMap<>();
            result.put("开始价格", orderAmount);
            result.put("折扣类型", strategyType);
            result.put("最后的折扣", finalPrice);

            return ResponseEntity.ok(result);

        } catch (IllegalArgumentException e) {
            // 如果传入的策略类型不合法,就返回一个错误响应
            Map<String, Object> error = new LinkedHashMap<>();
            error.put("error", "INVALID_STRATEGY");
            error.put("message", e.getMessage());
            return ResponseEntity.badRequest().body(error);
        }
    }
}

简单来说呢,就是通过工厂类来创建不同的优惠策略对象,然后在控制器里把这些策略应用到订单上,计算出最终的优惠价格,要是遇到不认识的策略类型,还会给出错误提示。

改进(枚举+sprinboot自动注册)

在咱们现在用的工厂类里,代码采用的是硬编码方式。这就意味着,要是有新的优惠策略类加入,就得去修改工厂方法的代码。可在开发中,频繁修改代码是我们不想看到的,因为这可能会引入新的问题,也不利于代码的维护和扩展。

简单工厂模式确实帮我们把客户端和具体的策略类实现分离开来了,让它们之间的依赖关系没那么紧密。不过呢,工厂类在初始化策略对象(也就是策略 beans)的时候,还是和具体的策略类绑得比较紧。也就是说,工厂类得明确知道有哪些具体的策略类,这就导致一旦有新的策略类出现,工厂类就得跟着改。

为了让它们之间的关系更松散,我们可以借助 Spring 框架里的 InitializingBean 接口和 ApplicationContextAware 接口来自动完成策略对象的装配工作。

下面来详细说说这两个接口的作用:

InitializingBean 接口

InitializingBean 接口有一个 afterPropertiesSet 方法。当 Spring 容器创建并初始化一个实现了 InitializingBean 接口的类的实例时,在设置完所有属性之后,会自动调用 afterPropertiesSet 方法。我们可以在这个方法里完成一些初始化操作。

ApplicationContextAware 接口

ApplicationContextAware 接口有一个 setApplicationContext 方法。实现了这个接口的类可以通过该方法获取到 Spring 的 ApplicationContext(应用上下文)。ApplicationContext 就像是 Spring 容器的大管家,它能管理所有的 Bean,我们可以通过它获取到容器中所有实现了某个接口的 Bean 实例。

实现自动装配策略对象

结合这两个接口,我们可以在工厂类里这样做:

  1. 实现 InitializingBean 接口,在 afterPropertiesSet 方法中进行策略对象的初始化操作。
  2. 实现 ApplicationContextAware 接口,通过 setApplicationContext 方法获取 ApplicationContext
  3. 使用 ApplicationContextgetBeansOfType 方法获取所有实现了 PromotionStrategy 接口的 Bean 实例,并将它们存到一个 Map 里。

这样,当有新的策略类添加时,只要它实现了 PromotionStrategy 接口,Spring 容器会自动把它注册为一个 Bean,afterPropertiesSet 方法会把它添加到 Map 中,工厂类就不需要再手动修改代码来创建新的策略对象,从而实现了工厂类和具体策略类的解耦。

java 复制代码
@Component
public class PromotionFactory implements InitializingBean {

    private final Map<PromotionType, PromotionStrategy> strategyMap = new HashMap<>();


    private final ApplicationContext applicationContext;

    @Autowired
    public PromotionFactory(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 初始化:从Spring容器中获取所有策略Bean,并按枚举类型注册
     * Key:Bean 的名称(默认是类名首字母小写,如fullReduceStrategy)。
     * Value:Bean 的实例(实现了PromotionStrategy接口的具体策略类)。
     */
    @Override
    public void afterPropertiesSet() {
        // 获取所有实现PromotionStrategy接口的Bean
        Map<String, PromotionStrategy> beans = applicationContext.getBeansOfType(PromotionStrategy.class);
        beans.forEach((beanName, strategy) -> {
            // 从策略Bean中获取对应的枚举类型(需在策略类中增加获取类型的方法)
            // 这里假设策略类通过枚举类型命名(如FullReduceStrategy对应FULL_REDUCE枚举)
            PromotionType type = parseTypeFromBeanName(beanName);
            if (type != null) {
                strategyMap.put(type, strategy);
            }
        });
    }

    /**
     * 从Bean名称解析枚举类型(简化实现,实际可通过注解或接口方法指定类型)
     */
    private PromotionType parseTypeFromBeanName(String beanName) {
        try {
            // Bean名称默认驼峰式,转为枚举大写
            return PromotionType.valueOf(beanName.toUpperCase());
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    /**
     * 获取策略实例
     */
    public PromotionStrategy getStrategy(PromotionType type) {
        if (!strategyMap.containsKey(type)) {
            throw new IllegalArgumentException("未知促销类型:" + type);
        }
        return strategyMap.get(type);
    }
}
java 复制代码
@Getter
public enum PromotionType {
    FULL_REDUCE("满减"),
    DISCOUNT("打折"),
    GIFT("赠品");

    private final String desc;

    PromotionType(String desc) {
        this.desc = desc;
    }
}
java 复制代码
@RestController
public class TestController {
    private final OrderService orderService;

    public TestController(OrderService orderService) {
        this.orderService = orderService;
    }

    /**
     * 测试接口
     * http://localhost:8080/calculate?orderAmount=300&promotionType=FULL_REDUCE
     */
    @GetMapping("/calculate")
    public String calculatePrice(@RequestParam double orderAmount, @RequestParam PromotionType promotionType) {
        double finalPrice = orderService.calculateFinalPrice(orderAmount, promotionType);

        return "订单金额:" + orderAmount + "元,使用" + promotionType.getDesc() + "后,最终价格:" + finalPrice + "元";
    }
}

调用的模型图

ice = orderService;

}

复制代码
/**
 * 测试接口
 * http://localhost:8080/calculate?orderAmount=300&promotionType=FULL_REDUCE
 */
@GetMapping("/calculate")
public String calculatePrice(@RequestParam double orderAmount, @RequestParam PromotionType promotionType) {
    double finalPrice = orderService.calculateFinalPrice(orderAmount, promotionType);

    return "订单金额:" + orderAmount + "元,使用" + promotionType.getDesc() + "后,最终价格:" + finalPrice + "元";
}

}

复制代码
调用的模型图

[外链图片转存中...(img-GYDzYNR0-1748529814600)]
相关推荐
希望_睿智14 分钟前
实战设计模式之建造者模式
c++·设计模式·架构
ErizJ37 分钟前
Golang|分布式搜索引擎中所使用到的设计模式
设计模式
哈哈哈哈哈哈哈哈哈...........2 小时前
【设计模式】策略模式
设计模式·策略模式
琢磨先生David9 小时前
责任链模式:构建灵活可扩展的请求处理体系(Java 实现详解)
java·设计模式·责任链模式
charlie11451419110 小时前
从C++编程入手设计模式1——单例模式
c++·单例模式·设计模式·架构·线程安全
琢磨先生David12 小时前
Java 访问者模式深度重构:从静态类型到动态行为的响应式设计实践
java·设计模式·访问者模式
linux-hzh19 小时前
设计模式之原型模式
设计模式·原型模式
蔡蓝20 小时前
设计模式-工厂方法模式
java·设计模式·工厂方法模式
linux-hzh20 小时前
设计模式之单例模式
单例模式·设计模式
QQ_hoverer21 小时前
Java设计模式之工厂模式与策略模式简单案例学习
java·开发语言·学习·设计模式·策略模式