跳转至

策略模式

图片的样式

➊单纯策略模式,没有结合其他设计模式

感觉策略模式更适合在实际开发中,有这样一种业务:它是有好多的逻辑,但是有很多不同的进入情况,可以是业务需要也可以是逻辑需要, 这个模式对于代码,可以简化业务逻辑,不至于将代码写成一坨,长长的几十上百行,同时又具有很好的扩展能力,可以很方便的加入新逻辑 最典型的场景,就是if条件的逻辑判断简化。

但是它也有缺点

1️⃣如果 if-else的判断情况很多,那么对应的具体策略实现类也会很多
2️⃣没法俯视整个分派的业务逻辑,因为具体的逻辑被分拆

一次面试中,阿里面试官提到策略模式,他不这样认为,觉得都是一样需要看很多分支逻辑的,无论怎么改代码, 基础的业务逻辑就是无可更改的,天王老子来了该写的逻辑就是要写,该看的逻辑还是要看,策略模式更像是整理代码的一种方法, 就是不用也没法“俯视”所有逻辑。这也是一种全新的理解。

不大喜欢一些理论性质的解释,直接样例理解,自己更能懂,代码就是一种语言,它自己会表达清楚

🏀代码样例

首先要有一个接口,里面要包含用于判断每个业务的不同,每个业务具体的实现的方法,比如要计算快递的收费, 就要有一个判断每个快递的类型的方法,每一种快递到底是如何计费的。

/**
 * 策略模式接口设计
 */
public interface LogisticsService {

    /**
     * 1️⃣需要用来判断具体走那个逻辑
     */
    boolean isCurrentLogistics(Integer type);

    /**
     * 2️⃣计算逻辑
     */
    BigDecimal calculateFee(TransferFeeRequest transferFeeRequest);
}

同时具体如何计费,就要有快递的运输距离,单价,最重要的快递类型,这些都是具体的业务,参数到底如何写是业务决定的, 也可以有快递的重量,体积,形状等,每家快递公司的收费标准不一样,还有一些政策更改,体量优惠减免,是否有保价, 都会决定具体的逻辑。

@Data
public class TransferFeeRequest {

    /**
     * 距离
     */
    private BigDecimal distance;

    /**
     * 单价
     */
    private BigDecimal unitPrice;

    /**
     * 快递类型
     */
    private Integer type;
}

一般这种业务都是写成具体的服务,比如合作的快递公司有京东、顺丰、圆通、中通,那就是四个服务

/**
 * JD快递服务
 */
@Service
public class JDTransfercompany implements LogisticsService {
    private final BigDecimal pickFee = BigDecimal.TEN;

    private final BigDecimal minDistance = BigDecimal.valueOf(80);

    @Override
    public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
        BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
                minDistance : transferFeeRequest.getDistance();
        // do business
        return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
    }

    @Override
    public boolean isCurrentLogistics(Integer type) {
        return Objects.equals(type, 1);
    }
}
/**
 * 顺丰快递服务
 */
@Service
public class SFTransfercompany implements LogisticsService {
    private final BigDecimal pickFee = BigDecimal.TEN;

    private final BigDecimal minDistance = BigDecimal.valueOf(60);

    @Override
    public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
        BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
                minDistance : transferFeeRequest.getDistance();
        // do business
        return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
    }

    @Override
    public boolean isCurrentLogistics(Integer type) {
        return Objects.equals(type, 2);
    }
}
/**
 * 中通快递服务
 */
@Service
public class ZTTransfercompany implements LogisticsService {
    private final BigDecimal pickFee = BigDecimal.TEN;

    private final BigDecimal minDistance = BigDecimal.valueOf(40);

    @Override
    public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
        BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ? 
                minDistance : transferFeeRequest.getDistance();
        // do business
        return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
    }

    @Override
    public boolean isCurrentLogistics(Integer type) {
        return Objects.equals(type, 3);
    }
}
/**
 * 圆通快递服务
 */
@Service
public class YTTransfercompany implements LogisticsService {
    private final BigDecimal pickFee = BigDecimal.TEN;

    private final BigDecimal minDistance = BigDecimal.valueOf(20);

    @Override
    public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
        BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
                minDistance : transferFeeRequest.getDistance();
        // do business
        return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
    }

    @Override
    public boolean isCurrentLogistics(Integer type) {
        return Objects.equals(type, 4);
    }
}

都是类似的,本来可能是if-else一撸到底,现在变成分开实现(例子只是例子,比较简)

那我们在使用的时候就简单一些了,用stream流就能调用

🐔使用方式

    /**
     * 策略模式需要把所有的可能都注入进来
     */
    private final List<LogisticsService> logisticsServices;

    public BigDecimal calculateFee(@RequestBody TransferFeeRequest transferFeeRequest) {
        // 遍历,根据每个类中的具体实现类来判断到底走那个逻辑
        LogisticsService logisticsService = logisticsServices.stream()
                .filter(logistics -> logistics.isCurrentLogistics(transferFeeRequest.getType()))
                .findFirst()
                .orElse(null);
        // 一定要有对应匹配为空对不上的兜底逻辑,也可以单独列出一个实现类来写
        if (logisticsService == null) {
            throw new RuntimeException("没有对应的快递计算方式");
        }
        return logisticsService.calculateFee(transferFeeRequest);
    }

我现在也只讲设计模式如何用,使用场景等,但是对它的理解实在是见仁见智了。其他理论化的介绍就不写了。


➋策略模式结合工厂方法模式

