Java 设计模式之策略模式

2023-05-16

一、了解策略模式

1.1 什么是策略模式

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

1.2 策略模式组成结构

  • 环境 (Context):持有一个策略类的引用,最终给客户端调用。
  • 抽象策略 (Strategy): 策略类,通常是一个接口或者抽象类。
  • 具体策略 (ConcreteStrategy):实现了策略类中的策略方法,封装相关的算法和行为。

1.3 策略模式 UML 图解

这里写图片描述

1.4 策略模式应用场景

  • 多个类只区别在表现行为不同,可以使用 Strategy 模式,在运行时动态选择具体要执行的行为。
  • 需要在不同情况下使用不同的策略 (算法),或者策略还可能在未来用其它方式来实现。
  • 对客户隐藏具体策略 (算法) 的实现细节,彼此完全独立。

二、策略模式具体应用

2.1 问题描述

模拟鸭子游戏:游戏中会出现各种鸭子,一边游泳戏水、一边呱呱叫,为了提高游戏的乐趣,加入了让鸭子飞的功能。但是考虑到并不是所有的鸭子都会飞,比如像小孩子游泳时玩的橡皮鸭。现在让你利用 OO 技术,设计鸭子相关的类。

2.2 使用继承

这里写图片描述


我们可能想到使用继承,在超类 Duck 中定义鸭子的相关方法,并实现其对应的动作,这样就能让所有鸭子都可以对应其 fly() 的动作。在定义橡皮鸭时,只需要覆盖其父类 (Duck) 中的 fly() 方法即可。

如果我们还想加入诱饵鸭,这种鸭子既不会叫,也不会飞,那么我们就要继承 Duck 类,重写其中的 quack() 、display() 和 fly() 方法。

这种通过继承的方法是可以解决问题,但是有很多的局限

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

2.3 使用接口

这里写图片描述

认识到上面继承的不足,我们可能想到了另一种方式去解决这种问题,通过接口的方式去实现某些动作。把 fly() 和 quack() 方法从 Duck 类中抽取抽取出来,分别放在 Flyable 和 Quackable 接口中。

通过接口的方式是可以完成任务,但是这也确实是一个很笨的方式。因为对于很多种鸭子来说,它们大部分都会飞与呱呱叫,但是我们在定义它们类的时候都要去实现 Flyable 和 Quackable 接口,这样一来重复的代码更多了。

2.4 问题归零

到这里我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类中是不断变化的,并且让所有的鸭子都具有这些行为是不恰当的,比如橡皮鸭不具有飞的行为。通过接口的方式似乎还不错,但是 Java 接口并不具备实现代码,所以继承接口并不能达到代码复用的目的,一不小心,就可能造成新的错误!

幸运的是,有一个设计原则,恰好适用于这种状况:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这样以来,代码变化引起的后果变少,系统将更有弹性。

2.5 策略模式登场

除了 fly() 和 quack() 方法之外,Duck 中的其他方法还算一切正常,没有什么需要经常需要变化或修改的地方。所以除了 fly() 和 quack() 方法,我们不打算对 Duck 中的其他方法做太多处理。我们希望一切具有弹性,正是因为没有弹性,上面两种方法都被我们淘汰掉了。

比如说,我们要产生一个绿头鸭的实例,并制定特定“类型”的飞行行为给它。我们可以在鸭子类中包含设定行为的方法,这样就可以在“运行时”动态地“改变”绿头鸭的飞行行为。

有了这些实现目标,于是就有了第二个设计原则:针对接口编程,而不是针对实现编程。

这里我们使用接口代表每个行为,比如说,FlyBehavior 与 QuackBehavior,而行为的每个实现都将实现其中一个接口。所以这次鸭子类不会去实现 Flyable 和 Quackable 接口,反而是由我们制造一组其他类专门实现 FlyBehavior 与 QuackBehavior,这就称为“行为”类。由行为类而不是 Duck 类来实现该接口。

(1)策略模式设计图

这里写图片描述

改造原来的鸭子类

这里写图片描述

(2) 代码实现

这里我们将 Duck 类定义成抽象类,并把 display() 方法定义成抽象方法。

接口 QuackBehavior

package com.jas.strategy;

