策略模式(Comparator和Comparable区别)

定义一系列的算法,把他们一个个封装起来,在使用他们的时候可以相互替换,并且不会影响到使用算法的客户端。
UML图
该图右侧是策略接口以及具体策略实现类,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属性写对应的排序算法,来看一下现在的设计类图

Cat类图

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就是穿插着各种设计模式,掌握设计模式,对我们学习和掌握一些良好的开源框架是非常有帮助的,相信大家有了设计模式的底子,就不会再看一些源码时发出这样的疑问:卧槽,这里为什么需要定义一个接口,卧槽这里干嘛要抽象。。。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值