这个模式相较于单纯策略模式,加入了工厂方法,将一些获取逻辑再度封装一下,这样使用的时候只要简单的获取, 没有单纯策略模式那样需要自己写获取对象并判断的逻辑

🏀代码样例

首先还是一样需要一个接口,里面写一些业务需要的方法

public interface ActionService {
    /**可以在下面自己扩展新的方法以适应实际业务*/
    void doAction();
}

之后就是实现它,每一个实现类,对应一种不同的业务分支情况

@Service
public class ActionService1 implements ActionService {
    @Override
    public void doAction() {
        System.out.println("该分支下业务实际要做得而事情");
    }
}
@Service
public class ActionService2 implements ActionService {
    @Override
    public void doAction() {
        System.out.println("该分支下业务实际要做得而事情");
    }
}

最后也是最重要的一步,有一个工厂类方便封装判断逻辑(这里将理解写到代码注释里)

public class ActionServiceFactory {
    /**
     * 维护一个MAP,作为用来放策略的容器,可以随时添加新的
     */
    private static final Map<String, ActionService> ACTION_SERVICE_MAP = new HashMap<>();

    static {
        ACTION_SERVICE_MAP.put("action1", new ActionService1());
        ACTION_SERVICE_MAP.put("action2", new ActionService2());
        // .....继续添加新策略
    }

    private ActionServiceFactory() {
    }

    /**
     * 使用内部类单例模式,统一对象的获取
     */
    private static class SingletonHolder {
        private static final ActionServiceFactory INSTANCE = new ActionServiceFactory();
    }

    public static ActionServiceFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 这里就是将获取判断的逻辑封装进工厂里面,不需要使用的时候写,不然用一次写一次
     */
    private static ActionService getActionService(String actionCode) {
        ActionService actionService = ACTION_SERVICE_MAP.get(actionCode);
        // 一定要有兜底逻辑
        if (ObjectUtil.isNull(actionService)) {
            throw new RuntimeException("非法 actionCode");
        }
        return actionService;
    }

    /**
     * 统一获取,使得该方法作为工厂的生产物
     */
    public void doAction(String actionCode) {
        getActionService(actionCode).doAction();
    }
}

🐔使用方式

这样的话,使用的时候就很简洁又简单了,一行代码就行,无论在那里使用都一样

@RestController
@RequestMapping("/test")
public class TestController {

    @PostMapping("")
    public void strategyFactory(String actionCode) {
        // 这里的actionCode也要根据实际业务改动
        ActionServiceFactory.getInstance().doAction(actionCode);
    }
}

➌ Map+函数式接口的变种策略模式

这个就没有前面的策略模式那样,需要定义一个接口加实现类,直接将你的业务放到服务里面, 每一个策略就是一个方法,这样就可以避免业务策略多的时候,实现类很多的情况,同时又保留了 工厂模式的简单获取的优点

🏀代码样例

首先写一个服务,里面一个业务策略就是一个方法,每个方法写策略对应的逻辑

@Service
public class GrantTypeSerive {
    public String redPaper(String resourceId) {
        // 红包的发放方式
        return "每周末9点发放";
    }
    public String shopping(String resourceId) {
        // 购物券的发放方式
        return "每周三9点发放";
    }
    public String QQVip(String resourceId) {
        // qq会员的发放方式
        return "每周一0点开始秒杀";
    }

    public String WXVip(String resourceId) {
        // 微信会员的发放方式
        return "微信会员";
    }
}

然后再写一个服务用于封装判断逻辑,方便获取

/**
 * 就是将对应的if-else分支判断依据作为key,要走的逻辑作为value,匹配对应结果达到目的
 */
@Service
@RequiredArgsConstructor
public class QueryGrantTypeService {
    private final GrantTypeSerive grantTypeSerive;
    private final Map<String, Function<String, String>> grantTypeMap = Maps.newHashMap();

    /**
     * 初始化业务分派逻辑,代替了if-else部分
     * key: 优惠券类型
     * value: lambda表达式,最终会获得该优惠券的发放方式
     */
    @PostConstruct
    public void dispatcherInit() {
        grantTypeMap.put("红包", grantTypeSerive::redPaper);
        grantTypeMap.put("购物券", grantTypeSerive::shopping);
        grantTypeMap.put("qq会员", grantTypeSerive::QQVip);
        grantTypeMap.put("微信会员", grantTypeSerive::WXVip);
    }

    public String getResult(String resourceType) {
        // Controller根据 优惠券类型 `resourceType`、编码`resourceId` 去查询 发放方式`grantType`
        Function<String, String> result = grantTypeMap.get(resourceType);
        if (result != null) {
            // 传入`resourceId` 执行这段表达式获得String型的grantType
            // 这里的id还是需要根据具体的业务区别,有时候可能压根没有这种东西
            return result.apply("resourceId");
        }
        // 兜底逻辑
        return "查询不到该优惠券的发放方式";
    }
}

🐔使用方式

这样只需要注入并获取就可以了(其实也可以在写一个单例,这样都不用注入了,但是这样就和spring的容器有一点冲突)

@RestController
@RequiredArgsConstructor
public class GrantTypeController {

    private final QueryGrantTypeService queryGrantTypeService;

    @PostMapping("/grantType")
    public String test(String resourceName) {
        return queryGrantTypeService.getResult(resourceName);
    }
}

➍ 策略+责任链+组合模式(以合同签章为例)

这个是我在看微信公众号 “Java基基” 的时候了解到的 TODO 整理文章内容

原文链接🔗:《进阶玩法:策略+责任链+组合实现合同签章》