public interface QuackBehavior {
    void quack();
}

接口 QuackBehavior 实现类 Quack(实现鸭子呱呱叫)

​
package com.jas.strategy;

public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Quack!");
    }
}

​

接口 QuackBehavior 实现类 SQuack(实现鸭子橡皮吱吱叫)

​package com.jas.strategy;

public class SQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("SQuack!");
    }
}

接口 QuackBehavior 实现类 MuteQuack(实现鸭子不会叫)

​
​
​package com.jas.strategy;

public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Silence!");
    }
}

接口 FlyBehavior

package com.jas.strategy;

public interface FlyBehavior {
    void fly();
}

接口 FlyBehavior 实现类 FlyWithWings(实现鸭子飞)

​
package com.jas.strategy;

public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I'm flying!");
    }
}

接口 FlyBehavior 实现类 FlyNoWay(实现鸭子不会飞)

package com.jas.strategy;

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I can't fly!");
    }
}

Duck 类

​
package com.jas.strategy;

public abstract class Duck {
    private QuackBehavior quackBehavior;
    private FlyBehavior flyBehavior;
    
    public void swim(){
        System.out.println("All ducks float.");
    }
    
    public abstract void display();
    
    public void performQuack(){
        quackBehavior.quack();
    }

    public void performFly(){
        flyBehavior.fly();
    }
    
    public void setQuackBehavior(QuackBehavior quackBehavior){
        this.quackBehavior = quackBehavior;
    }
    
    public void setFlyBehavior(FlyBehavior flyBehavior){
        this.flyBehavior = flyBehavior;
    }
    
}

​

测试类 RubberDuck

package com.jas.strategy;

public class RubberDuck extends Duck {

    @Override
    public void display() {
        System.out.println("Rubber Duck");
    }
    
    public static void main(String[] args) {
        
        Duck rubberDuck = new RubberDuck();    //橡皮鸭实例
        
        rubberDuck.setQuackBehavior(new SQuack());    //橡皮鸭吱吱叫
        rubberDuck.setFlyBehavior(new FlyNoWay());    //橡皮鸭不会飞
        
        rubberDuck.performQuack();
        rubberDuck.performFly();
    }
}

//输出
//SQuack!
//I can't fly!

2.6 从策略模式组成结构对问题进行总结

这里写图片描述

三、策略模式总结

3.1 策略模式的优缺点

优点

  • 策略模式提供了管理相关的算法族的办法,从而避免重复的代码。
  • 策略模式提供了可以替换继承关系的办法。因为继承使得动态改变算法或行为变得不可能。
  • 使用策略模式可以避免使用多重条件转移语句。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 设计模式之策略模式 的相关文章

