概念
策略模式的思想是针对一组算法,将每一种算法都封装到具有共同接口的独立类中,从而使它们可以相互替换。
什么意思呢?比如现在要做一个高考成绩统计,有某第一中学跟某第二中学我们称为A,B两所中学,A中学要求统计时将本一线以上的做个保存,B中学要求统计时将本二线以上的做保存,基于这两所中学要实现的方法calculation()我们可以给它提取到共同的接口中,A跟B分别去实现这个接口,那么就实现了将共同接口封装到独立类中,假设现在两所中学又都要进行招收学生,那么招收这个方法就可以跟calculation()方法写在同一个接口中,A,B分别实现即可。
组成
1:抽象策略角色,通常使用接口或者抽象类去实现
2:具体策略角色,包装了具体的算法和行为,可以理解为实现类,实现了抽象策略角色的类
3:环境角色Context,内部会有一个抽象策略角色的引用,如果抽象角色有十个方法,根据客户端需要,只需要把客户端要调用的方法在Context定义,然后内部调用具体策略角色的算法和行为。
遵循的原则,能解决的问题
可以解决多重if...else...带来的复杂和难以维护问题
代码实现
1:创建一个接口
public interface Strategy {
public int cal(int num1, int num2);
}
2:创建加法实现类和减法实现类
public class AddStrategy implements Strategy{
@Override
public int cal(int num1, int num2) {
return num1 + num2;
}
}
public class SubStrategy implements Strategy{
@Override
public int cal(int num1, int num2) {
return num1 - num2;
}
}
3:创建 Context 类
public class Context {
private Strategy strategy;
public Context(){
}
public Context(Strategy strategy){
this.strategy = strategy;
}
public int execute(int num1, int num2){
return strategy.cal(num1, num2);
}
}
4:简单的测试类
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context();
context.SetStrategy(new AddStrategy());
System.out.println("10 + 5 = " + context.execute(10, 5));
context.SetStrategy(new SubStrategy());
System.out.println("10 - 5 = " + context.execute(10, 5));
}
}
5:其他实现方案
如果是以接口方式,可以先通过后台把数据配置到数据库或者缓存中;也可以通过枚举进行设定。
如(type=1则:beanId="com.*.*.AddStrategy")(type=2则:beanId="com.*.*.SubStrategy")
之后通过客户端传类型值type去获取,这样是不是就完美结合了呢
@RestController
public class OperateService {
@Autowired
private OperateMapper operateMapper;
@Autowired
private SpringUtils springUtils;
@RequestMapping("/cal")
public String toPayHtml(String type){
// 1.验证参数
if(StringUtils.isEmpty(type)){
return "操作类型不能为空!";
}
// 2.使用type查询type对应的实现类
OperateEntity entity = operateMapper.getOperateEntity(type);
if(entity ==null){
return "该渠道为空...";
}
// 3.获取策略执行的beanid
String beanId = entity.getBeanId();
// 4.使用beanId获取对应spring容器bean信息
Strategy strategy = springUtils.getBean(beanId, Strategy.class);
// 5.执行具体策略算法
return strategy.cal();
}
}
@Data
public class OperateEntity {
/** ID */
private Integer id;
/** 名称 */
private String operateName;
/** 渠道ID */
private String operateId;
/**
* 策略执行beanId
*/
private String beanId;
}
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
框架中哪里有使用到
1:TreeSet 源码中,使用Comparator作为入参的类型数据,入参只要实现了Comparator并重写compare()方法就可以使用自定义的比较器
public class TestComparator {
public static void main(String[] args) {
TreeSet ts = new TreeSet(new MyComparator());
ts.add(new MyPerson("java01",1));
ts.add(new MyPerson("java04",4));
ts.add(new MyPerson("java03",3));
ts.add(new MyPerson("java02",2));
for(Iterator it = ts.iterator(); it.hasNext();)
System.out.println(it.next());
}
}
class MyPerson{
private String name;
private int age;
public MyPerson(String name,int age){
this.name = name;
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;
}
@Override
public String toString() {
return "姓名:" + this.name+",年龄:" + this.age;
}
}
class MyComparator implements Comparator{
@Override
public int compare(Object obj1, Object obj2) {
if(!(obj1 instanceof MyPerson)||!(obj2 instanceof MyPerson))
throw new RuntimeException("1111");
MyPerson p1 = (MyPerson)obj1;
MyPerson p2 = (MyPerson)obj2;
if((p1.getAge()-p2.getAge())==0)
return p1.getName().compareTo(p2.getName());
else
return p1.getAge()-p2.getAge();
}
}
2:Spring 中在实例化对象的时候用到策略模式, 在 SimpleInstantiationStrategy 有使用。
采用实现部分、抽象部分的策略。Spring 中角色分工很明确,创建对象的时候先通过 ConstructorResolver 找到对应的实例化方法和参数,再通过实例化策略 InstantiationStrategy 进行实例化,根据创建对象的三个分支(工厂方法、有参构造方法、无参构造方法), InstantiationStrategy 提供了三个接口方法:
在 SimpleInstantiationStrategy 中对这三个方法做了简单实现,CglibSubclassingInstantiationStrategy则是继承了SimpleInstantiationStrategy 并且做了扩展,结构图如下。
重点看SimpleInstantiationStrategy中的instantiate方法
分析:如果工厂方法实例化的时候直接用反射创建对象,如果是构造方法实例化的则判断是否有 MethodOverrides,如果无 MethodOverrides 也是直接用反射,如果有 MethodOverrides 就需要用 Cglib 实例化对象,SimpleInstantiationStrategy 把通过 Cglib 实例化的任务交给了它的子类 CglibSubclassingInstantiationStrategy。
3:Spring的Resource设计是一种典型的策略模式
Spring 改进了 Java 资源访问的策略,为资源访问提供了一个 Resource 接口,该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。关系图如下
Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。那么spring是如何实现的呢?
Spring中有两个标志性的接口:
- ResourceLoader:该接口实现类的实例可以获得一个 Resource 实例。在 ResourceLoader 接口里有如下方法:Resource getResource(String location):该接口仅包含这个方法,该方法用于返回一个 Resource 实例。ApplicationContext 的实现类都实现 ResourceLoader 接口,因此 ApplicationContext 可用于直接获取 Resource 实例。
- ResourceLoaderAware:该接口实现类的实例将获得一个 ResourceLoader 的引用。
Spring在ApplicationContext中,继承了ResourcePatternResolver,而ResourcePatternResolver实现了ResourceLoader口,而ResourceLoader实现了ResourceLoader,因此在ApplicationContext可用于直接获取Resource实例,ApplicationContext在策略模式中充当Context角色,它能智能的选择策略实现。先看下下面的代码:
public class SpringStrategyDemo {
public static void main(String[] args) {
// 创建 ApplicationContext 实例
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
Resource res = ctx.getResource("bean.xml");
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
ApplicationContext ctx = new ClassPathApplicationContext("bean.xml");
}
}
分析:当ApplicationContext获取Resource实例时,默认采用与ApplicationContext相同的策略,例如使用FileSystemXmlApplicationContext时,resource就是使用FileSystemResource;ClassPathXmlApplicationContext则是ClassPathResource;XmlWebApplitaionContext则是ServlcetContextResource;
优缺点
优点:算法可以自由切换,可以解决多重if带来的复杂维护。
缺点:策略类会增多,所有策略类都需要对外暴露。
注意事项
如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。