设计模式学习之模板和策略模式实战

前面文章由抽奖活动赠送不同的奖励从而引出了策略模式,定义一个策略接口,不同的奖励发放实现不同的策略。即使后续新增加奖励种类,只要重新实现一个策略即可,符合了设计模式中的开闭原则-对扩展开放对修改关闭。我在一次订单活动功能开发中使用到了策略模式,在开发的过程中发现,订单符合活动条件的判断、发放奖励给用户,可以提升到父类实现,具体的条件判断逻辑可以延迟到子类去实现,从而引出本篇文章的主题:模板模式。

模板模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,这种类型的设计模式属于行为型模式。模板模式中涉及到在父类实现算法骨架,具体步骤在子类实现,所以必须要有抽象类(Java8中的接口的 default 方法貌似也可以实现)。

介绍
  • 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
  • 何时使用:有一些通用的方法。
  • 如何解决:将这些通用算法抽象出来。
  • 关键代码:在抽象类实现,其他步骤延迟到子类实现。
应用实例:
  • JDK中 ReentrantLock中公平锁和非公平锁的实现
  • Spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点:
  • 封装不变部分,扩展可变部分。
  • 提取公共代码,便于维护。
  • 行为由父类控制,子类实现。
缺点:
  • 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景:
  • 有多个子类共有的方法,且逻辑相同。
  • 重要的、复杂的方法,可以考虑作为模板方法。
注意事项:

为防止恶意操作,一般模板方法都加上 final 关键词。

本文示例UML图
模板、策略模式 UML 图
代码结构图

代码结构图

示例代码

1、策略接口,定义了一个活动策略需要实现的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

package com.ydstudio.flashsale.module.pattern.service;

import com.ydstudio.flashsale.common.Result;
import com.ydstudio.flashsale.module.pattern.dto.ActiveOrderDto;

/**
*
* 活动接口
*
* @author Sam
* @date 2020/11/26
* @since 1.7.3
*/
public interface IActiveHandle {
/**
* 返回活动的类型
* ActiveCategoryEnum 枚举
*
* @return
*/
String getCategory();

/**
* 返回活动的详细类型
* ActiveCategoryDetailEnum 枚举
*
* @return
*/
String getCategoryDetail();


/**
* 订单检查
* @param temporaryOrderDto 临时订单
* @return Result
*/
Result checkOrder(ActiveOrderDto temporaryOrderDto);
}

2、活动抽象类包含订单规则是否符合的判断以及符合规则之后发送奖励的算法骨架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.ydstudio.flashsale.module.pattern.service;

import com.ydstudio.flashsale.common.Result;
import com.ydstudio.flashsale.module.pattern.dto.ActiveOrderDto;
import org.springframework.stereotype.Component;

/**
* 活动抽象类,抽取公共方法,
* 把订单是否符合奖励的判断之后发送奖励的公共逻辑在此处实现,
* 订单具体条件的判断延迟由子类去实现.
*
* 策略和模板模式组合使用
*
* @author Sam
* @date 2020/11/26
* @since 1.7.3
*/
@Component
public abstract class AbstractActiveHandle implements IActiveHandle {

/**
* 其他抽象方法
*/
public abstract void otherMethod();


public void otherMethod1() {
System.out.println("其他公用方法");
}

/**
* 外部真正要调用的方法
*
* @param temporaryOrderDto 订单
*/
public final String handle(ActiveOrderDto temporaryOrderDto) {

// 调用接口中需要子类实现的方法
Result result = checkOrder(temporaryOrderDto);
if (!result.isSuccess()) {
System.out.println("不符合奖励发放条件");
return "不符合奖励发放条件";
}
return sendReward(temporaryOrderDto, temporaryOrderDto.getMemberId(), "积分");
}

/**
* 统一的发送奖励的方法
* @param temporaryOrderDto 订单
* @param memberId 用户ID
* @param reward 奖励
*/
private String sendReward(ActiveOrderDto temporaryOrderDto, long memberId, String reward) {
String s = "给用户" + memberId + "的订单" + temporaryOrderDto + "发送奖励" + reward;
System.out.println(s);
return s;
}
}

