今天一起讨论下设计模式中的策略模式,大家平时在用java的时候会用到类似:Collections.sort(List<T> list)的函数来实现排序功能,其实这个排序功能用到的就是策略模式。可以深究下Colloections.sor的源码去跟踪一下。此处暂时不详细展开,后面再展开。
策略模式(Strategy Pattern)体现出两个面向对象的原则;封装变化的概念,编程中使用接口,而不是对接口的实现。
策略模式的定义:定义一组算法,将每个算法都封装起来,并且他们之间可以互换,策略模式使这些算法在客户端调用他们的时候可以互不影响地变化。
策略模式的意义:使开发人员可以开发出许多可以替换部分组成的软件,并且各个部分是弱连接关系。弱连接的特性使软件具有很强的扩展性,易于维护,更重要的是他可以大大提高软件的可重用性。
策略模式的组成:
1)抽象策略角色:策略类,通常由一个接口或者是抽象类组成;
2)具体策略角色:实现抽象策略角色的接口,包装了相关的算法和行为。这个一般有多个
3)环境角色:持有一个策略类的引用,最终给客户端调用的。
策略模式的实现:
1)策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
2)策略模式使得算法可以在不影响到客户端的情况下发生变化。使用策略模式可以把行为和环境分割开来。
3)环境类负责维持和查询行为类,各种算法则在具体策略中提供。由于算法和环境独立开来,算法的修改都不会影响环境和客户端。
策略模式的编写步骤
1)对策略对象定义一个公共接口。
2)编写策略类,该类实现了上面的公共接口。
3)在使用策略对象的类中保存一个对策略对象的引用。环境类。
4)在使用策略对象的类中,实现对策略对象的set和get方法(注入)或者使用构造方法完成赋值。---这里说的是上面的环境类。
注意,这里可以去参看JDK Collections类的源代码来进行很好的学习。
例子:
这里举一个策略类的例子,按照策略模式编写的步骤来操作:
第一步:
Strategy.java
package com.java.strategy;
/**
*
*
* @author bluesky
* 第一步:对策略对象定义一个公共接口。
*
*/
public interface Strategy {
public int caculation(int a,int b);
}
第二步:
编写策略类,该类实现了上面的公共接口。
共有加减乘除四个具体的策略类:
1.AddStrategy.java--->加法类
package com.java.strategy;
/**
*
* @author bluesky
* 编写策略类,该类实现了上面的公共接口。这里具体功能是现实了加法运算
*/
public class AddStrategy implements Strategy {
@Override
public int caculation(int a, int b) {
// TODO Auto-generated method stub
return a+b;
}
}
2.SubtraceStrategy.java--->减法类
package com.java.strategy;
/**
*
* @author bluesky
* 编写策略类,该类实现了上面的公共接口。这里具体功能是实现了减法运算
*/
public class SubtraceStrategy implements Strategy {
@Override
public int caculation(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
}
3.MultiplyStrategy.java --->乘法类
package com.java.strategy;
/**
*
* @author bluesky
* 编写策略类,该类实现了上面的公共接口。这里具体功能是实现了乘法运算
*/
public class MultiplyStrategy implements Strategy {
@Override
public int caculation(int a, int b) {
return a*b;
}
}
4.DivStrategy.java --->除法类
package com.java.strategy;
/**
*
* @author bluesky
* 编写策略类,该类实现了上面的公共接口。这里具体功能是实现了除法运算
*/
public class DivStrategy implements Strategy {
@Override
public int caculation(int a, int b) {
return a/b;
}
}
第三步:实现环境类
Environment.java
环境类里面也有和接口中的同一个方法,然后调用接口类中的方法即可。
package com.java.strategy;
/**
*
*
* @author bluesky
*
*在使用策略对象的类中保存一个对策略对象的引用。
*/
public class Environment {
private Strategy strategy;
public Environment(Strategy strategy) {
this.strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
//关键的一个函数
public int caculation(int a,int b){
return strategy.caculation(a, b);
}
}
第四步:客户端调用
Cletent.java
package com.java.strategy;
public class Client {
/**
* @param args
* 客户端调用
*/
public static void main(String[] args) {
int a = 40;
int b = 8;
AddStrategy addStrategy = new AddStrategy();
//环境端初始化
Environment enviroment = new Environment(addStrategy);
System.out.println("add: "+enviroment.caculation(a, b));
SubtraceStrategy subStrategy = new SubtraceStrategy();
//直接通过环境段的setter来改变算法接口的行为,而且不影响其它的
enviroment.setStrategy(subStrategy);
System.out.println("substract: "+enviroment.caculation(a, b));
MultiplyStrategy multiplyStrategy = new MultiplyStrategy();
//直接通过环境段的setter来改变算法接口的行为,而且不影响其它的
enviroment.setStrategy(multiplyStrategy);
System.out.println("multiply: "+enviroment.caculation(a, b));
DivStrategy divStrategy = new DivStrategy();
//直接通过环境段的setter来改变算法接口的行为,而且不影响其它的
enviroment.setStrategy(divStrategy);
System.out.println("Div: "+enviroment.caculation(a, b));
}
}
结果:
add: 48
substract: 32
multiply: 320
Div: 5
策略模式相对来说实现的步骤比较固定,用户需要知道在用的时候需要的实体类。从以上也能看到策略模式的缺点:
1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
2)造成很多的策略类。
这个有个解决办法就是:可以采用工厂方法来进行。工厂方法也是一种设计模式,相对用的也比较多。
举个例子:
一个person类,要求用策略模式进行排序,按照姓名、年龄、ID的方式升序,如果姓名、年龄相等,则按照ID排序。
首先定义person类:
package com.java.strategydemo;
public class Person {
public String name;
public int age;
public int id;
public Person(String name,int age,int id) {
this.name = name;
this.id = id;
this.age = age;
}
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 getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
String ret = "name: "+ name+" age: "+age+" id: "+id+"\n";
return ret;
}
}
然后定义策略类(这里的策略类其实是实现了java标准API里面的Comparator的接口,这个接口就是前面讲到的接口),此处定义升序和降序两个类:
升序:
AscendSort.java
package com.java.strategydemo;
import java.util.Comparator;
public class AscendSort implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
//mingzi nianling id
int nameC = o1.name.compareTo(o2.name);
int ageC = o1.age - o2.age;
int idC = o1.id - o2.id;
if(nameC!=0){
return nameC;
}else{
if(ageC!=0){
return ageC;
}else{
return idC;
}
}
}
}
降序:
DesendSort.java
package com.java.strategydemo;
import java.util.Comparator;
public class DescendSort implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
//mingzi nianling id
int nameC = o2.name.compareTo(o1.name);
int ageC = o2.age - o1.age;
int idC = o2.id - o1.id;
if(nameC!=0){
return nameC;
}else{
if(ageC!=0){
return ageC;
}else{
return idC;
}
}
}
}
最后是测试类:
TestMain.java
package com.java.strategydemo;
import java.awt.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
public class TestMain {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Person p1 = new Person("zhangsan", 20, 1);
Person p2 = new Person("zhangsan", 21, 3);
Person p3 = new Person("zhangsan", 21, 2);
Person p4 = new Person("lisi", 30, 13);
Person p5 = new Person("wangwu", 10, 12);
Person p6 = new Person("mazi", 50, 10);
java.util.List<Person> list = new ArrayList<Person>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(p4);
list.add(p5);
list.add(p6);
Collections.sort(list, new AscendSort());
Iterator<Person> it = list.iterator();
while(it.hasNext()){
Person p = it.next();
System.out.println(p);
}
System.out.println("===================================");
Collections.sort(list, new DescendSort());
Iterator<Person> it2 = list.iterator();
while(it2.hasNext()){
Person p = it2.next();
System.out.println(p);
}
}
}
输出结果如下:
name: lisi age: 30 id: 13
name: mazi age: 50 id: 10
name: wangwu age: 10 id: 12
name: zhangsan age: 20 id: 1
name: zhangsan age: 21 id: 2
name: zhangsan age: 21 id: 3
===================================
name: zhangsan age: 21 id: 3
name: zhangsan age: 21 id: 2
name: zhangsan age: 20 id: 1
name: wangwu age: 10 id: 12
name: mazi age: 50 id: 10
name: lisi age: 30 id: 13
就是按照我们i所期望的排序策略进行排序了。这里的Collections其实就是我们前面所讲的环境类,在环境类里面可以调用升序和降序的实际策略类,接口这块毫无感知具体的变化。当然缺点也暴露了,你必须知道这里面实现了Comaparator接口的具体的排序类。