策略模式的多种实现

3分钟看懂设计模式01:策略模式 那我们知道了策略模式怎么实现,也就是已经有了一把锤子在手上了,那什么时候用这把锤子呢?1. 系统中需要动态地在几种算法中选择一种。2. 一个对象有很多的行为,如果不用策略模式就只能用一大堆的if…else…来实现。3. 不希望客户端知道复杂的、与算法相关的数据结构。在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。1.假设平台支持多种支付方式,比如微信、支付宝、银行卡等。2.如果你有一个应用程序,它可以以多种格式输出数据,比如XML、JSON或CSV。3. 阅读详情

最近几天好好补了下血,才恢复了点精力。所以有了一点写些啥的欲望,那就写一下设计模式好了。

设计模式,相信大家应该都或多或少的接触过。总的来说,设计模式是一些前辈们在开发过程中面对一些常见问题抽象出来的通用解决方案,而且也经过了相当长时间的验证,普适性极强,对于大多数问题基本上都能解决。当然如果自己的业务确实比较特别,那现有的设计模式可能也确实无法满足。而且咱们这个行业也是在发展的,现在适用的设计模式可能过一段时间就不怎么适用了,也可能有些大佬发现了更好的设计抽象出来之后成了另一种设计模式。当然,现在的真实情况大多数是对多个设计模式的组合以及变种等。

注意:设计模式不能滥用哦,不合理的使用设计模式反而会使代码变得更加复杂,所以不要炫技。

介绍了这么多,那么什么是策略模式呢?顾名思义,就是当同一个行为可以有不同的策略的时候,通过某种条件(策略)选择某一个具体的策略实现执行方法。比如支付,支付本身是一个行为,但是可以选择微信支付、支付宝支付、银联支付和聚合支付等多种策略,这种就可以使用策略模式。可以减少很多if else语句块。

策略模式的优缺点:

优点:

  1. 避免多重的条件判断
  2. 易扩展

缺点:

  1. 代码量增多
  2. 策略实现需要对外暴露

可以先看看如果没有使用策略模式,那我们在遇到扩展时是怎么处理的。就拿支付来举例吧。伪代码如下:

public String doPay(PayParam payParam) {
    // 先验
    preCheck(payParam);
    String result = null;
    if(payParam.getPayChannelEnum() == PayChannelEnum.ALI) {
        // 支付宝支付的一些校验和处理
        result = doAli(payParam);
    } else if (payParam.getPayChannelEnum() == PayChannelEnum.WECHAT) {
        // 微信支付的一些校验和处理
        result = doWechat(payParam);
    } else {
        // 其他情况的处理
        result = null;
    }
    // 结果处理
    result = resultHandle(result);
    return result;
}

这个时候如果又加上了银联支付,那么就得在这个if else的分支上进行修改,再加一个分支,如果我们的处理不只是这么简单的话,甚至还涉及到代码结构的调整,先验和结果处理调整,这不符合代码封装的开闭原则。而且也会改得很令人头痛。

策略实现

使用策略模式实现也是有好几种方法的,下面我就来讲讲我在真实场景中使用过的策略实现吧。

第一种

这种方式非常简单,简直就是上面的if else套了个封装的壳子而已。定义一个接口,然后接口有几个实现,在根据if else判断需要构造哪个实现。然后调用执行方法。

// 策略接口类,用于抽象支付的行为
public interface PayStrategy {

    String doPay(PayParam payParam);
}

// 具体支付宝支付的实现
public class AliPayStrategyImpl implements PayStrategy {

    @Override
    public String doPay(PayParam payParam) {
        return "alipay";
    }
}

// 具体微信支付的实现
public class WechatPayStrategyImpl implements PayStrategy {

    @Override
    public String doPay(PayParam payParam) {
        return "wechatpay";
    }
}

// 调用支付的入口
public class OrderPay {

    private void preCheck(PayParam payParam) {

    }

