定义一些列算法类,将每一个算法封装起来,并让它们可以互相替换。
策略模式让算法独立于使用它的客户而变化,是一种对象行为型模式。
以上是策略模式的一般定义,属于是课本内容。
在没有真正理解策略模式之前并不需要对此定义下过多功夫,读一遍直接进入下一章节。
我们应该知道,所谓的设计模式实际上是一种经过检验的、科学高效的、针对某种场景的最佳编程设计实践。
所以要理解某一种设计模式,就必须知道我们什么时候可以用,用之前和用之后到底有什么区别。
练习:
假设现在要设计一个贩卖各类书籍的电子商务网站的购物车系统。
一个最简单的情况就是把所有货品的单价乘上数量,但是实际情况肯定比这要复杂。
比如,本网站可能对所有的高级会员提供每本20%的促销折扣;对中级会员提供每本10%的促销折扣;对初级会员没有折扣。
根据描述,折扣是根据以下的几个算法中的一个进行的:
算法一:对初级会员没有折扣。
算法二:对中级会员提供10%的促销折扣。
算法三:对高级会员提供20%的促销折扣。
给出一本图书,如300元,若是高级会员,则输出价格为240元。
针对以上场景,大多数的我们写的代码就是使用的if…else…。
我们先提前揭晓,这种场景下就是我们使用策略模式的最佳时机。
那在我们尝试使用策略模式改进代码之前,我们必须要问:
if…else到底有什么问题?
public Double calculationPrice(String type, Double originalPrice, int n) {
//中级会员计费
if (type.equals("intermediateMember")) {
return originalPrice * n - originalPrice * 0.1;
}
//高级会员计费
if (type.equals("advancePrimaryMember")) {
return originalPrice * n - originalPrice * 0.2;
}
//普通会员计费
return originalPrice;
}
这种编码方式到底差在哪?
大佬告诉我们说:维护性差。
什么叫维护性差?
就是下次你想加个超级黄金vip会员,以及各种后续会员种类,你就要不断往里加if…else…,这就违反了开闭原则。
这里又有另外两个问题:
什么是开闭原则?我凭啥要遵守开闭原则?
什么是开闭原则?
开闭原则比较好记忆,顾名思义:
对扩展开放,对修改关闭。
大意就是你想改东西,不要改原代码,而是进行扩展代码。
为什么要遵守开闭原则(以及各种乱七八糟的原则)?
简单直接一点就是,这些原则都是巨佬们总结出来的,你如果不懂,你就直接选择相信就好了。
解释一下就是:
系统随着开发的不断进展,需求不断增多,代码越来越长,如果没有合理框架的制约那就只能沦为一个扩展难、维护难的屎山。
所以我们遵守开闭原则就是说需要一个科学合理的框架规范我们的系统熵增,在不修改原代码的基础上让系统拥有灵活性和稳定性。
一句话,上面的代码直接修改原代码,时间久了系统只会沦为屎山。
那怎么使用策略模式改造,而遵守开闭原则呢?
它为所支持的算法声明抽象方法,是所有策略类的父类。它可以使抽象类或者具体类,也可以是接口。
public interface MemberStrategy {
// 一个计算价格的抽象方法
//price商品的价格 n商品的个数
public double calcPrice(double price, int n);
}
它实现了上面抽象策略类的抽象方法。
在实际运行中,这个具体的策略类将会代替在**环境类(Context)**中定义的抽象策略类对象最终执行不同的实现逻辑。
可以看到下面的代码中,三种不同的策略类实现了同一个抽象策略类,每种策略对应一种实现,分别应对一个业务处理方式。
// 普通会员——不打折
public class PrimaryMemberStrategy implements MemberStrategy { // 实现策略
@Override
public double calcPrice(double price, int n) {
return price * n;
}
}
// 中级会员 打百分之10的折扣
public class IntermediateMemberStrategy implements MemberStrategy{
@Override
public double calcPrice(double price, int n) {
double money = (price * n) - price * n * 0.1;
return money;
}
}
// 高级会员类 20%折扣
public class AdvanceMemberStrategy implements MemberStrategy{
@Override
public double calcPrice(double price, int n) {
double money = price * n - price * n * 0.2;
return money;
}
}
这个对我来说一开始很难理解。
主要是不能理解 Context 这个词在这里的意思,再加上网上一大堆直接翻译为“上下文”的文章博客,我直接吐了:
我不理解Context的意思,难道就能理解“上下文”的意思?
还有类似的:事务又是什么东西?
所以我直接不管这个Context是什么东西,直接看代码。
首先他是一个类,我们看这个类里有什么。
一个成员变量memberStrategy
一个构造方法
一个计算价格的方法,内容返回memberStrategy的calcPrice方法
往下看。
/**
* 负责和具体的策略类交互
* 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。
*/
// 上下文类/环境类
public class MemberContext {
// 用户折扣策略接口
private MemberStrategy memberStrategy;
// 注入构造方法
public MemberContext(MemberStrategy memberStrategy) {
this.memberStrategy = memberStrategy;
}
// 计算价格
public double qoutePrice(double goodsPrice, int n){
// 通过接口变量调用对应的具体策略
return memberStrategy.calcPrice(goodsPrice, n);
}
}
接下来看测试类中 Context 类的使用是什么样子的。
// 测试类
public class Application {
public static void main(String[] args) {
// 具体行为策略
MemberStrategy primaryMemberStrategy = new PrimaryMemberStrategy(); // 接口回调(向上转型)
MemberStrategy intermediateMemberStrategy = new IntermediateMemberStrategy();
MemberStrategy advanceMemberStrategy = new AdvanceMemberStrategy();
// 用户选择不同策略
MemberContext primaryContext = new MemberContext(primaryMemberStrategy);
MemberContext intermediateContext = new MemberContext(intermediateMemberStrategy);
MemberContext advanceContext = new MemberContext(advanceMemberStrategy);
//计算一本300块钱的书
System.out.println("普通会员的价格:"+ primaryContext.qoutePrice(300,1));// 普通会员:300
System.out.println("中级会员的价格:"+ intermediateContext.qoutePrice(300,1));// 中级会员 270
System.out.println("高级会员的价格:"+ advanceContext.qoutePrice(300,1));// 高级会员240
}
}
发现了什么?
Context都是被new出来的,new的时候传入的Strategy实现类全部不一样,你传的不一样,将来context.calcPrice()执行的逻辑就不一样。
懂了没有?
什么是上下文?
什么是Context?
就是随机应变,像变色龙一样随着不同的环境变化而自由变化。
开发者根据“上下文”不同的业务需求往Context里面放置不同的Strategy。
这就是Context上下文的意思。
这里的Strategy可以你自己new,你也可以把它放在配置类里面配置,然后在代码中读取,这样更加灵活方便。
那我们知道了策略模式怎么实现,也就是已经有了一把锤子在手上了,那什么时候用这把锤子呢?
1. 系统中需要动态地在几种算法中选择一种。
2. 一个对象有很多的行为,如果不用策略模式就只能用一大堆的if…else…来实现。
3. 不希望客户端知道复杂的、与算法相关的数据结构。在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。
然后我们用一些实际的例子来理解策略模式的大概的使用场景:
1. 支付方式选择: 假设平台支持多种支付方式,比如微信、支付宝、银行卡等。
2. 数据渲染方式: 如果你有一个应用程序,它可以以多种格式输出数据,比如XML、JSON或CSV。
3. 导航策略: 导航应用多种路径计算方法,如最快路线、最短路线、避开收费路线等。
4. 压缩数据: 根据不同的情况(比如压缩率、速度等)使用不同的压缩算法(如ZIP、RAR、7z等)。
1. 完美支持了开闭原则。
2. 通过抽象算法和继承实现,避免了大量重复代码。
3. 避免了多重选择语句(硬编码,不易维护)。
往期推荐:
● 纪念JDBC