前言

  定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
  分析下定义,策略模式定义和封装了一系列的算法,它们是可以相互替换的,也就是说它们具有共性,而它们的共性就体现在策略接口的行为上,另外为了达到最后一句话的目的,也就是说让算法独立于使用它的客户而独立变化,我们需要让客户端依赖于策略接口。
  下面给出策略模式的类图:


  这个类图并不复杂,右边是策略接口以及它的实现类,左边会有一个上下文,这个上下文会拥有一个策略,而具体这个策略是哪一种,我们是可以随意替换的。
  博主下面使用Java代码诠释上面的类图,方便各位理解各个类之间的关系。

首先是策略接口以及它的实现类。

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
public interface Strategy {

void algorithm();

}

class ConcreteStrategyA implements Strategy{

public void algorithm() {
System.out.println("采用策略A计算");
}

}

class ConcreteStrategyB implements Strategy{

public void algorithm() {
System.out.println("采用策略B计算");
}

}

class ConcreteStrategyC implements Strategy{

public void algorithm() {
System.out.println("采用策略C计算");
}

}

下面是我们的上下文,它会拥有一个策略接口。

1
2
3
4
5
6
7
8
9
10
11
12
public class Context {

Strategy strategy;

public void method(){
strategy.algorithm();
}

public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}

method方法是上下文类的一个公开方法,实际当中一般会和业务相关,这里就暂且取名为method方法。下面我们使用客户端调用一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {

public static void main(String[] args) throws Exception {
Context context = new Context();
context.setStrategy(new ConcreteStrategyA());
context.method();

context.setStrategy(new ConcreteStrategyB());
context.method();

context.setStrategy(new ConcreteStrategyC());
context.method();
}
}

上面我们替换了两次策略,但是调用方式不变,下面我们看下运行结果。

1
2
3
采用策略A计算
采用策略B计算
采用策略C计算

栗子

  上面的例子代码清晰但却理解起来很生硬,下面博主举一个具有实际意义的例子。
  就比如我们要做一个商店的收银系统,这个商店有普通顾客,会员,超级会员以及金牌会员的区别,针对各个顾客,有不同的打折方式,并且一个顾客每在商店消费1000就增加一个级别,那么我们就可以使用策略模式,因为策略模式描述的就是算法的不同,而且这个算法往往非常繁多,并且可能需要经常性的互相替换。
  这里我们举例就采用最简单的,以上四种顾客分别采用原价,八折,七折和半价的收钱方式。

那么我们首先要有一个计算价格的策略接口,如下。

1
2
3
4
5
public interface CalPrice {
//根据原价返回一个最终的价格
Double calPrice(Double originalPrice);

}

下面我们给出四个计算方式。

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
class Common implements CalPrice{

public Double calPrice(Double originalPrice) {
return originalPrice;
}

}

class Vip implements CalPrice{

public Double calPrice(Double originalPrice) {
return originalPrice * 0.8;
}

}

class SuperVip implements CalPrice{

public Double calPrice(Double originalPrice) {
return originalPrice * 0.7;
}

}

class GoldVip implements CalPrice{

public Double calPrice(Double originalPrice) {
return originalPrice * 0.5;
}

}

以上四种计算方式非常清晰,分别是原价,八折,七折和半价。下面我们看客户类,我们需要客户类帮我们完成客户升级的功能。

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
//客户类
public class Customer {

private Double totalAmount = 0D;//客户在本商店消费的总额
private Double amount = 0D;//客户单次消费金额
private CalPrice calPrice = new Common();//每个客户都有一个计算价格的策略,初始都是普通计算,即原价

//客户购买商品,就会增加它的总额
public void buy(Double amount){
this.amount = amount;
totalAmount += amount;
if (totalAmount > 3000) {//3000则改为金牌会员计算方式
calPrice = new GoldVip();
}else if (totalAmount > 2000) {//类似
calPrice = new SuperVip();
}else if (totalAmount > 1000) {//类似
calPrice = new Vip();
}
}

//计算客户最终要付的钱
public Double calLastAmount(){
return calPrice.calPrice(amount);
}
}

下面我们看客户端调用,系统会帮我们自动调整收费策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//客户端调用
public class Client {

public static void main(String[] args) {
Customer customer = new Customer();
customer.buy(500D);
System.out.println("客户需要付钱:" + customer.calLastAmount());
customer.buy(1200D);
System.out.println("客户需要付钱:" + customer.calLastAmount());
customer.buy(1200D);
System.out.println("客户需要付钱:" + customer.calLastAmount());
customer.buy(1200D);
System.out.println("客户需要付钱:" + customer.calLastAmount());
}

}
  运行以后会发现,第一次是原价,第二次是八折,第三次是七折,最后一次则是半价。我们这样设计的好处是,客户不再依赖于具体的收费策略,依赖于抽象永远是正确的。不过上述的客户类实在有点难看,尤其是buy方法,我们可以使用简单工厂来稍微改进一下它。我们建立如下策略工厂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//我们使用一个标准的简单工厂来改进一下策略模式
public class CalPriceFactory {

private CalPriceFactory(){}
//根据客户的总金额产生相应的策略
public static CalPrice createCalPrice(Customer customer){
if (customer.getTotalAmount() > 3000) {//3000则改为金牌会员计算方式
return new GoldVip();
}else if (customer.getTotalAmount() > 2000) {//类似
return new SuperVip();
}else if (customer.getTotalAmount() > 1000) {//类似
return new Vip();
}else {
return new Common();
}
}
}
  这样我们就将制定策略的功能从客户类分离了出来,我们的客户类可以变成这样。
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
//客户类
public class Customer {

private Double totalAmount = 0D;//客户在本商店消费的总额
private Double amount = 0D;//客户单次消费金额
private CalPrice calPrice = new Common();//每个客户都有一个计算价格的策略,初始都是普通计算,即原价

//客户购买商品,就会增加它的总额
public void buy(Double amount){
this.amount = amount;
totalAmount += amount;
/* 变化点,我们将策略的制定转移给了策略工厂,将这部分责任分离出去 */
calPrice = CalPriceFactory.createCalPrice(this);
}
//计算客户最终要付的钱
public Double calLastAmount(){
return calPrice.calPrice(amount);
}

public Double getTotalAmount() {
return totalAmount;
}

public Double getAmount() {
return amount;
}

}
  现在比之前来讲,我们的策略模式更加灵活一点,但是相信看过博主博文的都知道,博主最不喜欢else if,所以策略模式也是有缺点的,就是当策略改变时,我们需要使用else if去判断到底使用哪一个策略,哪怕使用简单工厂,也避免不了这一点。比如我们又添加一类会员,那么你需要去添加else if。再比如我们的会员现在打九折了,那么你需要添加一个九折的策略,这没问题,我们对扩展开放,但是你需要修改else if的分支,将会员的策略从八折替换为九折,这是简单工厂的诟病,在之前已经提到过,对修改开放。好了今天的分享就到此结束~

ps:因作者能力有限,有错误的地方请见谅

  • 喜欢这篇文章的话可以用快捷键 Ctrl + D 来收藏本页

最后更新: 2018年11月29日 14:58

原始链接: https://blog.hdqyf.club/2018/11/29/20181129-策略模式详解/

× 请我吃糖~
打赏二维码