    private String resultHandle(String result) {
        // do something
        return "result";
    }


    public String doPay(PayParam payParam) {
        // 先验
        preCheck(payParam);
        PayStrategy payStrategy = null;
        if(payParam.getPayChannelEnum() == PayChannelEnum.ALI) {
            payStrategy = new AliPayStrategyImpl();
        } else if (payParam.getPayChannelEnum() == PayChannelEnum.WECHAT) {
            payStrategy = new WechatPayStrategyImpl();
        }
        String result = payStrategy == null ? null : payStrategy.doPay(payParam);
        // 结果处理
        result = resultHandle(result);
        return result;
    }
}

这种如果需要扩展的话,就是再加一个实现类,然后if else这里构造实现类的地方再增加一个分支,其实也没有完全解决掉对修改关闭的问题。当然如果将bean交给spring管理的话又会方便很多。

第二种(推荐)

这种就比较有趣了,是使用枚举类来进行多策略的实现。下面我们就一起来看看吧。

public enum PayChannelEnum {

    ALI() {
        @Override
        public String doPay(PayParam payParam) {
            return "alipay";
        }
    },
    WECHAT() {
        @Override
        public String doPay(PayParam payParam) {
            return "wechatpay";
        }
    },
    ;

    public abstract String doPay(PayParam payParam);
}

这种方式,代码看着很简洁,逻辑也很清晰,使用也很方便;但是有一个缺点,如果你需要用到spring注入的对象的话,这种方式就无法工作了。

第三种

接下来这种就需要依赖于Spring的IOC和DI了,这两者是啥我这里就不展开介绍了,如果不了解的小伙伴,可以去搜索一下,大致的就是帮我们管理实现类的,类构造和注入等。

// 策略接口类,用于抽象支付的行为。依然需要一个策略接口用来抽象支付的行为
public interface PayStrategy {

    String doPay(PayParam payParam);
}

// 具体支付宝支付的实现
@Component("ALI") // 重点,需要指定实例bean时的名称
public class AliPayStrategyImpl implements PayStrategy {

    @Override
    public String doPay(PayParam payParam) {
        return "alipay";
    }
}
// 具体微信支付的实现
@Component("WECHAT") // 重点,需要指定实例bean时的名称
public class WechatPayStrategyImpl implements PayStrategy {

    @Override
    public String doPay(PayParam payParam) {
        return "wechatpay";
    }
}

// 调用支付的入口
@Component // 重点
public class OrderPay {

    @Autowired // 重点
    private Map<String , PayStrategy> payStrategyMap;

    private void preCheck(PayParam payParam) {
		// do something check
    }

    private String resultHandle(String result) {
        // do something
        return "result";
    }


    public String doPay(PayParam payParam) {
        preCheck(payParam);
        String result = payStrategyMap.get(payParam.getPayChannelEnum().name()).doPay(payParam);
        return resultHandle(result);
    }
}

当然这里我们是没有考虑那些异常情况,以及策略未找到之类的兜底或者处理等。实际编程是需要都考虑的。

可以看到在这段实现中我是标了几个重点。

  1. 首先是各自的实现类需要指定bean名称(策略指定的枚举名称),这是为了在依赖注入的时候方便我们匹配用的
  2. 其次是在调用的入口OrderPay一定也需要加上Spring的bean声明注解,这是为了将bean统一交给Spring管理,才能将策略实现注入
  3. payStrategyMap这个域对应的是个map,key为实例名,value为对应的实例。这样在调用doPay时就可以根据传入的策略枚举找到实例然后执行具体方法。

缺点也很明显,需要指定实例名称,且需要是一个静态常量,所以也不能直接使用枚举的名称。这样容易出现两个问题。

  1. 实例名称如果和其他实例冲突会导致启动服务就报错,那换个实例名称的话,枚举也得跟着改动,容易引起其他依赖的问题。
  2. 枚举的name和实例名称需要一一对应,如果有哪里大小写或者字符敲错了等问题,不太容易排查。

