Java设计模式-策略模式(Decorate)
目录
- 什么是策略模式
- JavaSE策略模式的应用
- Struts2策略模式的应用
1、策略模式是为了解决不同算法的自由切换,而不影响执行环境
2、每个设计模式都有自己的应用场景和优缺点
一、什么是策略模式
策略模式有点像JAVA的类型转换
**定义:**基本含义是针对一组算法或者行为特性,将他们抽象到具有共同接口函数的独立抽象类或者接口中,从而使得他们可以相互替换。这样就使得某一个特定的接口行为可以在不影响客户端的情况下发生变化。–取自《Struts2技术内幕》
下图是对策略模式的UML类图
策略模式的核心角色:
- Strategy(抽象策略):对算法、行为的抽象,一般为接口
- context(执行环境):被称为上下文,有点难理解,其实是对策略的二次封装,避免其它模块直接调用具体的策略。context中有一个Strategy类引用,在context中决定要调用哪个ConcreteStrategy
- ConcreteStrategy(具体策略):对具体的策略、算法的实现,由Context调用
就像装饰模式一样,大多设计模式、编程思想都讲松耦合作为设计的基本原则,策略模式同样也是,通过上面的内容我们很容易得到策略模式其实就2个大的角色
- 具体策略:特点是策略是多变的
- 业务场景:同样也是多变的
两个多变的东西强耦合会让代码变得混乱不堪,所以策略模式的核心是将策略与业务解耦,也就是将算法实现与算法环境进行解耦,使其各自独立。算法的增加、修改都不会影响到业务。
什么样的情况下会用到策略模式?
我们来完成一个业务需求:开发一个导航业务,完成步行导航功能;完成后又要增加自行车导航、汽车导航。。。
下面的代码可以完成功能吗?
public class Navigation{
private String start;
private String end;
public Navigation(String start, String end){
}
public void navigating(String preferendence){
if (preference == "大路优先"){
System.out.println("从 x 到 x,导航策略" + preferendence);
}
if (preference == "距离优先"){
System.out.println("从 x 到 x,导航策略" + preferendence);
}
if (preference == "时间最快"){
System.out.println("从 x 到 x,导航策略" + preferendence);
}
}
}
public class Client {
public static void main(String[] args) {
Navigation bj2qd = new Navigation("北京","青岛");
bj2qd.navigating("大路优先");
Navigation bj2xa = new Navigation("北京","西安");
bj2xa.navigating("时间最快");
Navigation bj2nn = new Navigation("北京","南宁");
bj2xa.navigating("时间最快");
}
}
上面的代码可以完成功能,但问题很大:
- if太多,不够优雅
- 增加任何需求都要修改Navigation代码,这直接会导致所有使用Navigation类的代码都要重新测试一遍,给系统带来极大的工作量
- 而且这也违法了JAVA开发的开闭原则、单一职责原则等等
来看看下面的实现方式
public class DLYX {
private String start;
private String end;
public DLYX(String start, String end){
this.start = start;
this.end = end;
}
public void navigating(){
System.out.println("从 " + start + " 到 " + end + ",导航策略:大路优先");
}
}
public class JLYX {
private String start;
private String end;
public JLYX(String start, String end){
this.start = start;
this.end = end;
}
public void navigating(){
System.out.println("从 " + start + " 到 " + end + ",导航策略:距离优先");
}
}
public class SJZK {
private String start;
private String end;
public SJZK(String start, String end){
this.start = start;
this.end = end;
}
public void navigating(){
System.out.println("从 " + start + " 到 " + end + ",导航策略:时间最快");
}
}
/**
* 将导航策略进行了封装,可以灵活的修改导航策略的细节
* 但是!!!北京到青岛我必须要用大路优先吗?我赶时间,我选时间最快,怎么实现?重新选择策略,然后在输入目的地吗?
* 目前这种形式就不太行了!
*/
public class Client {
public static void main(String[] args) {
DLYX bj2qd = new DLYX("北京","青岛");
bj2qd.navigating();
SJZK bj2xa = new SJZK("北京","西安");
bj2xa.navigating();
JLYX bj2nn = new JLYX("北京","南宁");
bj2nn.navigating();
}
}
上面的代码将每种导航模式进行单独封装,增强了代码的灵活性,但还是有问题。
是用户自己选择导航模式吗?
正常的业务应该是用户输入地址,然后出现策略,用户自己选择,而现在恰恰相反,所以不合理,我们需要一个策略的【调度者】,再看下使用策略模式写的代码
public interface Navigation {
public String navigating();
}
public class GaoDe implements Navigation{
private Navigation navigation;
private String start;
private String end;
public GaoDe(String start, String end, Navigation navigation){
this.start = start;
this.end = end;
this.navigation = navigation;
}
public String navigating() {
String navigation = "从 " + start + " 到 " + end + ",导航策略:" + this.navigation.navigating();
System.out.println(navigation);
return navigation;
}
}
public class DLYX implements Navigation{
public DLYX(){
}
public String navigating(){
String navigation = "大路优先;";
navigation += "正在车辆引导";
return navigation;
}
}
public class JLYX implements Navigation{
public JLYX(){
}
public String navigating(){
String navigation = "距离优先;";
navigation += "正在车辆引导";
return navigation;
}
}
/**
* 现在可以满足北京-青岛的大路优先的策略
* 如果要改为距离优先只需要修改GaoDe的策略参数即可
*
* 如果要修改策略,例如大路优先的同时,要增加车辆引导,那么只需要修改DLYX策略类的代码即可,无需修改Client与GaoDe
*/
public class Client {
public static void main(String[] args) {
GaoDe gaode = new GaoDe("北京","青岛",new JLYX());
gaode.navigating();
}
}
代码复杂了很多,但是清晰了很多,扩展性更好了我将策略封账到了GaoDe中,我只需要告诉GaoDe目的地即可,至于我选择什么样的策略,只需要修改策略参数就行,如果策略实现类需要修改,那也仅仅修改策略类即可,对于用户Client、GaoDe都没有任何影响
该代码中,策略模式的核心角色:
- Strategy(抽象策略):对算法、行为的抽象,一般为接口–Navigation接口
- context(执行环境):被称为上下文,有点难理解,其实是对策略的二次封装,避免其它模块直接调用具体的策略。context中有一个Strategy类引用,在context中决定要调用哪个ConcreteStrategy – GaoDe类
- ConcreteStrategy(具体策略):DLYX、JLYX类
总结下策略模式的优缺点:
优点:
- 支持开闭原则,系统可以在不修改原代码的情况下灵活的增加新的策略、算法
- 符合单一职责原则
- 策略可以复用,避免重复代码
缺点:
- context(执行环境)必须要知道所有的策略,自行决定使用哪种策略,这就意味着必须得了解所有的算法
- 策略类会逐渐增多
- 策略不能叠加使用,不会像装饰模式一样叠加,这就导致策略类增多
每个设计模式都有自己的使用场景,不同的业务要选择恰当的设计模式。
二、JAVASE策略模式的应用
java.util.Comparator中使用了策略模式,首先分析下该接口
- Strategy(抽象策略):对算法、行为的抽象,一般为接口–Comparator接口
- context(执行环境):被称为上下文,有点难理解,其实是对策略的二次封装,避免其它模块直接调用具体的策略。context中有一个Strategy类引用,在context中决定要调用哪个ConcreteStrategy – Collections、Arrays类
- ConcreteStrategy(具体策略):自定义类
现在要实现一个需求,对People类按年龄进行灵活排序:升序、降序,如何实现?
可以选择自己实现排序算法,但没有必要,JAVA已经提供了对应的接口,我们只需要扩展即可
下面看代码
// people类
package org.strategy.compare;
public class People {
private int age;
private String name;
public People(int age, String name){
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "{age=" + age + ", name='" + name + "'}";
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 降序
*/
import java.util.Comparator;
public class DescSortor implements Comparator<People> {
public int compare(People o1, People o2) {
return o1.getAge() - o2.getAge();
}
}
import java.util.Comparator;
/**
* 升序
*/
public class AscSortor implements Comparator<People> {
public int compare(People o1, People o2) {
return o2.getAge() - o1.getAge();
}
}
// 测试类
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
People peoples[] = {
new People(11,"A"),
new People(22,"B"),
new People(33,"C"),
};
thisPrint(peoples, "排序前");
Arrays.sort(peoples, new AscSortor());
thisPrint(peoples, "降序后");
Arrays.sort(peoples, new DescSortor());
thisPrint(peoples, "生序后");
}
public static void thisPrint(People[] peoples, String desc){
String res = desc;
for (int i = 0; i < peoples.length; i++) {
res += ", " +peoples[i].toString();
}
System.out.println(res);
}
}
测试结果如下
排序前, {age=11, name='A'}, {age=22, name='B'}, {age=33, name='C'}
降序后, {age=33, name='C'}, {age=22, name='B'}, {age=11, name='A'}
生序后, {age=11, name='A'}, {age=22, name='B'}, {age=33, name='C'}
真的非常方便,通过使用Arrays.sort()方法可以快速实现自定义类的排序,我们只需要关心自己的具体策略即可,扩展性非常好。
这里插一个题外话,为什么DescSortor的compare方法是这样写?
public class DescSortor implements Comparator<People> {
public int compare(People o1, People o2) {
return o1.getAge() - o2.getAge();
}
}
这与Java中的Arrays.sort方法有关系,Arrays会在调用TimSort.sort方法
如果c.compare的结果小于0则调用reverseRange方法,该方法是数组顺序调整
而c.compare的c对象便是DescSortor类,也就是我们自定义的排序类,换句话说,如果
o1.getAge() 比 o2.getAge()小,就调换顺序,也就是大的在前面小的在后面,就是降序。
三、Struts2策略模式的应用
Struts2的配置初始化是典型的策略模式应用,Struts2中的配置文件多种多样,例如xml、properties等,而无论有多少种类,都可以使用同一的策略加载器去加载不同的策略,只要他们的特征是一样的,例如init方法、register方法。
下图是Struts2-2.0.8中加载配置文件的流程,通过Dispatcher.init加载,但此处并未真正加载,只是统一放到ConfigurationManager的configurationProviders对象中
在ConfigurationManager中调用reload方法进行真正的加载
下图为Struts2配置加载的策略模式的类关系图
- Strategy(抽象策略):对算法、行为的抽象,一般为接口–ConfigurationProvider接口
- context(执行环境):被称为上下文,有点难理解,其实是对策略的二次封装,避免其它模块直接调用具体的策略。context中有一个Strategy类引用,在context中决定要调用哪个ConcreteStrategy – ConfigurationManager类
- ConcreteStrategy(具体策略):XmlConfigurationProvider等类
通过以上分析,Struts2的配置加载使用了策略模式