Sui

设计模式-策略模式

字数统计: 1.9k阅读时长: 7 min
2021/02/22 Share

设计模式-策略模式

设计模式在我们的日常开发中十分重要,好的软件架构可以使软件更加强壮,使软件变得更易于维护,建立可以维护的软件系统,要诀就在于随时想到系统以后可能需要的变化以及应付变化的原则。相信大多数的人都学习过面向对象语言,c++、java、python,这些面向对象的语言精髓就在于抽象、继承、多态,但是只知道这三样是不能设计出好的软件的,里面需要设计模式的参与。

有过web开发经验的小伙伴可能对SpringMyBatis这类的框架比较了解,这类框架的实现里面充满了设计模式,各种设计模式,从而它才能走的长远,想要走的长远就需要维护,想要维护就需要好的软件架构,再加上它功能的强大,因而走到了今天,还可能走的更远。

设计模式是前人总结出来的经验,我们站在前人的肩膀上,依附着前人的经验,我们就可以设计出更好的、更棒的软件

接下来就以一个场景出发,从而逐渐引出策略模式

场景

我们模拟一个人,人由于有共同的行为,例如姓名、性别、吃饭、睡觉、工作等等,这就能抽象出一个类,由于每个人都有自己的特性,因此用一个具体的类去实现这个抽象出的类

代码如下(代码中的get与set均省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Person {
private String name;
private String sex;
private int age;

public abstract void doWork();

public void sleep() {
System.out.println("正在睡觉");
}

public void eat() {
System.out.println("正在吃饭");
}
}
1
2
3
4
5
6
public class Adult extends Person{
@Override
public void doWork() {
System.out.println("我是一名程序员,我正在写代码");
}
}
1
2
3
4
5
6
public class Young extends Person {
@Override
public void doWork() {
System.out.println("我是青年,我正在学习");
}
}

这样设计是不是看上去挺好的,没什么瑕疵,其实这里面有很大的漏洞

存在的问题

第一个问题,很多人没有工作,例如儿童,它是没有任何工作的,就目前的设计来看,没工作也是需要实现doWork()方法的,例如下面这样

1
2
3
public void doWork() {
System.out.println("我是儿童,我没有工作");
}

第二个问题,同类人有同一种工作,那是不是每个类都需要重复的实现一些东西,这样做开发效率会很低。例如程序员的工作的敲代码,而学习计算机的学生的工作也是敲代码,这两类人的工作都指向了同一个,这就需要两次实现

第三个问题,很多人具有同一类工作,例如上面的这样,但是,当这份工作随着时代的更新,工作的方式有所衍化,那是不是要到每一个实现类中去修改这种工作方式,还是上面的例子,随着时代的进步,程序员和学习计算机的学生不再需要编码了,每天喝喝咖啡锻炼锻炼身体就是程序员的工作,那是不是需要将每个实现都改一下

解决方案

目前针对第一个问题,倒是有一个解决方案,我们可以把doWork()抽象为一个接口,有工作的人则实现这个接口,对AdultChildren类,可以这样做,首先修改Person

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Person {
private String name;
private String sex;
private int age;

public void sleep() {
System.out.println("正在睡觉");
}

public void eat() {
System.out.println("正在吃饭");
}
}

将Person中的抽象方法doWork()转移到接口Work中

1
2
3
public interface Work {
public void doWork();
}

这样,有工作的人可以实现这个接口,并且实现其中的方法,没有工作的人则不实现这个接口,AdultChildren类如下

1
2
3
4
5
6
public class Adult extends Person implements Work {
@Override
public void doWork() {
System.out.println("我是一名程序员,我正在写代码");
}
}
1
2
public class Children extends Person {
}

这样做是否还存在一个问题,因为Java接口不具有实现代码,所以实现接口不具有代码重用的功能,这也就是引出来了问题二、三。

这里提出来一个设计原则:

找出应用中可能需要变化之处,把他们独立出来并封装,不要和那些不需要变化的代码混合在一起

这个设计原则也是之所以有设计模式的初衷,几乎所有的设计模式都遵守这个原则

换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分代码需要被抽出来,和其他稳定的代码有所区分。

现在我们来尝试解决第二个和第三个问题,其实他俩可以被划分为同一个问题,即代码的重用

其实可以这样做,定义一个接口将work这个方法抽象出去,然后定义一些类去实现这个doWork()方法,每个类代表了每个不同的工作

1
2
3
public interface Work {
public void doWork();
}
1
2
3
4
5
6
public class StudyWork implements Work{
@Override
public void doWork() {
System.out.println("我是年轻人,学习是我的工作");
}
}
1
2
3
4
5
6
public class ProgrammeWork implements Work{
@Override
public void doWork() {
System.out.println("我是一名程序员,我只能敲代码");
}
}

这样当使用的时候,只需要使用实现的类即可,这样相同的工作就达到了代码复用的原则

这就引出了第二个设计原则:

针对抽象编程,而不是根据实现编程

以Young这个类为例,详细一下使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Young extends Person {
private Work work;

public Young(Work work) {
this.work = work;
}

public void setWork(Work work) {
this.work = work;
}

public void doHimselfWork() {
work.doWork();
}
}

是不是很清晰了,通过构造器指明他从事的工作,并且利用多态的性质,随时可以改变工作的类型,比如换工作了。如果这时候又增加了一类人,这类人从事的工作很少见,我们就可以再创建一个类实现Work接口,然后给这类人,这期间并没有修改原有的代码,真正的让设计具有了弹性

对UML类图不了解的小伙伴可以查看https://juejin.cn/post/6872242138378141710

另外需要说到的一点就是“有一个”比“是一个”更好,这句话的含义引出了第三个设计原则:

少用继承,多用组合

最后给出策略模式的定义:

策略模式定义了算法簇,分别封装起来,让他们之间可以互相替换,此模式让算法的替换独立于使用算法的客户

总结

我们从一个场景出发,首先进行了一个简略的封装,到一步一步提高封装的粒度,到最后使用策略模式,这期间引出了三个设计原则:

  • 找出应用中可能需要变化之处,把他们独立出来并封装,不要和那些不需要变化的代码混合在一起
  • 针对抽象编程,而不是根据实现编程
  • 少用继承,多用组合
CATALOG
  1. 1. 设计模式-策略模式
    1. 1.1. 场景
    2. 1.2. 存在的问题
    3. 1.3. 解决方案
    4. 1.4. 总结