说到策略模式,见名知意,重点在策略。
那么什么时候会用到策略模式呢?
我们通过下面的这个例子进行一下学习
首先为了引出策略模式,我们需要先了解一下泛型 , 什么是泛型呢,下面是百度百科的介绍:
泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。下面我们就来看一个简单的泛型代码。
public interface TestInterface<T>{
public test(T o){
}
}
上面就是一段简单的使用泛型定义的接口的代码。
在上面代码中我们看到的T是在实例化时作为参数指明的类型
假设我们有一个Test类要实现上边的TestInterface接口我们可以这么写
public class Test implements TestInterface<Test>{
@Override
public test(Test){
// 具体实现
}
}
也就是说 , 这个接口的参数类型可以通过我们实现的时候指定。
下面我们来做一个示例吧
首先我们先来写一个简单的排序 , 我们就用最简单的选择排序吧
package com.starcpdk.test;
public class Sorter {
public void sort(int[] arr){
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j] > arr[minIndex] ? j : minIndex;
}
swap(arr , i , minIndex);
}
}
private void swap(int[] arr, int i, int minIndex) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
这个排序功能基本上都实现了。我们传入两个int数 , 也完全可以排序
但是存在一个问题,我们想给float,想给其他类型去做排序怎么办呢 , 此时就会有人说 , 我们可以写重载方法,写他一堆重载方法,让他适配各种类型的参数排序。如果只有基础的八大数据类型,这种方式也不是不可以,但是如果我们又想给对象排序怎么办呢?对象该怎么排序呢?我们想到的肯定是根据对象的某个属性进行排序。那么我们看下下面这个例子。
package com.starcpdk.test;
public class Cat {
public int weight, height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
public int compareTo(Cat cat) {
if (this.height > cat.height) return 1;
else if (this.height < cat.height) return -1;
else return 0;
}
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
package com.starcpdk.test;
public class Sorter {
public void sort(Cat[] arr){
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j].compareTo(arr[minIndex]) == -1 ? j : minIndex;
}
swap(arr , i , minIndex);
}
}
private void swap(Cat[] arr, int i, int minIndex) {
Cat temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
我们看到上面的代码,发现他可以解决我们比较对象的问题了,但是 , 这个排序类还是不够通用,我们现在用Cat对象可以用这个方法进行排序,那么我们换成了Dog想要排序,我们这个方法就无法进行使用了。此时我们也不适合写重载方法了,毕竟对象是无限的,我们无法做到定义那么多,每增加一个对象,我们就需要在这个排序类中加一个方法,这不符合开闭原则,因此,我们想到 ,所有对象不都是Object的子类嘛 , 那我们参数处统一使用Object接收对象是不就完美了。我们尝试使用Object数组接收参数,但是我们很快发现又面临了一个新问题,Object方法中没有compareTo这样的比较方法呀 , 那该怎么办呢?
既然不能用Object,那么我们想到一种和Object思想一样的,就是我们自己写一个接口 (Comparable),我们在我们自己写的接口中定义一个方法compareTo() , 此时我们想用这个排序类进行排序的对象,我们都让他实现我们的接口。请看下面的代码
package com.starcpdk.test;
public interface Comparable {
int compareTo(Object o);
}
package com.starcpdk.test;
public class Dog implements Comparable{
int food;
public Dog(int food){
this.food = food;
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
@Override
public int compareTo(Object o) {
Dog dog = (Dog) o;
if (this.food > dog.food) return 1;
else if (this.food < dog.food) return -1;
else return 0;
}
}
package com.starcpdk.test;
public class Sorter {
public void sort(Comparable[] arr){
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j].compareTo(arr[minIndex]) == -1 ? j : minIndex;
}
swap(arr , i , minIndex);
}
}
private void swap(Comparable[] arr, int i, int minIndex) {
Comparable temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
我们看到上面的代码他可以接受任意对象进行比较了 , 我们想比较什么对象 , 我们就让那个对象实现Comparable接口,然后重写Comparable接口中的比较方法就好。但是上述还是存在着问题,我们看到Comparable接口中的compareTo方法为了能够让任何对象都可以用,我们给了一个Object类型的参数。但是我们给了Object类型参数就意味着我们在实现了这个接口的方法中我们需要将Object强转为我们当前对象的类型 , 这样就存在一个问题,那就是我们如果给compareTo传入的类型并不是我们重写的方法中进行强转时候用的对象类型,就会抛出异常。为了避免这个问题,我们尝试引入泛型。请看下面的代码。
package com.starcpdk.test;
public interface Comparable<T> {
int compareTo(T o);
}
package com.starcpdk.test;
public class Dog implements Comparable<Dog>{
int food;
public Dog(int food){
this.food = food;
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
@Override
public int compareTo(Dog dog) {
if (this.food > dog.food) return 1;
else if (this.food < dog.food) return -1;
else return 0;
}
}
我们看到上面的代码感觉就好了很多了,此时我们可以做到各种对象传入这个方法都可以进行比较了,无需修改我们的Sorter类。
虽然很完美了,但是这个代码中仍旧存在着一定的问题,我们的比较策略是存在问题的,我们要比较的对象都实现了Comparable接口,重写了CompareTo方法,但是我们一个对象只重写一次compareTo方法,也就是说写完这个方法就等于定死了我们要用对象中的哪儿个属性去做比较,比较大小的方式就固定了,假如我们Cat类重写了compareTo方法 , 我们重写的方法实现是 使用cat对象的height属性进行比较。但是我们有另外一个场景需要使用cat的weight属性做比较,那该怎么办。我们就必须修改Cat类中重写的这个方法吗?这不太合适,我们开发中有一个原则便是开闭原则,只对扩展开放,对修改关闭,好的代码时可以扩展而不需破坏原有代码。此时我们引入策略模式 , 我们定义一个比较策略,在客户端进行比较的时候将该策略传入让其比较。具体实现请看下面的代码。
package com.starcpdk.test;
public class Sorter<T> {
public void sort(T[] arr , Comparetor<T> comparetor){
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = comparetor.compare(arr[j] , arr[minIndex]) < 0 ? j : minIndex;
}
swap(arr , i , minIndex);
}
}
private void swap(T[] arr, int i, int minIndex) {
T temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
package com.starcpdk.test;
public interface Comparetor<T> {
int compare(T o1 , T o2);
}
package com.starcpdk.test;
public class Cat {
public int weight, height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
//
// public int compareTo(Cat cat) {
// if (this.height > cat.height) return 1;
// else if (this.height < cat.height) return -1;
// else return 0;
// }
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
可以看到我们上面的代码就很完美了 , 我们传入各种类型的对象都可以比较,同时比较的策略也可以让我们自定义,可以说是很完美了,上边的代码就是策略模式。
我们来画一下策略模式的类图吧
上面的例子涉及泛型可能一时间不是很好理解,我们来写一个简单的策略模式
假设我们有一个场景,需要对对象进行加法减法乘法等多种操作。
加 , 减 , 乘 这些操作就是不同的策略
我们先写一个策略接口。
package com.starcpdk.test;
public interface Strategy {
int doOperation(int num1 , int num2);
}
然后我们写一个具有行为的操作
package com.starcpdk.test;
public class Operation {
private Strategy strategy;
public Operation(Strategy strategy){
this.strategy = strategy;
}
public int executeOperation(int a , int b){
return strategy.doOperation(a , b);
}
}
这个操作是通过我们传入不同的策略进行不同的操作。
package com.starcpdk.test;
public class Main {
public static void main(String[] args) {
int a = new Operation(new Strategy() {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}).executeOperation(1,2);
System.out.println(a);
}
}
我理解的什么时候会用到策略模式呢?
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、 一个系统需要动态地在几种算法中选择一种。
3、 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
有N个算法,它们要实现的目的是相同的,所以各算法之间是可以互相替换的,每个客户端独立选择使用哪一个算法,不同的客户端之间可以选择不同的算法。
就是说行为一致,但是这个行为里面具体的策略可以相互替换。
比如说,我们从家到公司goToCompany() 我们以坐公交 , 骑自行车 , 坐地铁多种方式都可以。
做得都是一个行为 , 但是不同的条件,这个行为具体怎么做不同,就可以用策略模式
就比如说 , 计算工时差异这样一个行为,如果工作日历,是一种计算方式,如果普通日历又是一种计算方式,那么我们就可以用策略模式。
我们写一个策略接口,里面有一个计算工时的方法。这个方法需要两个参数,分别是起始时间,和结束时间。然后我们在调用计算工时的方法时,传入我们的策略,以什么策略去计算。
发生的动作一样,但是动作的执行规则不一样,我们可以使用策略模式。