举例:
方案一:
写一个鸭子抽象类,里面有鸭子的一些行为方法,还有鸭子的描述。然后各种不同鸭子实现这个接口。
问题:
我们在鸭子抽象类中定义了鸭子飞行和游泳方法,但是北京烤鸭或者玩具鸭并不能飞或者游泳,这时候我们就要重写这些方法
方案二:
使用策略模式解决
策略模式
定义:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化
策略模式体现的设计原则:
- 把变化的代码从不变的代码中分离出来
- 针对接口编程而不是具体类(定义了策略接口)
- 多用组合/聚合,少用继承(客户通过组合方式使用策略)
UML图:
从图中可以看出context有成员变量strategey或者其他的策略接口,至于需要使用到哪个策略,我们在构造器中指定。
策略模式是对算法的包装,是把使用算法的责任和算法本身分开。策略模式通常是把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。
策略模式涉及到三个角色:
- 环境角色(使用者Context):持有一个策略Strategy的引用
- 抽象策略角色:这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有具体策略类所需的接口
- 具体策略角色:实现了抽象策略角色,包装了相关算法或行为
策略模式解决鸭子问题:
UML示意图(图中叫的方法我换成了游泳):
如图:图中的左边的鸭子都是使用者,右边的行为都是提供方。
接着来看代码:
行为提供接口:
public interface Fly {
void fly();
}
public interface Swim {
void swim();
}
具体行为算法类:
public class CanFly implements Fly{
@Override
public void fly() {
System.out.println("我很会飞!!!");
}
}
public class CanNotFly implements Fly{
@Override
public void fly() {
System.out.println("我不会飞!!!");
}
}
public class CanSwim implements Swim{
@Override
public void swim() {
System.out.println("我很会游泳噢!!!");
}
}
public class CanNotSwim implements Swim{
@Override
public void swim() {
System.out.println("我不会游泳哎!!!");
}
}
使用方抽象类:
public abstract class Duck {
// 属性,策略接口
Fly fly;
Swim swim;
public Duck(){}
public abstract void display();
public void fly() {
if (null != fly) {
fly.fly();
}
}
}
使用方具体类:
public class KingDuck extends Duck{
// 构造器,传入Fly的对象
public KingDuck() {
fly = new CanNotFly();
}
@Override
public void display() {
System.out.println("我是一只鸭王!");
}
}
public class WildDuck extends Duck{
// 构造器,传入Fly的对象
public WildDuck() {
fly = new CanFly();
}
@Override
public void display() {
System.out.println("我是一只野鸭!");
}
}
测试:
public static void main(String[] args) {
Duck kingDuck = new KingDuck();
kingDuck.fly();
Duck wildDuck = new WildDuck();
wildDuck.fly();
//动态改变某个对象的行为,野鸭不能飞
wildDuck.setFly(new CanNotFly());
wildDuck.fly();
}
结果:
我不会飞!!!
我很会飞!!!
我不会飞!!!
注意事项和细节:
使用场景
策略模式的使用场景
购物系统
举一个实际例子吧。假如有一个购物系统,在用户付款的时候,会产生很多场景,根据用户的不同情况算出不同用户要付款的金额,这时候最直观的一种做法是:
在付款的里面写N多的if…else if…else,判断用户的场景,根据场景计算用户付款金额。
这种设计明显违反了开闭原则。开闭原则的"闭",指的是对修改关闭,但是这里假如算法又多了几种,那么必须再次修改这个付款的类。
这时候就可以使用策略模式。在付款的类里面持有一个付款接口的引用,每次根据不同场景传入一个具体的策略就好了。比如A类中要使用S0算法,就传入一个S0策略;B类中要使用S1算法,就传入一个S1算法。不需要把判断都放在付款的类中,代码的可读性、可维护性也更高了。付款这个类甚至可以直接生成一个.class文件放在一个jar包里面供调用。
使得代码更优雅、更易维护
假如你的代码中某处有一个打分系统,你为这个打分系统写了一段非常长的逻辑,某天,产品部门的同事找你,给我换一段打分逻辑,此时,有两种做法:
(1)把原有的打分逻辑删除,但这么做一个缺点是是看不到以前的打分算法了,另一个缺点是如果以后打分算法要换回来就找不到代码,虽然SVN和GIT这种版本管理工具都有历史提交记录的功能,但还是显得麻烦
(2)把原有的打分逻辑注释,但这么做的最大缺点是代码中有大量的注释,尤其在策略逻辑非常长的时候,这就导致了代码的可读性非常差
此时,就可以使用策略模式,将打分逻辑抽象为一种策略,换打分策略,新增一个策略的实现类,最后再让代码中传入新的策略实现类即可。
在Java中的应用
Comparator策略接口:
public static void main(String[] args) {
Integer[] data = {4,3,5,9,2};
/**
* 1.匿名内部类实现了Comparator接口(策略接口),匿名类对象即new Comparator<Integer>() {}
* 2.匿名类对象中的compare方法其实就是具体的策略方法,可以指定不同的处理方式
*/
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 > o2) {
return 1;
} else {
return -1;
}
}
};
// 第二个参数相当于持有一个策略的引用
Arrays.sort(data, comparator);
System.out.println(data);
}
Spring源码的Resource资源获取
当bean需要访问资源配置文件时,Spring有两种方式
- 代码中获取Rescource实例
- 依赖注入
第一种方式需要获取rescource资源的位置,代码中耦合性太高,而今我们一直使用注解,依赖注入的方式去获取。这样的话就无需修改程序,只改配置文件即可。
<beans>
<bean id="test" class="com.example.Test">
<!-- 注入资源 -->
<property name="tmp" value="classpath:book.xml"/>
</bean>
</beans>
在依赖注入的过程中,Spring会调用ApplicationContext 来获取Resource的实例。然而,Resource 接口封装了各种可能的资源类型,包括了:UrlResource,ClassPathResource,FileSystemResource等,Spring需要针对不同的资源采取不同的访问策略。在这里,Spring让ApplicationContext成为了资源访问策略的“决策者”。在资源访问策略的选择上,Spring采用了策略模式。当 Spring 应用需要进行资源访问时,它并不需要直接使用 Resource 实现类,而是调用 ApplicationContext 实例的 getResource() 方法来获得资源,ApplicationContext 将会负责选择 Resource 的实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。
ApplicationContext ctx = new Class PathXmlApplicationContext("bean.xml");
Resource res = ctx.getResource("book.xml");
上面的代码中,Spring 将采用和 ApplicationContext 相同的策略来访问资源。即: ApplicationContext 是 ClassPathXmlApplicationContext,则res 就是 ClassPathResource 实例。若将代码改为:
ApplicationContext ctx = new Class FileSystemXmlApplicationContext("bean.xml");
则再次调用ctx.getResource时,res 就是 ClassPathResource 实例。