设计模式(七)-- 策略模式

举例:在这里插入图片描述

方案一:

写一个鸭子抽象类,里面有鸭子的一些行为方法,还有鸭子的描述。然后各种不同鸭子实现这个接口。
在这里插入图片描述
问题:
我们在鸭子抽象类中定义了鸭子飞行和游泳方法,但是北京烤鸭或者玩具鸭并不能飞或者游泳,这时候我们就要重写这些方法

方案二:
使用策略模式解决

策略模式

定义:

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化

策略模式体现的设计原则:

  1. 把变化的代码从不变的代码中分离出来
  2. 针对接口编程而不是具体类(定义了策略接口)
  3. 多用组合/聚合,少用继承(客户通过组合方式使用策略)

UML图:
在这里插入图片描述
从图中可以看出context有成员变量strategey或者其他的策略接口,至于需要使用到哪个策略,我们在构造器中指定。

策略模式是对算法的包装,是把使用算法的责任和算法本身分开。策略模式通常是把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。

策略模式涉及到三个角色:

  1. 环境角色(使用者Context):持有一个策略Strategy的引用
  2. 抽象策略角色:这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有具体策略类所需的接口
  3. 具体策略角色:实现了抽象策略角色,包装了相关算法或行为
策略模式解决鸭子问题:

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有两种方式

  1. 代码中获取Rescource实例
  2. 依赖注入

第一种方式需要获取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 实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值