定义一系列的算法,把他们一个个封装起来,在使用他们的时候可以相互替换,并且不会影响到使用算法的客户端。
该图右侧是策略接口以及具体策略实现类,Context持有Strategy的引用,最终客户端调用。
根据类图模拟一下Demo
//相同的算法接口(一个策略)
public interface Stragety {
void test();
}
具体算法实现
//算法具体实现A
class ConcreteStragetyA implements Stragety {
@Override
public void test() {
System.out.println("算法A实现");
}
}
//算法具体实现B
class ConcreteStragetyB implements Stragety {
@Override
public void test() {
System.out.println("算法B实现");
}
}
//算法具体实现C
class ConcreteStragetyC implements Stragety {
@Override
public void test() {
System.out.println("算法C实现");
}
}
Context上下文,持有一个Strategy接口引用
public class Context {
private Stragety stragety;
//初始化时,传入具体策略对象
public Context(Stragety stragety) {
this.stragety = stragety;
}
//根据传入策略对象,调用其具体算法
public void method(){
stragety.test();
}
}
客户端代码
public class Client {
public static void main(String[] args) {
Context c = new Context(new ConcreteStragetyA());
c.method();
c = new Context(new ConcreteStragetyB());
c.method();
c = new Context(new ConcreteStragetyC());
c.method();
}
}
打印结果
算法A实现
算法B实现
算法C实现
以上Demo清晰但也没什么用,任然都策略模式一头雾水,下面以JDK为我们提供的Comparable接口以及Comparator接口来看一下策略模式的具体应用。
在面试中,面试官经常让我们写一个简单的排序算法,在这里我们以冒泡排序为例,下面是一个简单排序工具类(相信大家对冒泡排序都很熟悉,这里不再解释)
public class SortUtil {
public static void bubbleSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = 0; j < a.length - i - 1; j++) {
if (a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
}
既然作为工具类,同时就要支持double、float类型,这些基本类型直接增加另外一个方法修改方法参数类型即可实现,可是现在问题来了,我们不仅需要对基本类型进行排序,还要兼容对对象类型进行排序,假如现在有一猫类,需要根据猫的具体身高进行排序
//需要根据猫的身高升序排序
public class Cat {
//身高
private int high;
//体重
private int height;
public int getHigh() {
return high;
}
public void setHigh(int high) {
this.high = high;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
也许会说,这不简单,再添加一个排序方法,传入Cat[],然后在遍历时候根据Cat的high属性进行比较排序不就可以了,假如以后有其他的Dog、Car等等需要排序,我们就需要修改该工具类的源码,违背了”对修改关闭”原则,同时也会造成该工具类代码过度膨胀。有没有更好的实现方法呢?答案肯定是有的,只需定义一个接口Comparable有一方法comparTo,规定想实现排序的类必须实现该接口,然后实现自己的具体排序算法,以后新增的类只需实现该接口即可实现排序,无序在修改排序工具类源码,符合了”对扩展开放”原则。
定义我们自己的Comparable接口
package stragety.sort;
public interface Comparable {
int compareTo(Comparable o);
}
然后Cat类实现Comparable接口,在以上例子中的Cat类增加实现方法
package stragety.sort;
public class Cat implements Comparable {
private int high;
private int weight;
public int getHigh() {
return high;
}
public void setHigh(int high) {
this.high = high;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Cat [high=" + high + ", height=" + weight + "]";
}
public Cat(int high, int weight) {
this.high = high;
this.weight = weight;
}
/*
* 重写compareTo方法,实现自己的比较方法,比较该对象与指定对象的顺序
* 如果该对象小于、等于或大于指定对象,则分别返回-1、0或1
*/
@Override
public int compareTo(Comparable o) {
//判断传入对象是否是Cat类型
if (o instanceof Cat) {
Cat c = (Cat) o;
if (this.getHigh() > c.getHigh()) {
return 1;
} else if (this.getHigh() == c.getHigh()) {
return 0;
}
return -1;
}
throw new ClassCastException("类型转换异常");
}
}
这样,我们的排序方法就可以修改成一下
//只要实现了我们自定义接口Comparable的类都可以使用此方法排序
public static void bubbleSort(Comparable[] a) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = 0; j < a.length - i - 1; j++) {
if (a[j].compareTo(a[j + 1]) > 0) {
Comparable temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
//打印数组方法
private static void print(Object[] c) {
for (int i = 0; i < c.length; i++) {
if (c.length == i + 1) {
System.out.print(c[i]);
return;
}
System.out.print(c[i] + ", ");
}
}
我们测试一下
public static void main(String[] args) {
Cat[] c = new Cat[] { new Cat(1, 2), new Cat(4, 5), new Cat(3, 1) };
bubbleSort(c);
print(c);
}
打印结果
Cat [high=1, height=2], Cat [high=3, height=1], Cat [high=4, height=5]
这就实现了任意实现了我们自定义Comparable接口类型对象的排序,通过查询JDK API我们发现JDK为我们提供的Comparable和我们自己定义的Comparable接口比较方法一样(JDK使用的泛型)。
但是,细心的小伙伴们会提出以下疑问,现在是排序方便了,但是跟策略模式有毛线关系,别急,看下面分析。
此时虽然我们可以根据Cat的high属性排序了,只要实现Comparable接口,在实现方法里根据high属性比较排序。但是现在需求改了,我们需要根据Cat的weight属性排序,或者根据high和weight综合排序,我们的排序类又满足不了需求了,方法实现只能有一个,现在该怎么办呢???,我们可以定义这样一个接口Comparator有一个compare方法,接受两个参数
package stragety.sort;
//相当于策略接口Strategy
public interface Comparator {
int compare(Object o1,Object o2);
}
我们还以Cat类为例
package stragety.sort;
//实现以上我们自定义接口Comparator,具体策略实现(根据Cat的high属性排序)
public class CatSortByHigh implements Comparator {
@Override
public int compare(Object o1, Object o2) {
//既然知道比较的是猫,直接强转
if(((Cat)o1).getHigh() > ((Cat)o2).getHigh()){
return 1;
}else if(((Cat)o1).getHigh() == ((Cat)o2).getHigh()){
return 0;
}
return -1;
}
}
下面是我们的最终Cat类
package stragety.sort;
public class Cat implements Comparable {
private int high;
private int weight;
// 这个比较方法就是用到了设计模式的策略模式,根据不同的具体策略实现不用的比较方法
private Comparator comparator;
public Comparator getComparator() {
return comparator;
}
public void setComparator(Comparator comparator) {
this.comparator = comparator;
}
public int getHigh() {
return high;
}
public void setHigh(int high) {
this.high = high;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Cat [high=" + high + ", height=" + weight + "]";
}
public Cat(int high, int weight) {
this.high = high;
this.weight = weight;
}
// 我们使用实现了Comparator接口的实现方法
@Override
public int compareTo(Comparable o) {
//将具体的排序策略交给了comparator的compare方法
return comparator.compare(this, o);
}
}
测试一下
public static void main(String[] args) {
CatSortByHigh high = new CatSortByHigh();
Cat c1 = new Cat(1, 2);
c1.setComparator(high);
Cat c2 = new Cat(4, 5);
c2.setComparator(high);
Cat c3 = new Cat(3, 1);
c3.setComparator(high);
Cat[] c = { c1, c2, c3 };
bubbleSort(c);
print(c);
}
打印结果
Cat [high=1, height=2], Cat [high=3, height=1], Cat [high=4, height=5]
可以看到实现排序了,如果在需要根据Cat的weight属性排序,只需要新建一个CatSortByWeight然后实现自己的Comparator接口,在实现方法里根据Cat的weight属性写对应的排序算法,来看一下现在的设计类图
Comparator接口相当于策略接口,CatSortByHigh类和CatSortByWeight类就是策略的具体实现,而Cat类就相当于策略模式点的上下文,持有Comparator的引用。通过查询JDK为我们提供的Comparable和Comparator,可以发现以上例子实现思路和JDK的一样(JDK使用了泛型)。
对JDK提供的Comparator和Comparable做一下总结,Comparable主要是用来对对象排序,Comparator是对容器的元素排序;Comparable是在对象内部实现排序,Comparator是在外部通过扩展实现,两者可以结合使用。
策略模式在我们平时开大的过程中用途还是很多的,比如商城会员系统,有银牌、金牌、钻石会员,不同的会员购物时有不同的优惠,又比如商场的打折活动,春节全场8折,情人节买二送一等。
分析一下具体用到策略模式的场景
1. 一个类定义了多种行为,并且这些行为在该类中已多个ifelse形式出现。可以使用策略模式将不同行放到策略类里从而代替这些ifelse语句。
2. 系统中算法使用的数据不可以让客户端知道。可以使用策略模式避免暴露与算法相关的数据。
3. 系统需要动态的使用几种算法中的一种,策略模式就提供了这种将多个行为中的一个行为配置一个类的方法。
通过以上描述发现策略模式的优点
1. 避免多重ifelse语句,多重ifelse不利于维护。
2. 代替继承,继承可以处理多种算法和行为,可以直接生成Context的子类,给它不同的行为或算法,然而这样一来算法的的实现就和Context的实现混合起来,从而导致Context难以扩展维护,继承让动态的改变算法变成了不可能。使用策略模式将不同的算法或行为封装到不同的Strategy实现类可以独立于Context去改变它,使它易于扩展维护。
3. 策略模式还可以提供相同的行为不同的实现,客户端可以根据不同的需求从不同的策略中做出选择。
4. 策略模式思路符合”开闭原则”,对扩展开放,就比如以上例子中Cat类增加新的排序算法,只需要实现Comparator接口写对应的算法;符合”里氏替换原则”,策略模式要求所有的策略对象都是可以互换的,因此他们都必须是同一个抽象类或接口策略的子类,在客户端只知道抽象策略。
策略模式也有缺点,比如客户端必须知道所有的策略类,并且知道这些类区别,以便选择恰当的策略类。
也许有些小伙伴会说了,虽然设计模式很重要,但是在开发项目过程中基本上就没有使用过设计模式,是的,JDK以及我们常用的开源框架已经对设计模式做好了封装,我们只需配置好使用即可。众所周知的Spring就是穿插着各种设计模式,掌握设计模式,对我们学习和掌握一些良好的开源框架是非常有帮助的,相信大家有了设计模式的底子,就不会再看一些源码时发出这样的疑问:卧槽,这里为什么需要定义一个接口,卧槽这里干嘛要抽象。。。