策略设计模式用实例理解(更好的理解接口,解耦合)
对象数组
:数组里的每个元素都是类的对象,赋值时先定义对象,然后将对象直接赋给数组。
🔥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方法,这样还是不符合开闭原则
,且细节还是暴露在外边,扩展性也差;
截至目前的两个问题:
- 细节暴露在Client类中,这就很不面向对象;(
宗旨
:一个方法解决一个问题,而不是一坨代码解决一个问题,即单一原则
) - 修改比较策略时还得改动源代码,这违反了“
开闭原则
”;
💖对于第一个问题
根据单一原则,我们构建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方法时就直接将比较器类Comparator
new进去就可;
//调用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());
最终结果:
🍜总结
- 从开始到最终做的工作就是一个解耦合的过程;
- 再定义一个方法时,其中的参数需要面向抽象或者接口,这样扩展性就强;若面向具体实现类,扩展性就会变差;
转载自:https://juejin.cn/post/7203690731277205560