为何会有Comparable与Comparator接口? 引入策略模式

引入

  • 你们先考虑一个场景, 有一个整形数组, 咱们但愿经过调用一个工具类的排序方法就能对该数组进行排序. 请看下面的代码:
public class Strategy {
    public static void main(String[] args) {
        int[] arr = {5, 3, 1, 7, 2};
        new DataSorter().sort(arr);//调用工具类进行排序
        for(int i = 0; i < arr.length; i++){
            System.out.println(arr[i]);
        }
    }
}

class DataSorter{//用于排序的工具类
    public void sort(int[] arr){//调用sort方法进行排序, 此处使用冒泡排序
        for(int i = arr.length - 1; i > 0; i--){
            for(int j = 0; j < i; j++){
                if(arr[j] > arr[j + 1])
                    swap(arr, j, j  + 1);
            }
        }
    }

    private void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
复制代码

 

Comparable接口的前因后果

  • 经过上面的代码, 咱们可以轻易地对整形数组进行排序, 那么若是如今有了新需求, 须要对浮点类型数据进行排序, 排序工具类应该如何作呢?
  • 或许你会想, 不如就新添加一个排序方法, 方法的参数类型为float类型, 把int类型数组的排序算法复制一遍不就能够了吗?
  • 那若是我继续追问, 若是如今要对一只猫进行排序, 那应该怎么作呢? 猫的类以下
class Cat{
    private int age;//猫的年龄
    private int weight;//猫的体重

    //get / set 方法...
}
复制代码
  • 你也许会顺着原来的思路回答, 照样copy一份排序的算法, 修改方法参数, 而后在比较的地方指定比较猫的年龄或体重不就能够了吗?
public void sort(Cat[] arr){//以猫数组做为参数
    for(int i = arr.length - 1; i > 0; i--){
        for(int j = 0; j < i; j++){
            if(arr[j].getAge() > arr[j + 1].getAge())//根据猫的年龄做比较
                swap(arr, j, j  + 1);
        }
    }
}
复制代码
  • 但仔细想一想, 若是还要继续比较小狗, 小鸡, 小鸭等各类对象, 那么这个排序工具类的代码量岂不是变得很大? 为了能让排序算法的可重用性高一点, 咱们但愿排序工具中的sort()方法能够对任何调用它的对象进行排序.
  • 你可能会想: 到对任何对象都能排序, 把sort()方法的参数改成Object类型不久能够了嘛. 这个方向是对的, 可是问题是, 当拿到两个Object类型对象, 应该根据什么规则进行比较呢?
  • 这个时候咱们天然而然地就但愿调用工具类进行排序的对象自己就具有本身的比较法则, 这样在排序的时候就能直接调用对象的排序法则进行排序了.
  • 咱们把比较法则抽象为Comparable接口, 凡是要进行比较的类都要实现Comparable接口, 而且定义本身的比较法则, 也就是CompareTo()方法.
  • 这样当咱们在封装工具时, 就能够直接对实现了Comparable接口的对象进行比较, 不用担忧比较的细节了.
public class Strategy {
public class Strategy {
    public static void main(String[] args) {
// Integer[] arr = {5, 3, 1, 7, 2};//注意这里把int改成Integer, Integer是Object的子类
        Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
        DataSorter ds = new DataSorter();
        ds.sort(arr);
        for(int i = 0; i < arr.length; i++){
            System.out.println(arr[i]);
        }
    }
}
}

class DataSorter{//用于排序的工具类
    public void sort(Object[] arr){//参数类型为Object
        for(int i = arr.length - 1; i > 0; i--){
            for(int j = 0; j < i; j++){
                Comparable c1 = (Comparable) arr[j];//先转为Comparable类型
                Comparable c2 = (Comparable) arr[j + 1];
                if(c1.CompareTo(c2) == 1)//调用CompareTo()进行比较, 不关心具体的实现
                    swap(arr, j, j  + 1);
            }
        }
    }

