java 设计模式责任链模式自我心得以及应用
责任链模式
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。Tomcat中的Filter就是使用了责任链模式,创建一个Filter除了要在web.xml文件中做相应配置外,还需要实现javax.servlet.Filter接口。
1.1 简单的例子
不同的过滤器实现不同的功能。类似于C# 指针委托事件概念,由每个对象遍历进行执行下去。
1.定义具有过滤功能的接口Filter,具体的过滤规则需要实现该接口
该接口的执行方法参数中需要一个链节模式的对象。
public interface Filter { void doFilter(Request req, FilterChain filterChain); }
其中Request是个对象,没啥含义,表示参数
@Data public class Request { private String name; }
FilterChain 就是链条功能,贯彻整个流程,里面有注册功能。
public class FilterChain implements Filter { //用List集合来存储过滤规则 List<Filter> filters = new ArrayList<Filter>(); //用于标记规则的引用顺序 int index = 0; public FilterChain addFilter(Filter filter) { filters.add(filter); return this; } @Override public void doFilter(Request req, FilterChain filterChain) { //index初始化为0,filters.size()为3,不会执行return操作 if (index == filters.size()) { return; } //每添加一个过滤规则,index自增1 Filter f = filters.get(index); index++; //根据索引值获取对应的规律规则对字符串进行处理 f.doFilter(req, filterChain); } }
以上的代码逻辑很多,就是先一次性注册全部filter,然后依次从List获取,根据index自增获取,其中这里的漏洞还是很多的,尤其在spring boot框架中比如index,自增可以优化不,还有FilterChain实现filter接口,自己其实也相当于注册来,应该过滤它。
创建HTMLFilter
public class HTMLFilter implements Filter
{
@Override
public void doFilter(Request req, FilterChain filterChain)
{
if (req.getName().equals("zhangs"))
{
//符合做事情
System.out.println("zhangs");
}
//继续往下走
filterChain.doFilter(req, filterChain);
}
}
创建FaceFiter
public class FaceFilter implements Filter
{
@Override
public void doFilter(Request req, FilterChain filterChain)
{
if (req.getName().equals("wangsi"))
{
//符合做事情
System.out.println("wangsi");
}
//继续往下走
filterChain.doFilter(req, filterChain);
}
}
主函数运行
FilterChain filter = new FilterChain(); filter.addFilter(new HTMLFilter()); filter.addFilter(new FaceFilter()); Request request = new Request(); request.setName("zhangs"); filter.doFilter(request, filter);
就是这么简单,只是简单的样品而已,正在运用到项目中可不会这么简单,考虑的因素会很多,如果我放在spring boot框架中,将会是如何,我就试着写下。
我们就举个其他例子吧,比如商品搞活动价格问题,一般电商都会搞活动,比如有折扣价活动,满减活动,满赠活动,还有很多,我们不列举那么多,就这3个活动来做吧,
一般做这个第一时间会想到策略模式,一个活动一个对象,实现统一接口活动内容,根据商品参数得出价格多少情况。我们现在用责任链模式来实现该内容,具体题目如下:
指定商品A,商品B,商品C,商品D这4件商品,价格分别为:20元,25元,12元,15元。
活动价格顺序 满增 -> 折扣 -> 单品满减。
其中商品A参加折扣9折活动,又参加了满15元减2元活动。
商品B参加满15元减2元活动。
商品C参加满2件赠送1件商品。
商品D参加折扣9折活动,又参加了满15元减2元,满2件赠送1件活动。
实现流程:
1.获取所有活动的信息,分别带有哪些活动哪些商品参加了。
2.商品依次遍历计算带有参加的活动以及最后的付款价格。
假如商品A数量2,商品B买1件,商品C买3件,商品D买2
则商品A价格20*2*0.9-2=34元,参加折扣和满15元减2活动
。。。。自己去算
用spring boot 框架来实现,不涉及数据库表之类的,数据都是自己造数据写死吧。
代码结构如下:
用枚举定义活动类型
@Getter public enum ActivityType { Discount(0, "折扣"), FullReduce(1, "满减"), FullGive(2, "满赠"); private int code; private String desc; ActivityType(int code, String desc) { this.code = code; this.desc = desc; } }
定义一个实体类商品信息和一个活动信息,对应关系商品对应的一个或多个活动。
@Data @Accessors(chain = true) public class GoodsInfo { /** * 主键 */ private Integer id; /** * 商品编号 */ private String goodsId; /** * 商品价格 */ private BigDecimal amount; /** * 商品名称 */ private String goodsName; /** * 活动信息 */ private List<ActivityInfo> activityInfos; }
@Data public class ActivityInfo { /** * 主键 */ private String id; /** * 活动类型 */ private ActivityType activityType; /** * 折扣率 */ private BigDecimal discountRate; /** * 满赠-满 */ private Integer fullCount; /** * 满赠-赠 */ private Integer fullGive; /** * 满减-满 */ private BigDecimal fullAmount; /** * 满减-减 */ private BigDecimal fullReduce; }
手动创建数据:
@Repository public class GoodsStore { private List<GoodsInfo> goodsInfos = new ArrayList<GoodsInfo>(); public GoodsStore() { //商品A参加折扣9折活动,又参加了满15元减2元活动 ActivityInfo goodsAActivity = new ActivityInfo().setActivityType(ActivityType.Discount) .setDiscountRate(new BigDecimal("0.9")); ActivityInfo goodsAActivity1 = new ActivityInfo().setActivityType(ActivityType.FullReduce) .setFullAmount(new BigDecimal("15")).setFullReduce(new BigDecimal("2")); List<ActivityInfo> goodsAActivitys = new ArrayList<>(); goodsAActivitys.add(goodsAActivity); goodsAActivitys.add(goodsAActivity1); GoodsInfo goodsA = new GoodsInfo().setId(1).setGoodsId("T0001") .setGoodsName("商品A").setAmount(new BigDecimal("20")).setActivityInfos(goodsAActivitys); //商品B参加满15元减2元活动 ActivityInfo goodsBActivity = new ActivityInfo().setActivityType(ActivityType.FullReduce) .setFullAmount(new BigDecimal("15")).setFullReduce(new BigDecimal("2")); List<ActivityInfo> goodsBActivitys = new ArrayList<>(); goodsBActivitys.add(goodsBActivity); GoodsInfo goodsB = new GoodsInfo().setId(2).setGoodsId("T0002") .setGoodsName("商品B").setAmount(new BigDecimal("25")).setActivityInfos(goodsBActivitys); //商品C参加满2件赠送1件商品 ActivityInfo goodsCActivity = new ActivityInfo().setActivityType(ActivityType.FullGive) .setFullCount(2).setFullGive(1); List<ActivityInfo> goodsCActivitys = new ArrayList<>(); goodsCActivitys.add(goodsCActivity); GoodsInfo goodsC = new GoodsInfo().setId(3).setGoodsId("T0003") .setGoodsName("商品C").setAmount(new BigDecimal("12")).setActivityInfos(goodsCActivitys); //商品D参加折扣9折活动,又参加了满15元减2元,满2件赠送1件活动 ActivityInfo goodsDActivity = new ActivityInfo().setActivityType(ActivityType.Discount) .setDiscountRate(new BigDecimal("0.9")); ActivityInfo goodsDActivity1 = new ActivityInfo().setActivityType(ActivityType.FullReduce) .setFullAmount(new BigDecimal("15")).setFullReduce(new BigDecimal("2")); List<ActivityInfo> goodsDActivitys = new ArrayList<>(); ActivityInfo goodsDActivity2 = new ActivityInfo().setActivityType(ActivityType.FullGive) .setFullCount(2).setFullGive(1); goodsDActivitys.add(goodsDActivity); goodsDActivitys.add(goodsDActivity1); goodsDActivitys.add(goodsDActivity2); GoodsInfo goodsD = new GoodsInfo().setId(4).setGoodsId("T0004") .setGoodsName("商品D").setAmount(new BigDecimal("15")).setActivityInfos(goodsDActivitys); goodsInfos.add(goodsA); goodsInfos.add(goodsB); goodsInfos.add(goodsC); goodsInfos.add(goodsD); } public List<GoodsInfo> getGoodsInfos() { return goodsInfos; } }
创建一个活动计算的接口:IActivityCalc,方法有个计算,返回参加的活动内容以及金额,我实体类简单点,返回参加的内容用字符串拼接返回,不再具体返回指定的满减赠折扣等字段。
public interface IActivityCalc { /** * 计算活动 * * @param order * @param contentDto 用来存返回数据 * @param chain */ void calc(SubmitOrderDto order, ActivityContentDto contentDto, ActivityChain chain); }
在Dto下创建ActivityContentDto对象
@Data public class ActivityContentDto { /** * 活动内容 */ private String content; /** * 付款金额 */ private BigDecimal amount; }
再创建个下单的Dto
@Data @Accessors(chain = true) public class SubmitOrderDto { /** * 商品编号 */ private String goodsId; /** * 商品数量 */ private Integer goodsCount; }
其中活动链ActivityChain 如下
public class ActivityChain implements IActivityCalc { private List<IActivityCalc> activityCalcList; private int index = 0; public ActivityChain() { activityCalcList = ActivityExecutorUtil.activityCalcMap.values().stream().collect(Collectors.toList()); } @Override public void calc(SubmitOrderDto order, ActivityContentDto contentDto, ActivityChain chain) { if (index == activityCalcList.size()) { return; } IActivityCalc activityCalc = activityCalcList.get(index); index++; activityCalc.calc(order, contentDto, chain); } }
还要再弄个注册机制,不能像之前那样放入ActivityChain,因为每次用它必须要new新的对象。
我想来下这个注册机制在spring boot 执行完后再进行注册,而springboot项目启动成功后执行一段代码的两种方式:实现ApplicationRunner 和CommandLineRunner 。选择其中之一
@Component public class ApplicationRunnerImpl implements ApplicationRunner { @Autowired private Map<String, IActivityCalc> activityCalcMap; @Override public void run(ApplicationArguments args) throws Exception { ActivityExecutorUtil.activityCalcMap = activityCalcMap; } }
这里需要一个单例util工具,让人访问
public class ActivityExecutorUtil { public static Map<String, IActivityCalc> activityCalcMap; }
继续写每个活动需要实现的类型,使用策略模式+模板方法模式
创建个模板抽象类写公用方法并实现活动算计
public abstract class BaseActivityProcessor implements IActivityCalc { @Autowired private GoodsStore goodsStore; public GoodsInfo getGoodsInfo(String goodsId) { //获取商品参加的所有活动内容 List<GoodsInfo> goodsInfos = goodsStore.getGoodsInfos(); GoodsInfo goodsInfo = goodsInfos.stream().filter(x -> x.getGoodsId().equals(goodsId)).findFirst().orElse(null); return goodsInfo; } public ActivityInfo getActivityInfo(GoodsInfo goodsInfo, ActivityType type) { return goodsInfo.getActivityInfos().stream().filter(x -> x.getActivityType().equals(type)).findFirst().orElse(null); } }
创建折扣活动来继承这个抽象模板
@Service("Discount") public class DiscountActivity extends BaseActivityProcessor { @Override public void calc(SubmitOrderDto order, ActivityContentDto contentDto, ActivityChain chain) { GoodsInfo goodsInfo = getGoodsInfo(order.getGoodsId()); //获取对应的活动 ActivityInfo activityInfo = getActivityInfo(goodsInfo, ActivityType.Discount); //有活动参加 BigDecimal price = goodsInfo.getAmount().multiply(new BigDecimal(order.getGoodsCount().toString())); //优先设置金额,反之以下活动没有金额比较 contentDto.setAmount(price); Optional.ofNullable(activityInfo).ifPresent(x -> { //折扣价格 BigDecimal discountPrice = activityInfo.getDiscountRate().multiply(price); contentDto.setAmount(discountPrice); contentDto.setContent(String.format("%s,参加%s活动", goodsInfo.getGoodsName(), "9折")); }); chain.calc(order, contentDto, chain); } }
创建满减活动
@Service("FullReduce") public class FullReduceActivity extends BaseActivityProcessor { /** * 计算活动 * * @param order * @param contentDto * @param chain */ @Override public void calc(SubmitOrderDto order, ActivityContentDto contentDto, ActivityChain chain) { GoodsInfo goodsInfo = getGoodsInfo(order.getGoodsId()); //获取对应的活动 ActivityInfo activityInfo = getActivityInfo(goodsInfo, ActivityType.FullReduce); Optional.ofNullable(activityInfo).ifPresent(x -> { //金额大于满金额 if (contentDto.getAmount().compareTo(activityInfo.getFullAmount()) > -1) { contentDto.setAmount(contentDto.getAmount().subtract(activityInfo.getFullReduce())); contentDto.setContent(String.format("%s %s,参加%s活动", Optional.ofNullable(contentDto.getContent()).orElse(""), goodsInfo.getGoodsName(), "满减")); } }); chain.calc(order, contentDto, chain); } }
创建满赠活动
@Service("FullGive") public class FullGiveActivity extends BaseActivityProcessor { /** * 计算活动 * * @param order * @param contentDto * @param chain */ @Override public void calc(SubmitOrderDto order, ActivityContentDto contentDto, ActivityChain chain) { GoodsInfo goodsInfo = getGoodsInfo(order.getGoodsId()); //获取对应的活动 ActivityInfo activityInfo = getActivityInfo(goodsInfo, ActivityType.FullGive); Optional.ofNullable(activityInfo).ifPresent(x -> { if (order.getGoodsCount() >= activityInfo.getFullCount()) { contentDto.setContent(String.format("%s %s,参加%s活动", Optional.ofNullable(contentDto.getContent()).orElse(""), goodsInfo.getGoodsName(), "满赠")); } }); chain.calc(order, contentDto, chain); } }
以上写好来之后,我们开始启动测试,用spring boot test 单元测试运行测试
@RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTest { @Test public void goodsTest() { List<SubmitOrderDto> orderDtos = new ArrayList<>(); orderDtos.add(new SubmitOrderDto().setGoodsId("T0001").setGoodsCount(2)); orderDtos.add(new SubmitOrderDto().setGoodsId("T0002").setGoodsCount(3)); orderDtos.add(new SubmitOrderDto().setGoodsId("T0003").setGoodsCount(3)); orderDtos.add(new SubmitOrderDto().setGoodsId("T0004").setGoodsCount(2)); for (SubmitOrderDto orderDto : orderDtos) { ActivityChain activityChain = new ActivityChain(); ActivityContentDto contentDto = new ActivityContentDto(); activityChain.calc(orderDto, contentDto, activityChain); System.out.println(JSON.toJSONString(contentDto)); } } }
输出结果:
{"amount":34.0,"content":"商品A,参加9折活动 商品A,参加满减活动"} {"amount":73,"content":" 商品B,参加满减活动"} {"amount":36,"content":" 商品C,参加满赠活动"} {"amount":25.0,"content":"商品D,参加9折活动 商品D,参加满赠活动 商品D,参加满减活动"}
总结:
这个例子处理核心是策略模式,调用管理是责任链模式,如果没有责任链模式,则需要for循环来遍历,如果我这边需求添加个按照指定顺序执行,则需要在注册的时候处理。
再来看下ActivityChain这个对象,为啥要实现IActivityCalc接口的calc方法,是为了再调用进入下一步链接环节。
我们再来增加一个需求,就是活动顺序目前是指定,因为注入的时候是无序的,添加按照指定顺序
解决方案添加一个自定义注解,来表示顺序
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) @Documented public @interface ExeSeq { int value() default 1; }
@Service("Discount") @ExeSeq(1) public class DiscountActivity extends BaseActivityProcessor @Service("FullReduce") @ExeSeq(2) public class FullReduceActivity extends BaseActivityProcessor @Service("FullGive") @ExeSeq(3) public class FullGiveActivity extends BaseActivityProcessor
在注册的时候需要重新保证顺序
@Component public class ApplicationRunnerImpl implements ApplicationRunner { @Autowired private Map<String, IActivityCalc> activityCalcMap; @Override public void run(ApplicationArguments args) throws Exception { Map<String, IActivityCalc> resMap = new LinkedHashMap<>(); Map<Integer, Map.Entry<String, IActivityCalc>> sortMap = new TreeMap<>(); for (Map.Entry<String, IActivityCalc> activityCalcEntry : activityCalcMap.entrySet()) { IActivityCalc activityCalc = activityCalcEntry.getValue(); ExeSeq exeSeq = activityCalc.getClass().getAnnotation(ExeSeq.class); if (exeSeq != null) { sortMap.put(exeSeq.value(), activityCalcEntry); } else { resMap.put(activityCalcEntry.getKey(), activityCalcEntry.getValue()); } } if (sortMap.size() > 0) { sortMap.entrySet().stream().sorted(Map.Entry.comparingByKey()) .forEach(x -> resMap.put(x.getValue().getKey(), x.getValue().getValue())); } ActivityExecutorUtil.activityCalcMap = resMap; } }