三、策略模式

以经典的鸭子项目为例:

  1. 各种鸭子,比如北京鸭,野鸭,水鸭,玩具鸭等;
  2. 鸭子有各种行为,如游泳,飞行,叫等;
  3. 能够鸭子的行为信息。

1 用继承实现该需求

在这里插入图片描述

  1. 所有的鸭子都会呱呱叫也会游泳,所以由超类负责这部分的代码实现;
  2. 因为每种鸭子的外观不同,所以display()的方法时抽象的;
  3. 每种鸭子负责实现自己的display()显示自己的外观;

此时,如果需要添加一个飞行的功能,要是在超类Duck中添加加上fly()方法,然后所有子类就会继承父类。这样看似没问题,但是不同的鸭子飞行的方式不同;MallardDuck可能飞的高,橡皮鸭不会飞,这样每个子类就要重写fly()方法;每新增方法就要根据特性修改子类代码实现。

缺点

  • 代码在多个子类中重复;
  • 很难知道所有鸭子的全部行为;
  • 运行时的行为不容易改变;
  • 改变会牵一发而动全身,造成其他鸭子不想要的改变。

2 利用接口实现

显然使用继承的方式并不理想,每当有新的鸭子类出现或者有新的行为出现,就要重写所有的方法或者修改所有的子类。可以把fly()方法抽取出来,放入一个Flyable接口中。这样,只有会飞的鸭子才实现此接口。同样可以设计一个Quackable接口,因为不是所有鸭子都会叫。(将变化的部分抽取出来,与不变的保持距离
在这里插入图片描述
这样写之后,每个实现接口的类都要实现接口的fly()或者quack()方法,会造成代码无法复用;无论何时要修改某个行为,必须往下追踪并在每一个定义此行为的类中修改它。

3 分开变化和不会变的部分

开发上不管软件当初设计的多么完美,一段时间之后,总要作出改变;否则这个软件就死亡了。

把会变化的部分抽取出并封装起来,不要和那些不需要变化的代码放在一起这是一个重要的设计原则;换句话说如果每次需求出来,都会使某方面的代码发生变化,那么就可以确定,这部分代码需要抽取出来,和其他稳定的代码有所区分。这是每个设计模式都背后精神所在。

在这里插入图片描述
将鸭子的行将放在单独的类中,该类专门提供类某种行为的接口实现。

面向接口编程,不要针对实现编程。 利用接口代表某种行为,比如:FlyBehaviorQuackBehavior,让鸭子使用这些行为接口。而某个具体的行为去实现这些接口,而不是让鸭子本身去实现这些行为。
“面向接口编程”中的“接口”关键是多态,利用多态程序要针对超类编程,执行时会根据实际情况执行实际的行为;不会被绑死咋超类的实现上。更明确的可以说成:变量的声明要用超类型

这样的设计,可以让飞行和叫的行为被其他对象服用,因为这些行为已经和具体的鸭子无关了;
当增加一些新的行为,不会影响到现有的行为类,也不会影响到使用到飞行行为的鸭子类。

3.1 代码实现

在这里插入图片描述

  1. 以飞行行为为例,创建飞行接口,已经实现类
/**
 * @author user
 * @date 2020/6/11 16:35
 */
public interface FlyBehavior {
    void fly();
}

/**
 * @author user
 * @date 2020/6/11 16:44
 */
public class FlyFast implements FlyBehavior {
    public void fly() {
        System.out.printf("快速飞行");
    }
}

/**
 * @author user
 * @date 2020/6/11 16:45
 */
public class FlyNoway implements FlyBehavior {
    public void fly() {
        System.out.printf("不会飞");
    }
}
  1. 首先在Duck类中加入flyBehaviorquackBehavior两个变量和performQuack()performFly()两个方法:
package com.gu.strategy.duck03;

/**
 * @author user
 * @date 2020/6/11 16:31
 */
public abstract class Duck {

    protected FlyBehavior flyBehavior;

    protected QuackBehavior quackBehavior;

    /**
     * 鸭子不是自己去实现叫的行为,而是托管给quackBehavior引用的对象
     */
    public void performQuack(){
        quackBehavior.quack();
    }

    /**
     * 鸭子不是自己去实现飞行行为,而是托管给flyBehavior引用的对象
     */
    public void performFly(){
        flyBehavior.fly();
    }

    public void swim(){
        System.out.println("游泳");
    }

    public abstract void display();

  1. 给具体的鸭子添加行为;各种鸭子可以根据flyBehavior 的实际引用,表现出不同的飞行方法
package com.gu.strategy.duck03;

/**
 1. @author user
 2. @date 2020/6/11 17:34
 */
public class MallardDuck extends Duck{

    /**
     * MallardDuck会快速的飞行也会嘎嘎叫,当performFly()被调用时就会托管给FlyFast的fly()方法
     */
    public MallardDuck() {
        this.flyBehavior = new FlyFast();
        this.quackBehavior = new QuackGaga();

    }

    public void display() {
        System.out.println("是一只MallardDuck");
    }
}
  1. 测试
    在这里插入图片描述

这里也可以动态的改变鸭子的行为,只需要给flyBehaviorquackBehavior添加set()方法,就可以实现动态的改变行为。

整体的结构:来自《head first 设计模式》书中的截图:
在这里插入图片描述

这里也体现了一个设计原则:**多用组合,少用继承。**即:Has-A可能比Is-A更好。

4 策略模式定义

定义了算法族,分别封装起来,让他们直接可以相互替换,此模式让算法的变化独立于使用算法的客户。

5 在JDK中的使用

5.1 比较器Comparator

在Java的集合框架中,经常需要通过构造方法传入一个比较器Comparator,或者创建比较器传入Collections的静态方法中作为方法参数,进行比较排序等,使用的是策略模式。
在该比较架构中,Comparator就是一个抽象的策略;一个类实现该结构,并实现里面的compare方法,该类成为具体策略类;Collections类就是环境角色,他将集合的比较封装成静态方法对外提供api。

在这里插入图片描述

在这里插入图片描述

5.2 ThreadPoolExecutor中的四种拒绝策略

在创建线程池时,需要传入拒绝策略,当创建新线程使当前运行的线程数超过maximumPoolSize时,将会使用传入的拒绝策略进行处理。

AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值