1、从项目"模拟鸭子游戏"实例讲解策略模式:假如一个有个鸭子,他会叫、会飞、肤色表现不一样,有不同颜色的鸭子,而且叫声不同、飞的方式也不同,要设计这样一个项目
2、从面向对象的角度设计这个项目
2.1 以封装、继承、多态的三个特点会抽象出鸭子超类,扩展超类:
public abstract class Duck{
public abstract void display();
public void swim(){
System.out.println("I can swim");
}
public void quack(){
System.out.println("gaga~~");
}
public void fly(){
System.out.println("I can fly");
}
}
2.2 然后不同定义不同颜色的鸭子继承分类Duck:
public class GreenHeadDuckextends Duck {
@Override
public void display() {
System.out.println("**GreenHead**");
}
}
public class RedHeadDuckextends Duck {
@Override
public void display() {
System.out.println("**ReadHeadDuck");
}
}
2.3 若此时新增需求,添加鸭子会飞的功能,当然按照面向对象的继承原则,我们可以在超类里添加一个fly()方法,
public abstract class Duck {
...;
public void Fly() {
System.out.println("~~im fly~~");
}
};
出现的问题:这个fly方法一加,让所有的子类都会飞了,这不科学。
继承的问题:对类的局部改动,尤其对超类的局部改动,会影响子类其他部分。影响会有溢出效应。
2.4 这时可以继续按照面向对象的继承以及多态原则进行改造,把超类的fly()方法定义成抽象类,子类重写这个fly方法
父类:
public abstract class Duck {
...;
public abstract void Fly() ;
};
子类:
public class GreenHeadDuck extends Duck {
...;
public void Fly() {
System.out.println("~~no fly~~");
}
}
2.5 但问题又来了,如果又新增一个需求,添加会游泳的验证,而且不同的鸭子游泳的方式不一样,这时按照面向对象的原则,又在超类上添加一个抽象方法,接着子类重写。若再添加一个新功能,还这样无限重复。超类每挖一个坑,每个子类都要填,增加工作量。不是好的设计方式。
那应该如何设计呢?
方法:分析项目中变化与不变的部分,提取变化部分,抽象成接口+实现
3、利用策略模式改造项目
3.1提取项目中变化的部分,抽象成接口。
//飞行接口
public interface FlyBehavior {
void fly();
}
//叫声接口
public interface QuackBehavior {
void quack();
}
3.2 超类中引用这两个接口的变量,在对应的方法上调用接口的方法,提供空的构造函数。子类在其构造函数上初始化对应行为的实现类,便具有其行为。
//父类抽象类
//父类抽象类
public abstract class Duck{
/**
* 飞行行为接口
*/
FlyBehaviorflyBehavior;
/**
* 叫声行为接口
*/
QuackBehaviorquackBehavior;
/**
* 提供空的构造函数,以便子类调用,子类调用时,new出来的行为是啥子类就是啥行为
*/
public Duck(){
}
public void fly(){
flyBehavior.fly();
}
public void swim(){
System.out.println("I can swim");
}
public void quack(){
quackBehavior.quack();
}
public abstract void display();
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
子类实现类
public class GreenHeadDuckextends Duck {
public GreenHeadDuck(){
flyBehavior =new GoodFlyBehavior();
quackBehavior =new GaGaQuackBehavior();
}
@Override
public void display() {
System.out.println("**GreenHeadDuck");
}
}
若要添加新的飞行方式或者添加新的叫声方式,只需写个实现类实现FlyBehavior或QuackBehavior接口。若要添加新需求,添加会游泳的鸭子,只需新创建各SwimBehavior接口,提供实现类,并在超类上添加这个属性,各子类需要游泳行为的,在其构造函数上,给swimBehavior赋值响应的实现类即可。
4、使用策略模式相比于面向对象的好处:
新增行为简单,行为类更好的复用,组合更方便。既有继承带来的复用好处,没有挖坑。
5、方法论:
当需要新的设计方式,应对项目的可扩展性,降低复杂度:
1)分析项目变化与不变的部分,提取变化部分,抽象成接口+实现
2)多用组合少用继承;用行为类组合,而不是行为的继承。更有弹性。
6、策略模式:
分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。
1)定义
策略模式是行为模式的一种,它对一系列的算法加以封装,为算又的算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体算法选择交由客户端决定。
2)原则
分离变化部分,封装接口,基于接口编程各种功能。此模式让行为算法的变化独立于算法的使用者。
3)策略模式的角色和原则
1、Strategy:策略抽象(算法抽象),本例的算法抽象是行为接口(FlyBehavior)以及叫声接口(QuackBehavior)
2、ConcreteStrategy:各种策略算法的具体实现,本例的策略算法的具体实现是GoodFlyBehavior、BadFlyBehavior、GuGuQuackBehavior、GaGaQuackBehavior
3、Context:策略的外部封装类,或者说是策略的容器类。根据不同的策略执行不同的行为。策略由外部环境决定的。本例中Context是GreenHeadDuck、RedHeadDuck
4)策略模式的优点
1、策略模式提供了管理相关的算法族的办法,策略类的登级结构定义了一个算法或行为族。恰当地使用继承可以把公共的代码移到父类里面,从而避免重复的代码
2、策略模式提供了可以替换继承关系的办法。继承可以处理多中算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为,但是,这样一来算法或行为的使用者和算法和行为本身混在了一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
3、使用策略模式可以避免多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
5)策略模式的缺点
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道的所有的算法或行为的情况
2、策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面。而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量
7、Spring中使用的策略模式
1)spring资源访问的方式
Resource接口是Spring资源访问策略的抽象,它本身并不提供任何资源访问实现,具体资源访问由该接口的实现类完成——每个实现类代表一种资源访问。
Spring 为 Resource 接口提供了如下实现类:
UrlResource:访问网络资源的实现类。
ClassPathResource:访问类加载路径里资源的实现类。
FileSystemResource:访问文件系统里资源的实现类。
ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
InputStreamResource:访问输入流资源的实现类。
ByteArrayResource:访问字节数组资源的实现类。
从上面的例子中可以看到,使用策略模式可以让客户端在不同的飞行行为和叫声行为中切换,但也有一个小小的不足,客户端代码需要和不同的策略类耦合。
下面我们回到spring框架里,看看Spring框架的Context如何智能地选择资源访问策略。
ResourceLoader接口和ResourceLoaderAware接口
spring提供了两个标志性接口:
ResourceLoader:该接口的实现类的实例可以获得一个Resource实例。
ResourceLoaderAware:该接口的实现类的实例将获得一个ResourceLoader实例。
在ResourceLoader接口里有如下方法:
Resource getResource(String location):该接口仅包含这个方法,该方法用于返回一个 Resource 实例。ApplicationContext 的实现类都实现 ResourceLoader 接口,因此 ApplicationContext 可用于直接获取 Resource 实例。
此处spring框架的ApplicationContext不仅是Spring容器,而且它还是资源访问策略的"决策者",也就是策略模式中Context对象,它将为客户端代码"智能"地选择策略实现。
当ApplicationContext 实例获取Resource实例时,系统将默认采用与ApplicationContext相同的资源访问策略。对于如下代码:
//通过ApplicationContext访问资源
Resource res = ctx.getResource("application.xml");
从上面代码中无法确定 Spring 将哪个实现类来访问指定资源,Spring 将采用和 ApplicationContext 相同的策略来访问资源。也就是说:如果 ApplicationContext 是 FileSystemXmlApplicationContext,res 就是 FileSystemResource 实例;如果 ApplicationContext 是 ClassPathXmlApplicationContext,res 就是 ClassPathResource 实例;如果 ApplicationContext 是 XmlWebApplicationContext,res 是 ServletContextResource 实例.
看如下示例程序,下面程序将使用 ApplicationContext 来访问资源:
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("beans.xml");
Resource res = context.getResource("beans.xml");
System.out.println(res.getFilename());
System.out.println(res.getDescription());
}
结果:
beans.xml
class path resource [beans.xml]
8、阿里支付宝官方支付sdk中使用的策略模式
1)策略模式中Context对象是AlipayClient
2)策略模式中的Strategy对象是AlipayRequest、AlipayResponse
这两个接口封装了所有的支付宝请求、和返回值。
AlipayResponse代码
public abstract class AlipayResponseimplements Serializable {
private static final long serialVersionUID =5014379068811962022L;
@ApiField("code")
private String code;
@ApiField("msg")
private String msg;
@ApiField("sub_code")
private String subCode;
@ApiField("sub_msg")
private String subMsg;
private String body;
private Map params;
public AlipayResponse() {
}
/** @deprecated */
@Deprecated
public String getErrorCode() {
return this.getCode();
}
/** @deprecated */
@Deprecated
public void setErrorCode(String errorCode) {
this.setCode(errorCode);
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return this.msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getSubCode() {
return this.subCode;
}
public void setSubCode(String subCode) {
this.subCode = subCode;
}
public String getSubMsg() {
return this.subMsg;
}
public void setSubMsg(String subMsg) {
this.subMsg = subMsg;
}
public String getBody() {
return this.body;
}
public void setBody(String body) {
this.body = body;
}
public Map getParams() {
return this.params;
}
public void setParams(Map params) {
this.params = params;
}
public boolean isSuccess() {
return StringUtils.isEmpty(this.subCode);
}
}
AlipayRequest代码
public interface AlipayRequest {
String getApiMethodName();
Map getTextParams();
String getApiVersion();
void setApiVersion(String var1);
String getTerminalType();
void setTerminalType(String var1);
String getTerminalInfo();
void setTerminalInfo(String var1);
String getProdCode();
void setProdCode(String var1);
String getNotifyUrl();
void setNotifyUrl(String var1);
String getReturnUrl();
void setReturnUrl(String var1);
Class getResponseClass();
boolean isNeedEncrypt();
void setNeedEncrypt(boolean var1);
AlipayObject getBizModel();
void setBizModel(AlipayObject var1);
}
3)ConcreteStrategy算法具体实现对象:AlipayTradePagePayRequest、AlipayTradePageResponse