工厂模式和策略模式是两种常用的设计模式,它们在编程中都有重要的应用。实际项目中可能遇到的场景比较多,下面是它们的基本概念和示例。
工厂模式(Factory Pattern)
工厂模式是一种创建型设计模式,它提供了一种创建对象的接口,但具体创建的对象类型在运行时确定。工厂模式的主要优点是可以在不修改原有代码的情况下增加新的产品类型,而不需要修改使用该产品的代码。
特点:
- 提供了一个创建对象的接口,但具体创建的对象类型在运行时确定。
- 允许动态地创建对象,可以在运行时根据需要选择不同的产品类型。
- 可以隐藏产品的实现细节,使客户端代码与产品无关,从而提高了代码的可维护性和可扩展性。
应用场景:
- 当需要创建不同类型的对象时,但具体类型在运行时确定。
- 当需要将对象的创建逻辑与使用对象逻辑分离时。
- 当需要增加新的产品类型而不需要修改原有代码时。
示例:
首先引入一个支付工厂类 PayFactory<T> ,这里使用泛型,方便后续业务参数拓展。
package com.ryarchit.pay.factory;
import com.ryarchit.common.core.model.Res;
import com.ryarchit.pay.payservice.PayRequest;
/**
*@ClassName: PayFactory
*@Description: 支付工厂.
*@Author lucas
*@Date 2024/03/05
*/
public interface PayFactory<T> {
/**
* 多场景支付.
* @param payRequest
* @return
*/
Res<String> multiTransfer(PayRequest<T> payRequest);
}
然后再创建一个支付工厂的实现类。
package com.ryarchit.pay.factory.impl;
import com.ryarchit.common.core.channel.ChannelInfo;
import com.ryarchit.common.core.model.Res;
import com.ryarchit.common.core.utils.ApplicationContextHelper;
import com.ryarchit.pay.factory.PayFactory;
import com.ryarchit.pay.payservice.PayRequest;
import com.ryarchit.pay.payservice.TestCl;
import org.springframework.stereotype.Service;
/**
*@ClassName: CMBBankPay
*@Description: 招商银行支付.
*@Author lucas
*@Date 2024/03/05
*/
@Service
@ChannelInfo(code = "3")
public class CMBBankPay implements PayFactory<String> {
@Override
public Res<String> multiTransfer(PayRequest<String> payRequest) {
// 这里通过上下文帮助类来引入其他依赖bean.
TestCl testCl = ApplicationContextHelper.popBean(TestCl.class);
testCl.test();
return Res.success("支付成功");
}
}
以下是入参PayRequest
package com.ryarchit.pay.payservice;
import lombok.Data;
import java.math.BigDecimal;
/**
*@ClassName: PayRequest
*@Description: 支付请求对象.
*@Author lucas
*@Date 2024/03/05
*/
@Data
public class PayRequest<T> {
/**
* 支付金額
*/
private BigDecimal payAccount;
/**
* 支付渠道code.
*/
private String channelCode;
/**
* 不同支付方式业务参数
*/
private T body;
}
策略模式(Strategy Pattern)
策略模式是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法独立于使用它的客户端,使得算法可以自由地替换和修改。
特点:
- 定义了一系列算法,并将每一个算法封装起来,以供选择使用。
- 算法的替换和修改不影响客户端的使用方式。
- 客户端只需要知道抽象策略类,无需知道具体的实现细节。
应用场景:
- 当需要改变算法的执行方式时。
- 当需要将算法与使用它的客户端解耦时。
- 当需要动态地改变行为时。
- 创建一个通用的工厂策略类FactoryStrategy
示例:
package com.ryarchit.pay.strategy;
import com.ryarchit.common.core.channel.ChannelInfo;
import com.ryarchit.common.core.exceptions.TBException;
import com.ryarchit.pay.enums.FactoryPackage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reflections8.Reflections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
*@ClassName: FactoryStrategy
*@Description: 工厂策略.
*@Author lucas
*@Date 2024/03/05
*/
@Slf4j
public class FactoryStrategy {
/** 工厂策略 */
private static FactoryStrategy FACTORY_SRATEGY = new FactoryStrategy();
/** 渠道code和工厂实现类Map */
private static Map<String, String> CHANEL_SERVICE = new HashMap<>();
/** 静态代码块进行服务注册初始化 */
static {
init();
}
/** 无参构造 */
private FactoryStrategy() {
}
/** 通过反射获取注解中渠道CODE */
private static void init() {
FactoryPackage[] factoryPackages = FactoryPackage.values();
for (FactoryPackage strategy : factoryPackages) {
Reflections reflections = new Reflections(strategy.getPackagePath());
Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(ChannelInfo.class);
classSet.stream().forEach(clazz -> registerService(clazz));
}
}
/**
* 注册服务.
* @param clazz
*/
private static void registerService(Class<?> clazz) {
ChannelInfo channelInfo = clazz.getAnnotation(ChannelInfo.class);
CHANEL_SERVICE.put(channelInfo.code(), clazz.getCanonicalName());
}
/**
* 根据渠道CODE创建所需要的目标类.
* @param channelCode
* @return
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public <T> T createPayFactory(String channelCode) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String clazzName = CHANEL_SERVICE.get(channelCode);
if (StringUtils.isBlank(clazzName)) {
throw new TBException("no channel service found");
}
Class<?> clazz = Class.forName(clazzName);
return (T) clazz.newInstance();
}
/**
* 获取策略工厂实例.
* @return
*/
public static FactoryStrategy getInstance(){
return FACTORY_SRATEGY;
}
}
创建一个支付策略类PayStrategy<T>
package com.ryarchit.pay.strategy;
import com.ryarchit.common.core.model.Res;
import com.ryarchit.pay.factory.PayFactory;
import com.ryarchit.pay.payservice.PayRequest;
/**
*@ClassName: PayStrategy
*@Description: 支付策略类.
*@Author lucas
*@Date 2024/03/05
*/
public class PayStrategy<T> {
private final PayFactory<T> payFactory;
public PayStrategy(PayFactory<T> payFactory) {
this.payFactory = payFactory;
}
/**
* 支付.
* @param t
* @return
*/
public Res<String> pay(PayRequest<T> t) {
return payFactory.multiTransfer(t);
}
}
创建一个枚举FactoryPackage 用来扫描具体工厂实现方法的业务处理。
public enum FactoryPackage {
/** 支付工厂实现类包路径. */
PAY_FACTORY_PCG("com.ryarchit.pay.factory.impl");
/** 工厂实现类包的全路径. */
private String packagePath;
FactoryPackage(String packagePath) {
this.packagePath = packagePath;
}
public String getPackagePath() {
return packagePath;
}
public void setPackagePath(String packagePath) {
this.packagePath = packagePath;
}
}
说明:上面主要是通过工厂策略获取具体的工厂,然后通过工厂找到对应的工厂方法来解决具体的业务,只要在工厂实现上加上@ChannelInfo(code = "3")渠道信息注解,然后把具体的工厂实现包的全路径配置到FactoryPackage 枚举里即可。
工厂模式 + 策略模式业务整合
创建一个支付接口
package com.ryarchit.pay.payservice;
/**
*@ClassName: PayService
*@Description: 支付接口.
*@Author lucas
*@Date 2024/03/05
*/
public interface PayService {
/**
* 支付.
* @param channelCode
* @return
* @throws Exception
*/
String pay(String channelCode) throws Exception;
}
再创建一个支付接口实现类
package com.ryarchit.pay.payservice.impl;
import com.ryarchit.pay.factory.PayFactory;
import com.ryarchit.pay.payservice.PayRequest;
import com.ryarchit.pay.payservice.PayService;
import com.ryarchit.pay.strategy.FactoryStrategy;
import com.ryarchit.pay.strategy.PayStrategy;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
*@ClassName: PayServiceImpl
*@Description: 支付接口实现类.
*@Author lucas
*@Date 2024/03/05
*/
@Service("payServiceImpl")
public class PayServiceImpl implements PayService {
@Override
public String pay(String channelCode) throws Exception {
PayFactory payFactory = FactoryStrategy.getInstance().createPayFactory(channelCode);
PayStrategy payStrategy = new PayStrategy(payFactory);
PayRequest payRequest = new PayRequest();
payRequest.setPayAccount(new BigDecimal(200));
payRequest.setChannelCode("3");
return (String) payStrategy.pay(payRequest).getData();
}
}
最后创建一个支付入口类
package com.ryarchit.pay.controller;
import com.ryarchit.pay.payservice.PayService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
*@ClassName: PayController
*@Description: 支付测试.
*@Author lucas
*@Date 2024/03/05
*/
@RestController
@RequestMapping("/pay")
@Api(tags = "支付测试")
public class PayController {
@Autowired
@Qualifier("payServiceImpl")
private PayService payServiceImpl;
@GetMapping("/pay")
public String pay(@RequestParam("channelCode") String channelCode) throws Exception {
return payServiceImpl.pay(channelCode);
}
}
测试结果
设计总结
同时使用工厂模式 + 策略模式的设计模式有利于一个项目对多个工厂模式的管理使用,同时工厂模式使用的是抽象工厂的设计模式,因为各个银行的接口API的出入参、鉴权方式、加密算法可能都不太一样, 所以某一个接口改动后可能其对应的工厂方法均要跟着改动,这里就是用抽象工厂模式来进行业务设计。设计中使用了自定义渠道注解,在项目启动后第一次调用策略生产工厂时会在加载静态代码块的时候扫描出项目中使用到的工厂实现类,然后会把工厂类,渠道信息等加载到系统内存中以便后续使用,从而实现动态的生产出不同业务的工厂策略接工厂实现,更好的解耦业务逻辑。
最后,选择使用哪种策略则由客户端代码根据实际情况决定。由于每种渠道和策略都被封装在工厂和策略接口中,因此客户端代码可以独立于这些细节进行编写。通过这种方式,我们实现了多渠道业务的灵活实现。
总结起来,通过结合使用工厂模式和策略模式,我们可以更方便地创建和管理不同的渠道和发送策略,并且能够灵活地根据需求选择合适的渠道和发送策略。这种组合模式通常适用于需要在多渠道业务中灵活选择使用不同逻辑的情况。