设计模式之strategy pattern

strategy pattern的定义

  • 定义一组算法,分别封装起来,让它们之间可以互相替换。Strategy pattern让算法的变化独立于使用它的客户端。
  • 在接口中定义抽象,在它的derived类中实现相应的细节。

在面向对象程序设计中,其中一个重要的原则就是针对接口编程,不要去针对实现编程。 下图说明了怎样实现这个原则:

strategy pattern

从上图可以看出:在接口中封装抽象,在它的derived类中实现相应的细节。Client针对接口编程,当derived类的数量改变时,Client代码不受影响;当derived类的实现细节改变时,Client代码同样也不受影响。

利用strategy pattern可以很好地针对这样的原则去编程。下面我来用具体的代码举个例子。

Example

相信大家都去KFC吃过东西吧。那么现在KFC有个活动,第一杯可乐正常价格,之后的每杯可乐都是半价。针对这个业务需求,我设计一个结账策略接口,它封装了getActPrice方法,代码如下:

strategy pattern
1
2
3
interface BillingStrategy {
public double getPrice(double rawPrice);
}

由于有两种价格策略,因此这里我写两个derived类,并分别实现具体的细节。

strategy pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 正常价格策略
class NormalStrategy implements BillingStrategy {
@Override
public double getPrice(double rawPrice) {
return rawPrice;
}
}
// 半价策略
class HalfStrategy implements BillingStrategy {
@Override
public double getPrice(double rawPrice) {
// 为了方便我把0.5写死了,现实应用中,可以定义常量
return rawPrice * 0.5;
}
}

现在,让我们设计顾客实体。

strategy pattern
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
import java.util.LinkedList;
import java.util.List;
class Customer {
private List<Double> drinks; // 存储客户每杯可乐的价格
private BillingStrategy strategy; // 针对接口编程
public Customer(BillingStrategy strategy) {
this.drinks = new LinkedList<Double>();
this.strategy = strategy;
}
public void add(double price) {
drinks.add(strategy.getPrice(price));
}
// 打印客户购买可乐的总价格
public void printBill() {
double sum = 0;
for (Double i : drinks) {
sum += i;
}
System.out.println("总价格为: " + sum);
drinks.clear();
}
// 由于客户可能会买很多杯可乐,这个方法可以改变用户的价格策略
public void setStrategy(BillingStrategy strategy) {
this.strategy = strategy;
}
}

假设我们每杯可乐的原始价格为10元,现在有一个客户买了3杯可乐,现在让我们算一算他总共需要付多少钱吧?

strategy pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StrategyPattern {
public static void main(String[] args) {
Customer customer = new Customer(new NormalStrategy()); // 正常价格策略
customer.add(10.0);
// 第二杯和第三杯采用半价策略
customer.setStrategy(new HalfStrategy());
customer.add(10.0);
customer.add(10.0);
customer.printBill();
}
}

至此,整个例子已经演示完了。大家可以认真想一想,用Strategy Pattern设计的好处是什么呢?如果未来某一天,我们推出个新的价格活动,比如第三杯1/4价格,我们只需要写一个价格类实现BillingStrategy接口就行了,并不需要去修改客户端的代码。这大大降低了我们软件的维护成本。

Java中用到Strategy Pattern的地方

java.util.Comparator#compare()Collections#sort()

下面我举个关于这个Java API的例子。

strategy pattern
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
package strategy_pattern;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
public class SortList {
public static void main(String[] args) {
List<Integer> list = new LinkedList<Integer>();
list.add(9);
list.add(2);
list.add(1);
list.add(64);
list.add(18);
Collections.sort(list, new FromSmallToLarge());
System.out.println(list);
Collections.sort(list, new FromLargeToSmall());
System.out.println(list);
}
}
class FromSmallToLarge implements Comparator<Integer> {
@Override
public int compare(Integer num1, Integer num2) {
return num1 - num2;
}
}
class FromLargeToSmall implements Comparator<Integer> {
@Override
public int compare(Integer num1, Integer num2) {
return num2 - num1;
}
}

接口Comparator只定义了Sort方法的抽象,我们可以定义不同的derived类分别实现各自不同的sort细节。而Collections的sort方法定义方法签名并不需要提前知道具体的sort细节,它只需要定义Comparator接口,具体的实现会等到运行时找相应的derived类实现,这是一个很漂亮的例子,非常值得学习。