第四种(推荐)

这种目前是我常用的一种策略实现,不过呢,如果策略本身比较少的话,写这个轮子可能会觉得比较浪费,因为本身的架子代码就挺多的了。话不多说,show you the code。

// 很熟悉了吧?一定需要的一个策略接口
public interface PayStrategy {

    String doPay(PayParam payParam);
}

// 重要。实现策略的一个抽象类。用于定义所有的策略实现的基类
public abstract class AbstractPayStrategy implements PayStrategy {
	// 重要。新定义一个策略实现类需要实现的策略。返回对应的枚举
    public abstract PayChannelEnum strategy();
}

// 基础实现,注意,此处是实现顶上的策略接口。在调用入口地方也是注入此实现。
@Component
@Primary // 重要。用于注入时优先选择此实例
public class PayStrategyImpl implements PayStrategy {

    private Map<PayChannelEnum, AbstractPayStrategy> payStrategies;

    // 构造器注入。然后转化成map存储。
    public PayStrategyImpl(List<AbstractPayStrategy> payStrategyList) {
        payStrategies = payStrategyList.stream().collect(Collectors.toMap(AbstractPayStrategy::strategy, Function.identity()));
    }

    // 实际的支付接口都是调用此方法
    @Override
    public String doPay(PayParam payParam) {
        return payStrategies.get(payParam.getPayChannelEnum()).doPay(payParam);
    }
}

// 注意这里的改动,此处是继承抽象类,并且实现返回策略枚举的接口
@Component
public class AliPayStrategyImpl extends AbstractPayStrategy {

    @Override
    public String doPay(PayParam payParam) {
        return "alipay";
    }

    @Override
    public PayChannelEnum strategy() {
        return PayChannelEnum.ALI;
    }
}

@Component
public class WechatPayStrategyImpl extends AbstractPayStrategy {

    @Override
    public String doPay(PayParam payParam) {
        return "wechatpay";
    }

    @Override
    public PayChannelEnum strategy() {
        return PayChannelEnum.WECHAT;
    }
}

到此整体的架子就已经搭建好了,而具体的调用入口也是很简单的一个注入即可。

@Autowired
private OrderPay orderPay;

这种实现代码量很多,但是基本上模糊了选择策略的那部分逻辑,而且有个很重要的优点是只需要维护一个策略枚举,能很方便的将实现与策略枚举映射起来。

其中用到的知识点有:

  1. 构造器注入。PayStrategyImpl有定义一个List参数的构造方法,实例化该类时会自动将Spring管理的策略bean注入进去
  2. Spring的Primary注入,当接口有多个实现时,使用Autowired注解的话,Spring并不知道选择哪个bean注入,所以如果加上Primary时就可以给Spring决策,优先注入那个实例。

类图如下:

最后

关于策略模式的多种实现,到这里就算结束了。也是有写几个我平常有使用的策略模式实现,当然还有一些其实可以改造一下形成自己代码风格的实现方式,比如第三种,可以再定义一个Map,在bean实例化时将自身put到Map中(可以通过@PostConstruct注解的方式,或者实现InitializingBean的方式等),然后剩下的工作就水到渠成了。

每种实现其实都是有一些优缺点的,也并不一定适用于所有人和所有场景,希望大家能够找到符合自己代码风格的实现思路,也希望大家能够通过这一节对策略模式有一个清晰一点的认识,在实际工作中能够更简洁清晰的写自己的bug咯~。那就这样吧,谢谢大家的观看,我们下期见。

 

策略模式详解 1.简介 在现实生活中长长遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超时促销可以采用打折、送商品、送积分等方法。 在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。 如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加。删除或更换算法要修改原代码,不易维护,违背开闭原则。如 阅读详情

相关推荐

策略模式在若依框架多种登录方式实现中的深度应用与实践

