设计模式之策略模式

概念

策略模式的思想是针对一组算法,将每一种算法都封装到具有共同接口的独立类中,从而使它们可以相互替换。

什么意思呢?比如现在要做一个高考成绩统计,有某第一中学跟某第二中学我们称为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带来的复杂维护。

缺点:策略类会增多,所有策略类都需要对外暴露。

注意事项

如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值