3、 活动策略的实现类

  • 会籍订单策略实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package com.ydstudio.flashsale.module.pattern.service.impl;

    import com.ydstudio.flashsale.common.Result;
    import com.ydstudio.flashsale.module.pattern.dto.ActiveOrderDto;
    import com.ydstudio.flashsale.module.pattern.service.AbstractActiveHandle;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;

    import java.util.Random;

    /**
    *
    * 会籍订单的处理
    *
    * @author Sam
    * @date 2020/11/26
    * @since 1.7.3
    */
    @Slf4j
    @Service
    public class LevelOrderActiveHandle extends AbstractActiveHandle {
    @Override
    public void otherMethod() {
    System.out.println("会籍订单的实现");
    }

    @Override
    public String getCategory() {
    return "2";
    }

    @Override
    public String getCategoryDetail() {
    return "2";
    }

    @Override
    public Result checkOrder(ActiveOrderDto temporaryOrderDto) {

    Random random = new Random();
    int i = random.nextInt(4);
    if (i >=2){
    return new Result();
    }
    return new Result("不符合会籍订单活动条件","不符合会籍订单活动条件");
    }
    }
  • 酒店订单活动策略实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package com.ydstudio.flashsale.module.pattern.service.impl;


    import com.ydstudio.flashsale.common.Result;
    import com.ydstudio.flashsale.module.pattern.dto.ActiveOrderDto;
    import com.ydstudio.flashsale.module.pattern.service.AbstractActiveHandle;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;

    import java.util.Random;

    /**
    *
    * 酒店订单处理
    *
    * @author Sam
    * @date 2020/11/26
    * @since 1.7.3
    */
    @Slf4j
    @Service
    public class OrderActiveHandle extends AbstractActiveHandle {
    @Override
    public void otherMethod() {
    System.out.println("酒店订单的实现");
    }

    @Override
    public String getCategory() {
    return "1";
    }

    @Override
    public String getCategoryDetail() {
    return "1";
    }

    @Override
    public Result checkOrder(ActiveOrderDto temporaryOrderDto) {

    Random random = new Random();
    int i = random.nextInt(4);
    if (i<=2){
    return new Result();
    }
    return new Result("不符合酒店订单活动条件","不符合酒店订单活动条件");
    }
    }

4 、统一入口提供一个Context 给外部使用

调用方直接使用@Autowired 注入ActiveHandleContext即可使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.ydstudio.flashsale.module.pattern.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
* 负责所有活动处理的入口,根据 getImpl(String categoryDetail)类型来判断调用具体的活动策略
*
* @author Sam
* @date 2020/7/13
* @since 1.6.8
*/
@Slf4j
@Component
public class ActiveHandleContext {

@Autowired
private List<AbstractActiveHandle> activeHandleList;


/**
* 对外的统一入口
* @param categoryDetail 类型
* @return
*/
public AbstractActiveHandle getImpl(String categoryDetail) {
log.info("参数 categoryDetail =[{}] 获取实现 ", categoryDetail);
for (AbstractActiveHandle activeHandle : activeHandleList) {

if (activeHandle.getCategoryDetail().equals(categoryDetail)) {
return activeHandle;
}
}
throw new UnsupportedOperationException("没有该实现");
}

}

父类AbstractActiveHandle实现活动规则判断、发放奖励的算法骨架,子类实现具体的规则判断,这样就实现了代码的优化和公用。

  • 作者: Sam
  • 发布时间: 2020-12-04 21:41:34
  • 最后更新: 2020-12-07 22:02:17
  • 文章链接: https://ydstudios.gitee.io/post/c14af19a.html
  • 版权声明: 本网所有文章除特别声明外, 禁止未经授权转载,违者依法追究相关法律责任!