策略设计模式用实例理解(更好的理解接口,解耦合)

What to do?

接下来我们聊一聊对象数组排序,有排序那肯定有两两比较的过程;对于对象而言,其中有很多属性方法,我们可以通过某个属性进行比较,进而实现对对象数组的排序,对于下面程序:

        Student stu1 = new Student("张三",12,153.4);
        Student stu2 = new Student("李四",14,163.4);
        Student stu3 = new Student("王五",13,123.4);
        Student stu4 = new Student("赵六",4,6.4);
        
        Student[] stus = {stu1,stu2,stu3,stu4};
复制代码

Student类中的有参构造器中的参数分别为:姓名、年龄、身高;其中stus就是对象数组,现在我需要通过年龄对stus数组进行排序,那么我们就可以通过冒泡排序实现,具体如下:

        for (int i = 0; i < stus.length-1; i++) {
            for (int j = 0; j < stus.length - i - 1; j++) {
                if(stus[j].age > stus[j+1].age){
                    Student temp = stus[j];
                    stus[j] = stus[j+1];
                    stus[j+1] = temp;
                }
            }
        }
复制代码

通过上述代码我们就可以将对象数组通过年龄进行排序;在上述代码中比较年龄就是一个比较策略,我们可以改变策略,比如比较身高,那么我们就需要让对象调用属性height。

How to do?

承接上述,我们就以对象数组排序来慢慢理解策略设计模式;

首先我们创建User类,存放属性和方法,做为实例化对象的原始类;

public class User {
    //创建属性
    private String name;
    private int age;
    private int height;

    //创建构造方法(若自己建立有参构造,须把无参构造加上)
    public User() {
    }

    public User(String name, int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    //创建get/set方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    //重写toString()方法
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}
复制代码

创建Client类,用来实例化对象,封装对象数组,然后实现通过年龄进行进行对象数组排序;

public class Client {
    //主函数中创建对象,封装对象数组
    public static void main(String[] args) {
        //创建User对象
        User user1 = new User("张三",12,153);
        User user2 = new User("李四",14,163);
        User user3 = new User("王五",13,133);
        User user4 = new User("赵六",11,143);
        //封装对象数组
        User[] users = {user1,user2,user3,user4};
        //通过年龄对数组进行正向排序
        for (int i=0;i < users.length - 1;i++){
            for (int j=0;j < users.length - i - 1;j++){
                if(users[j].getAge() > users[j+1].getAge()){
                    User temp = users[j];
                    users[j] = users[j+1];
                    users[j+1] = temp;
                }
            }
        }
        //输出查看对象数组是否排序成功
        System.out.println(Arrays.toString(users));
    }
}
复制代码

结果如下:

存在问题:我们可以看到已经排序成功;但是上述代码存在的问题是【将实现的细节暴露给了客户,并且比较也应该更面向对象一点】

解决方法:对于比较来说,我们可以改进比较方法,对比较进行封装,使得改变比较策略,而不复写代码;在此我们可以联想到equals()方法,因此我们可以抽离出一个让两个User对象进行比较的接口Comparable,然后让User实现该接口,从而完善Client中的比较细节;

创建Comparable接口,定义compare()方法;

public interface Comparable {
    //封装比较方法,传入User对象,实现与调用对象的比较
    Integer compare(Object object);
}
复制代码

更新User类,让其继承Comparable接口,实现compare()方法;

public class User implements Comparable{
    /**
     * 同之前的一样,就是增加了下面的这个方法
     */
     
    //返回两个对象的比较的结果
    @Override
    public Integer compare(Object object) {
        //如果传进来的是一个User类型的对象,那么再继续往下比,否则直接返回null
        if(object instanceof User){
            //首先将传进来的Object类型转换为User类型
            User user = (User) object;
            //返回比较的结果
            return this.getAge() - user.getAge();
        }
        return null;
    }
}
复制代码

Clien类中的排序方法中的比较就变得更加简洁,users[j].compare(users[j+1])

//通过年龄对数组进行正向排序
for (int i=0;i < users.length - 1;i++){
    for (int j=0;j < users.length - i - 1;j++){
        if(users[j].compare(users[j+1]) > 0){
            User temp = users[j];
            users[j] = users[j+1];
            users[j+1] = temp;
        }
    }
}
复制代码

上述这种写法的好处:让所有对象拥有了可比较的能力(这也是接口的好处),并且将具比的东西转给了对象;

问题:如果按其他属性去排序,也就是改变排序策略,那么还是得去修改User中的compaer方法,这样还是不符合开闭原则,且细节还是暴露在外边,扩展性也差;


截至目前的两个问题:

  1. 细节暴露在Client类中,这就很不面向对象;(宗旨:一个方法解决一个问题,而不是一坨代码解决一个问题,即单一原则)
  2. 修改比较策略时还得改动源代码,这违反了“开闭原则”;

对于第一个问题

根据单一原则,我们构建UserSorter类,实现排序方法;

public class UserSorter {
    //创建实现对象排序功能的方法sort
    public void sort(User[] users){
        //通过年龄对数组进行正向排序
        for (int i=0;i < users.length - 1;i++){
            for (int j=0;j < users.length - i - 1;j++){
                if(users[j].compare(users[j+1]) > 0){
                    User temp = users[j];
                    users[j] = users[j+1];
                    users[j+1] = temp;
                }
            }
        }
    }
}
复制代码

因此我们可将Client中实现比较的代码转换为 userSorter.sort(users)

public class Client {
    //主函数中创建对象,封装对象数组
    public static void main(String[] args) {
        //创建User对象
        User user1 = new User("张三",12,153);
        User user2 = new User("李四",14,163);
        User user3 = new User("王五",13,133);
        User user4 = new User("赵六",11,143);
        //封装对象数组
        User[] users = {user1,user2,user3,user4};
        //创建UserSorter对象
        UserSorter userSorter = new UserSorter();
        //调用UserSorter中的sort方法实现排序
        userSorter.sort(users);
        //输出查看对象数组是否排序成功
        System.out.println(Arrays.toString(users));
    }
}
复制代码

截至目前,我们就将实现细节屏蔽在Usersorter类中,并且让排序方法面向对象实现;

对于第二个问题

既然在改变比较策略时不想修改User类中的代码,那么我们就不让User类去实现Comparable接口;然后比较器UserSorer中的sort方法就得改!如果我们将sort中的比较策略写死,比如就写比较年龄,那我们改变比较策略的时候,比如想比较身高,那么就一定会对代码进行修改,但我们要对修改关闭,因此我们可以将User中的比较方法抽出去,让其不具备比较的能力;那么我们就可以想到建立一个类,让其拥有比较两个对象的能力,最终我们只需要将users[j],users[j+1],做为参数传入建立夫人那个类中的哪个方法中,最终让它返回比较的结果就行;

这样就对User本身无侵入性,就实现了不改动User代码;

我们现在定义这个类叫做Comparator,其中比较两个对象的方法为compare(User user,User user1);

public class Comparator {
    public Integer compare(User user,User user1){
        return user.getAge() - user1.getAge();
    }
}
复制代码

那么UserSorter中的`sort方法就需要加参数,改进比较;

public class UserSorter {
    //创建实现对象排序功能的方法sort
    public void sort(User[] users,Comparator comparator){
        //通过年龄对数组进行正向排序
        for (int i=0;i < users.length - 1;i++){
            for (int j=0;j < users.length - i - 1;j++){
                if(comparator.compare(users[j],users[j+1]) > 0){
                    User temp = users[j];
                    users[j] = users[j+1];
                    users[j+1] = temp;
                }
            }
        }
    }
}
复制代码

Client中在调用sort方法时就直接将比较器类Comparatornew进去就可;

//调用UserSorter中的sort方法实现排序
userSorter.sort(users,new Comparator());
复制代码

截至目前,我们这样写还是违背了开闭原则,【在这里我们定义方法时,参数要面向抽象、面向接口,这样扩展性强,如果面向具体实现类,那只能实现写死的那个类,扩展性就很差】;所以我们将sort(User[] users,Comparator comparator)中的第二个参数试着变成接口:

将Comparator改为接口;

public interface Comparator {
    Integer compare(User user,User user1);
}
复制代码

创建CompareAgeStrategy类实现Comparator接口,作用为比较年龄策略;

public class CompareAgeStrategy implements Comparator{
    public Integer compare(User user,User user1){
        return user.getAge() - user1.getAge();
    }
}
复制代码

这样写的话,我们在UserSorter中的sort方法里传入的就是接口,那么我们在Client类中调用sort方法时传入的参数就可以new 具体的比较策略,比如:

//实现了年龄策略比较
userSorter.sort(users,new CompareAgeStrategy());
复制代码

那么我们如果想比较身高的话,那就可以扩展一个比较身高策略类,传参数时直接new 比较身高策略类即可,这样就满足了不对源代码进行修改,只在源代码上进行扩展,这就满足了开闭原则,整个过程也诠释了策略设计模式;

创建CompareHeightStrategy类实现Comparator接口,作用为比较身高策略;

public class CompareHeightStrategy implements Comparator{
    @Override
    public Integer compare(User user, User user1) {
        return user.getHeight() - user1.getHeight();
    }
}
复制代码

修改Client中调用sort方法时穿的参数;

//实现了身高策略比较
userSorter.sort(users,new CompareHeightStrategy());
复制代码

最终结果:

至此,Client就只需要享受策略就ok;

总结

  1. 从开始到最终做的工作就是一个解耦合的过程;
  2. 再定义一个方法时,其中的参数需要面向抽象或者接口,这样扩展性就强;若面向具体实现类,扩展性就会变差;
展开阅读全文

页面更新:2024-03-07

标签:接口   策略   数组   实例   属性   身高   年龄   对象   参数   模式   代码   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top