策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互换。策略模式让算法独立于使用它的客户端而变化,这通常通过定义一系列的算法类来实现,这些算法类都实现一个共同的接口,并在运行时通过客户端代码来动态选择具体的算法实现策略模式多种登录方式的实现中提供了一种清晰、可扩展和易于维护的解决方案。通过将每种登录方式封装为独立的策略类,并在运行时通过上下文类动态地选择具体的策略实现,我们可以轻松地应对多样化的登录需求,同时保持代码的清晰和可扩展性。

ysy15350的专栏 803

策略模式,看一篇就够了

策略模式中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context对象。策略对象改变 context 对象的执行算法。...

weixin_45866849的博客 9434

采用策略模式实现订单支付多种方式

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。{}说明:定义抽象支付接口。本文对策略模式进行了讲解,策略模式在实际开发过程中应用的比较广泛,所以大家还是需要熟练的掌握其用法,如有疑问,请随时反馈。

weixin_62421895的博客 1706

设计模式-策略模式

策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。

鱼粉大王 8496

java中简单的策略模式实现

java中简单的策略模式实现

m0_72167535的博客 2573

策略模式实现多种支付】

使用策略模式实现多种支付方式

lyc000412的博客 4633

策略模式实现多种支付方式

使用策略模式优雅的实现多种支付方式(支付宝、微信),或者多种支付场景(订单、维修金)的业务,且方便扩展。 下例是使用注解配合反射方式,扫描到所有的具体的支付策略并放到map集合中,然后根据前端传递来的支付类型参数,选择对应的支付策略,完成支付过程。 如上图: PayStrategy是支付策略接口; OrderPay(订单支付),RepairPay(维修金支付)是具体的支...

dianhe6449的博客 1876

spring使用策略模式实现多种场景登录方式(策略模式

spring使用策略模式实现多种场景登录方式 @Autowired注解可以帮我们自动注入我们想要的 Bean。 如果只是简单使用@Autowired会遇到spring IOC容器中一个接口有多个实现的情况,spring无法识别具体的实现类,如果不是策略模式,我们可以进行具体的指定@Qualifier和@primary来避免bean冲突的情况。但在策略模式中是不行的。 而除了这个基本功能之外, @Autowired 还有更加强大的功能,还可以注入指定类型的数组,List/Set 集合,甚至还可以是 Map

九曜 2204

策略模式实现商品多种优惠策略

策略模式 Strategry 特点: 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。 优点 策略模式遵循了开闭原则,实现了代码的解耦合。在使用策略模式的时候,如果你想要拓展新策略,可以不改动任何旧逻辑,可以很方便的添加新逻辑。 ...

weixin_44224292的博客 1091

Springboot使用策略模式和自定义注解实现多种判题策略切换

Springboot使用策略模式和自定义注解实现多种判题策略的声明式切换,简洁美观,符合开闭原则,扩展性强。

hrh的博客 839

策略模式(多种登录方式实现) Java

主要是用于一些业务场景相似但业务处理的操作与返回结果不同的的时候,避免使用大量的if else判断语句,不利于代码维护,使代码的业务逻辑变得更加清晰,便于阅读理解

weixin_45503079的博客 2387

策略模式-实现多种支付方式

策略模式-实现多种支付方式

weixin_38351847的博客 1296

设计模式之策略模式(多种实现方式)

介绍 策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。 主要是为了代码的解耦,避免每次新增策略的时候都影响到之前的策略逻辑(开闭原则),降低代码的耦合有利于后面代码的延伸(新增行为),并确保每次新增策略(功能)时都不会影响到原来的代码逻辑! 策略模式UM...

v916975078的博客 1855

策略模式——多种发票上传实现案例

策略模式

qq_36138652的博客 416
上一篇: Java基础(四)—— HashCode和Equals
下一篇: 如何实现一个LRU算法
aischen
博客等级 码龄14年 0粉丝 10原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值