    private void swap(Object[] arr, int i, int j){
        Object temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

class Cat implements Comparable{
    private int age;
    private int weight;

    @Override
    public int CompareTo(Object o) {
        if(o instanceof Cat){//先判断传入的是不是Cat类对象, 不是则抛异常
            Cat c = (Cat) o;
            if(this.age > c.age) return 1;
            else if (this.age < c.age) return -1;
            else return 0;
        }
        throw null == o ? new NullPointerException() : new ClassCastException();
    }

    // get / set ...
    //toString() ...
}

interface Comparable{
    public int CompareTo(Object o);
}
复制代码

 

引入Comparator接口

  • 相信看了上面的Comparable接口来由, 你们会感受整个设计又美好了一些, 可是其中还有漏洞. 咱们在Cat类的CompareTo()方法中, 对猫的比较策略是写死的, 如今咱们按猫的年龄比较大小, 若是哪天咱们想按照猫的体重比较大小, 又要去修改源码了. 有没有扩展性更好的设计?
  • 咱们可让用户本身定义一个比较器类, 对象能够根据用户指定的比较器比较大小.
  • 整个逻辑是: 若是这个对象须要进行比较, 那么它必须实现Comparable接口, 可是它具体是怎么比较的, 则经过具体的Comparator比较器进行比较.
  • 固然这里少不了多态, 咱们首先要定义一个比较器接口Comparator, 用户的比较器须要实现Comparator接口, 下面上代码:
public class Strategy {
    public static void main(String[] args) {
        Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
        DataSorter ds = new DataSorter();
        ds.sort(arr);
        for(int i = 0; i < arr.length; i++){
            System.out.println(arr[i]);
        }
    }
}

class DataSorter{//用于排序的工具类
    public void sort(Object[] arr){//参数类型为Object
        for(int i = arr.length - 1; i > 0; i--){
            for(int j = 0; j < i; j++){
                Comparable c1 = (Comparable) arr[j];//先转为Comparable类型
                Comparable c2 = (Comparable) arr[j + 1];
                if(c1.CompareTo(c2) == 1)//背后已经转换为使用Comparator的定义的规则进行比较
                    swap(arr, j, j  + 1);
            }
        }
    }

    private void swap(Object[] arr, int i, int j){
        Object temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

class Cat implements Comparable{
    private int age;
    private int weight;
    private Comparator comparator = new CatAgeComparator();//默认持有年龄比较器

    @Override
    public int CompareTo(Object o) {
        return comparator.Compare(this, o);//调用比较器比较而不是直接在此写比较法则
    }

    // get / set / toString ...
}

interface Comparable{
    public int CompareTo(Object o);
}

interface Comparator{
    public int Compare(Object o1, Object o2);
}

//用户本身定义的, 按照猫的年龄比较大小的比较器
class CatAgeComparator implements Comparator{
    @Override
    public int Compare(Object o1, Object o2) {
        Cat c1 = (Cat) o1;
        Cat c2 = (Cat) o2;
        if(c1.getAge() > c2.getAge()) return 1;
        else if(c1.getAge() < c2.getAge()) return -1;
        else return 0;
    }
}

//按照猫的体重比较大小的比较器
class CatWeightComparator implements Comparator{
    @Override
    public int Compare(Object o1, Object o2) {
        Cat c1 = (Cat) o1;
        Cat c2 = (Cat) o2;
        if(c1.getWeight() > c2.getWeight()) return 1;
        else if(c1.getWeight() < c2.getWeight()) return -1;
        else return 0;
    }
}
复制代码

 

什么是策略模式?

  • 在上面的例子中, 咱们本身定义了Comparable接口和Comparator接口, 其实这两个接口都是Java自带的, 经过上面的代码示例, 想必你们也应该知道了为何会有这两个接口.
  • 其实Comparable定义的就是一种比较的策略, 这里的策略你能够理解为一个功能, 然而策略有了, 咱们还须要有具体的策略实现, 因而便有了Comparator接口.

 

  • 这里再举一个例子方便你们理解.
  • 如今有一个坦克小游戏, 坦克要可以发射炮弹, 那么咱们能够认为发射炮弹就是一种策略, 可是具体到发送什么炮弹, 这能够由具体的策略实现.
  • 到GitHub上看看该坦克游戏
  • 首先定义发射炮弹这种策略
public interface Fire {
    public void fire();//发射炮弹的策略
}
复制代码
  • 为了实现发射炮弹这种策略, 定义策略的具体实现, 也就是定义发射炮弹动做
public interface FireAction {
    public void fireAction(Tank tank);
}
复制代码
  • 坦克想要发送炮弹必须实现Fire()接口, 并且坦克拥有发射炮弹的动做, 至于动做的具体实现, 这里默认给出只发射一颗炮弹的动做.
public class Tank implements TankHitListener, Fire {
    //省略各类属性方法...
    private FireAction fireAction = new NormalFireAction();//默认动做是只发射一颗炮弹
    
    @Override
    public void fire() {
        fireAction.fireAction(this);
    }
    
    //...
复制代码

使用了策略模式有什么好处?

  • 以上面的坦克游戏为例, 当把发射炮弹定义为一种策略后, 能发射炮弹的对象就不仅坦克一个了, 若是游戏中有机关, 可让机关也实现fire()接口, 得到发射炮弹的能力.
  • 并且在定义策略后咱们能够根据策略给出不一样的实现方式, 比方说坦克发射炮弹的动做是每次只发射一颗炮弹, 而机关是每次向八个方向发射一颗炮弹. 很是灵活.
  • 结束

 

相关文章
相关标签/搜索