随机推荐

  • mac编译android源码-创建磁盘映像

    因为mac默认的磁盘环境是不区分大小的 xff0c 而git并不支持此类文件系统 xff0c 所以我们需要创建我们所需要的磁盘映像用来存放下载的源码 首先你需要找在mac上的磁盘工具 xff0c 一般是在应用程序列表 其他文件夹里面 2 如
  • android源码编译-如何在Mac中卸载openjdk15

    说明 之前在mac上使用intellij idea时 xff0c 由于没有在Mac上安装过jdk xff0c 所以就在intellij idea中下载了openjdk15版本 后来觉得想要换一个旧点的版本 xff0c 就想卸载了openjd
  • Mac OS查看和设置JAVA_HOME

    下载java https www java com zh CN download 1 查看JAVA版本 打开Mac电脑 xff0c 查看JAVA版本 xff0c 打开终端Terminal xff0c 通过命令行查看笔者的java版本 xff
  • Android源码编译–jdk版本查询

    2 1 Android源码所需JDK版本 根参考资料 1 的说明 xff0c 在android src build core main mk中对jdk的版本进行查询 xff0c 以确定当前系统是否安装了特定版本的jdk xff0c 因此可以
  • android源码编译 ninja: build stopped: subcommand failed.

    接着编译 make j8 线程加多少个具体看机器配置 xff0c 问题也最可能是这一步骤引起的 xff0c 如果是虚拟机的话 xff0c 建议不要加线程 xff0c 直接使用make执行
  • Ubuntu环境下完美安装python模块numpy,scipy,matplotlib

    不同的ubuntu版本安装过这三个模块几次了 xff0c 然而总是出现各种问题 xff0c 最近一次是在ubuntu 16 04 LTS server版本安装的 xff0c 总的来说安装的比较顺利 先把pip安装好 sudo apt get
  • prebuilts/misc/darwin-x86/bison/bison: Bad CPU type in executable

    方案一 cd external bison touch patch high sierra patch vim patch high sierra patch With format string strictness High Sierr
  • android源码编译 坑

    bash lunch command not found 先调用 build envsetup sh 再执行 lunch Can not find SDK Can not find SDK 10 6 at Developer SDKs Ma
  • 获取当前MacOSX SDK

    xcrun show sdk path 打印出 Library Developer CommandLineTools SDKs MacOSX sdk xcrun show sdk version 打印出 10 15 4 xcode sele
  • Mac OS10.12 编译Android源码8.1

    内容 介绍mac os10 12拉取android源码 xff0c 并且编译后 xff0c 刷入手机的过程 下载的rom是android 8 1 xff0c 手机是pixel 准备工作 硬盘大小 本人Mac磁盘空间只有256GB xff0c
  • android源码 xcode版本,【Android】AOSP源码下载及编译 for mac

    本文记录了AOSP在Mac系统上下载和编译的过程 采用的系统是 macOS 10 13 1 所使用的AOSP分支是 android 8 1 0 r7 系统预留空间 大于200G 一 环境配置 环境配置 xff0c 官网给出了非常全的教程 x
  • (Android 9.0)Activity启动流程源码分析

    前言 熟悉Activity的启动流程和运行原理是一个合格的应用开发人员所应该具备的基本素质 xff0c 其重要程度就不多做描述了 同时 xff0c 知识栈应该不断的更新 xff0c 最新发布的Android 9 0版本相较于之前的几个版本也
  • Lifecycle 源码详解

    Lifecycle 是 Jetpack 整个家族体系内最为基础的内容之一 xff0c 正是因为有了 Lifecycle 的存在 xff0c 使得如今开发者搭建依赖于生命周期变化的业务逻辑变得简单高效了许多 xff0c 使得我们可以用一种统一
  • git常用命令

    1 拉取远程所有分支 git clone xxx git branch r grep v 39 gt 39 while read remote do git branch track 34 remote origin 34 34 remot
  • Android应用启动流程分析

    1 前言 网上看过很多Activity启动过程的源码解析 xff0c 很多文章会贴上一大段代码 xff0c 然后从startActivity 函数开始深究整个源码的调用栈 个人感觉这类文章代码细节太多 xff0c 反而容易迷失在源码调用之中
  • 从一个分支cherry-pick多个commit到其他分支

    在branch1开发 xff0c 进行多个提交 xff0c 这是切换到branch2 xff0c 想把之前branch1分支提交的commit都 复制 过来 xff0c 怎么办 xff1f 单个commit只需要git cherry pic
  • IntWritable详解

    1 Hadoop数据类型如下图 xff1a 由上图的Writable层次结构图可以看到绝大多数的数据类型都实现了Writable WritableComparable接口 xff0c 在此先分析一下这两个接口情况 自顶下下逐步分析 Writ
  • 线程池源码剖析

    线程池 xff08 英语 xff1a thread pool xff09 xff1a 一种线程使用模式 线程过多会带来调度开销 xff0c 进而影响缓存局部性和整体性能 而线程池维护着多个线程 xff0c 等待着监督管理者分配可并发执行的任
  • Java 设计模式之装饰者模式

    一 了解装饰者模式 1 1 什么是装饰者模式 装饰者模式指的是在不必改变原类文件和使用继承的情况下 xff0c 动态地扩展一个对象的功能 它是通过创建一个包装对象 xff0c 也就是装饰者来包裹真实的对象 所以装饰者可以动态地将责任附加到对
  • Java 设计模式之策略模式

    一 了解策略模式 1 1 什么是策略模式 策略模式 Strategy Pattern 是指对一系列的算法定义 xff0c 并将每一个算法封装起来 xff0c 而且使它们还可以相互替换 此模式让算法的变化独立于使用算法的客户 1 2 策略模式