设计模式——2_8 策略(Strategy)

文章目录

  • 定义
  • 图纸
  • 一个例子:如何切换坦克的攻击方式
          • GameElement(游戏元素)
          • TankFactory(坦克工厂)
          • Tank(坦克)
    • 医疗车和飞行车
    • 策略模式
          • Behavior(行为)
          • Tank
          • TankFactory
  • 碎碎念
    • 策略和状态
    • 为什么我们需要策略模式,是继承不好用吗?
      • 那我们为什么要用组合呢?

定义

定义一系列算法,把他们一个个封装起来,并且使他们可以互相替换。本模式使得算法可独立于使用他的客户而变化




图纸

在这里插入图片描述




一个例子:如何切换坦克的攻击方式

假定我们现在要设计一个RTS(即时战略)游戏,这个游戏中玩家主要通过战车工厂生产坦克来实现对对手进行攻击以取得胜利,为此我们必须创建属于坦克的类,就像这样:

在这里插入图片描述

GameElement(游戏元素)
/**
 * 游戏元素
 */
public class GameElement {

    /**
     * 所属者
     * 一般来说这个应该是玩家,这里简化用String代替
     */
    private String owner;

    /**
     * 生命值
     */
    private int HP;

    /**
     * 单位类型
     */
    private String type;

    public GameElement(String type, String owner, int HP) {
        this.type = type;
        this.owner = owner;
        this.HP = HP;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    public int getHP() {
        return HP;
    }

    public void setHP(int HP) {
        this.HP = HP;
    }
}

TankFactory(坦克工厂)
/**
 * 战车工厂
 */
public class TankFactory extends GameElement {

    public TankFactory(String owner) {
        super("战车工厂", owner, 20);
    }

    /**
     * 制造一辆坦克
     */
    public Tank makeTank() {
        return new Tank(getOwner());
    }
}
Tank(坦克)
/**
 * 坦克类
 */
public class Tank extends GameElement {

    public Tank(String owner) {
        super("坦克", owner, 10);
    }

    public void attack(GameElement e) {
        if(e.getOwner().equals(getOwner())){
            System.out.println("无法攻击友军");
        }else {
            e.setHP(e.getHP() - 5);
        }
    }

    public void move() {
        System.out.println("坦克进行了移动");
    }
}

首先,我们将所有游戏内玩家可以操控的元素通过一个根类体现出来,也就是 GameElement(游戏元素)。所有的游戏元素都必然会有自己所属的 owner(玩家)HP(生命值)type(类型)

接着,我们给 Tank(坦克) 赋予了 attack(攻击)move(移动) 两种能力,每个 Tank 在攻击的时候都会先判断被攻击者是否是友军,如果不是的话,降低被攻击方的生命值

最后,所有的坦克都是从 TankFactory(战车工厂) 中被生产出来的,而且 TankFactory 自身也是一个 GameElement 所以有自己的 所属玩家生命值



医疗车和飞行车

为了增加游戏的可玩性,我们添加了两种不同的坦克,功能是这样的:

  1. 医疗车:可以攻击己方单位,攻击时给对方回血
  2. 飞行车:移动时既可以在地面跑,也可以在空中飞行

加上原有的坦克,我们的游戏中出现了三种类型的坦克,他们的本质都是坦克,都应该拥有 moveattack 方法,只是不同类型的坦克需要对对应的方法进行重写。于是第一种解决方案跃然纸上,我必须立刻使用继承!就像这样:

在这里插入图片描述

我们通过继承实现了各种坦克的行为,这是没问题的,可是在编码的时候却很别扭。为了迁就 FlyTank 的飞行能力,我们给所有坦克类上都添加了 fly 方法。但事实上,这个方法除了对 FlyTank 有意义之外毫无作用


那你会说了,那我直接把 Tank 根类做成一个抽象类,默认实现一个空的 fly 方法不就行了吗?

这个办法可以,只不过治标不治本,将来策划突发奇想,整一个会飞的医疗车咋办?那我就只能再建一个 FlyCureTank

到这里,我们陷入了泥潭,我们创建新的子类不再是因为存在不同类型的内容,而是在不断的把原有内容进行排列组合,每有一种新组合,我们就必须成倍的增长子类(这取决于有多少种攻击方式和多少种移动方式),这显然违背了我们的初衷

而且将来不熟悉程序的人接手我们的代码时,他一定会奇怪,为什么在 Tank 类里面,会有一个空的 fly 方法,他真的有存在的意义吗?


我们不禁要思考,有没有办法可以直接把 movefly 内的算法直接提取出来,复用他?

答案当然是肯定的,要不然为啥要讲策略模式



策略模式

就像这样:

在这里插入图片描述

Behavior(行为)
/**
 * 攻击行为
 */
public abstract class AttackBehavior {

    /**
     * 使用策略的元素
     */
    private GameElement user;

    public GameElement getUser() {
        return user;
    }

    public void setUser(GameElement user) {
        this.user = user;
    }

    public abstract void attack(GameElement e);
}

/**
 * 普通攻击
 */
public class BasicAttack extends AttackBehavior {

    @Override
    public void attack(GameElement e) {
        if (e.getOwner().equals(getUser().getOwner())) {
            System.out.println("无法攻击友军");
        } else {
            e.setHP(e.getHP() - 5);
        }
    }
}

/**
 * 医疗攻击
 */
public class CureAttack extends AttackBehavior{

    @Override
    public void attack(GameElement e) {
        if(e.getOwner().equals(getUser().getOwner())){
            e.setHP(e.getHP() + 3);//恢复三点生命值
        }else {
            System.out.println("无法攻击敌军");
        }
    }
}


/**
 * 飞行策略
 */
public abstract class FlyBehavior {

    /**
     * 使用策略的元素
     */
    private GameElement user;

    public GameElement getUser() {
        return user;
    }

    public void setUser(GameElement user) {
        this.user = user;
    }

    public abstract void fly();
}

public class NoFly extends FlyBehavior{

    @Override
    public void fly() {
        //不会飞
    }
}

/**
 * 螺旋桨飞行
 */
public class PropellerFly extends FlyBehavior{

    @Override
    public void fly() {
        System.out.println("采用螺旋桨飞行");
    }
}
Tank
/**
 * 坦克类
 */
public class Tank extends GameElement {

    /**
     * 攻击策略
     */
    private AttackBehavior attackBehavior;
    /**
     * 飞行策略
     */
    private FlyBehavior flyBehavior;

    public Tank(String owner) {
        super("坦克", owner, 10);

        //默认使用普通攻击
        setAttackBehavior(new BasicAttack());
        //默认无法飞行
        setFlyBehavior(new NoFly());
    }

    public void attack(GameElement e) {
        attackBehavior.attack(e);//调用攻击策略的攻击方法
    }

    public void move() {
        System.out.println("坦克进行了移动");
    }

    /**
     * 飞行
     */
    public void fly() {
        flyBehavior.fly();//调用飞行策略的飞行方式
    }

    public void setAttackBehavior(AttackBehavior attackBehavior) {
        this.attackBehavior = attackBehavior;
        attackBehavior.setUser(this);
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
        flyBehavior.setUser(this);
    }
}
TankFactory
/**
 * 战车工厂
 */
public class TankFactory extends GameElement {

    public TankFactory(String owner) {
        super("战车工厂", owner, 20);
    }

    /**
     * 制造一辆基础坦克
     */
    public Tank makeBasicTank() {
        return new Tank(getOwner());
    }

    /**
     * 制造一辆医疗车
     */
    public Tank makeCureTank() {
        Tank tank = makeBasicTank();
        tank.setAttackBehavior(new CureAttack());
        return tank;
    }

    /**
     * 制造一辆用螺旋桨飞行的坦克
     */
    public Tank makePropellerTank() {
        Tank tank = makeBasicTank();
        tank.setFlyBehavior(new PropellerFly());
        return tank;
    }
}

我们去掉了 Tank 所有因为不同的行为方式而出现的子类

那这些行为方式去哪了呢?他们去了 Behavior(行为) 类簇中

我们把攻击和飞行这两种行为,独立到两个不同的算法类簇中,这样一来不同的攻击方式和飞行方式之间可以随意的组合。而在这个过程中,我们不需要动 Tank 的类簇,这跟他没关系。

请注意 TankFactory 在这个实现中做的事情,他没有根据不同类型的坦克产出不同的子类,而是给他们装上对应的算法类对象,以实现不同坦克的体现


这种写法让你不单在创建 Tank 对象的时候可以随意组合行为模式,他甚至在游戏进行的过程中都可以改变一辆坦克的行为模式,只要改变对应 Tank 对象的对应行为类对象就可以实现了,这是继承无法触及的领域

而这正是一个标准的策略模式实现




碎碎念

策略和状态

策略模式和状态模式就像是本体和镜像之间的关系

他们都是实现算法和算法调用者之间的解耦,而且实现这种解耦的方式都是通过把 不同的算法 封装成 算法类簇 实现的


那他们有区别吗?

  • 策略模式通过从外部直接切换对象即将要执行的算法来改变对象的行为
  • 状态模式通过改变内部的状态来实现算法对象的切换以改变对象的行为

这就是他们本质的区别

策略模式一定是外界 直接 驱动的,他把组装的“权限“交给了外部。所以对上级代码来说,当前这个对象将要执行哪个算法对象是显而易见的

状态模式不是这样的,一个对象内部状态的改变未必都是外部驱动的,他也可以是内部执行某些动作后触发的变化。而且即使外部去驱动这种变化,上级代码看到的也是自己改变状态的动作,而不是直接和算法对象之间的关联。也就是说,上级代码对执行算法的对象内部到底发生什么事情感知不强,甚至有的时候完全无感,毕竟他只是推倒了多米诺骨牌的第一张


这样的区别直接导致了状态模式的可拓展性终究比不上策略模式。在新的算法簇加入的情况下,策略模式可以通过新增一个算法类的方式直接把新的算法加入到程序中;而对状态模式来说,你只能去修改已被封装的代码

那你会说,不对啊,为什么我用状态模式要新增算法类的时候不可以对应的新增一个算法调用类的子类的?

从技术上来说是没问题的,而且对应的例子在前面的 状态模式 一文里面的转笔刀里已经演示过了。可是你有没有想过如果算法越来越多,是不是意味着每一种新算法都要对应一种新的算法调用类。这样一来,算法类簇和算法调用类簇之间甚至有可能形成 平行类层次(这个概念在前面的 工厂方法 一文已经聊过,这里不再赘述)



为什么我们需要策略模式,是继承不好用吗?

如果把设计模式比喻成一部小说,那么策略模式就是在我心目中当之无愧的主人公

几乎所有的 OO 设计模式出现的初衷,都是为了把改变的部分和不变的部分解耦,以实现代码的复用和动态组合


那你会说了,不对啊,继承不是也实现这个效果吗?

我们把多个类所共有的共有部分抽象出来,并通过继承的形式在子类中实现个性化,这难道不是继承诞生的初衷吗?

假如我们再往前推一步,就会发现其实策略模式所能实现的所有效果,我们用继承也都可以实现:在上例中,我们完全可以把多功能车设置成一个超类,然后把攻击车、医疗车和防空车作为多功能车的子类

但是策略模式放弃了继承,而是采用把部分算法“委托”出去的方式来组合。这不仅是给我们提供了一种解题思路,而是给我们提供了一种思考方式,尝试把继承转移到组合中

那我们为什么要用组合呢?

因为 有一个是一个 有用得多

【是一个】是系统设计者预设的 模具,在实现效果的时候,你只能去找合适的模具;如果没有,你就得再刻一个

【有一个】是在 搭积木。框架的设计者创造的是积木块,是写字时用的点竖横折钩;没有人规定积木要如何组合在一起,就像没有人规定笔画要如何组合一样(当然乱组合的话别人不认识这个字,但别人不认不意味着你不能这样写)。而处理具体业务的程序员的任务则是把组件组装起来以产出更大的组件或成品

高下立判,【有一个】的方式为系统带来了巨大的弹性,他不仅实现了模块颗粒度的细化,还实现这些模块之间在执行期间的动态组合


当然,即便如此,抽象、继承、多态这些概念也没有什么问题,还是那句话,知识怎么可能会有问题,问题一定是出在使用他的人身上。这些概念不会让你立刻就能设计出优秀的系统,她们是锛凿斧锯,可是设计师关注的不是如何切割这块木材,而是如何搭建一个具有弹性的系统,一如搭建一间可以屹立百年的高楼





万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/527801.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[数据结构]双向带头循环链表制作

前面我们有提到,单向不带头循环链表的制作 这里我们介绍一个双向带头循环链表的制作方法 双向带头循环链表的示意图如下 带头指针的作用体现在哪呢? 第一、防止头节点为空,既有头结点,头指针始终指向头结点,那么无论链表是否为空&#xf…

游戏公司面试题系列-CocosCreator实现虚拟摇杆控制角色移动中心旋转自转小球割草旋转逻辑

游戏公司面试题系列-CocosCreator实现虚拟摇杆控制角色移动&中心旋转自转小球&割草旋转逻辑<&#xff01;&#xff01;&#xff01;文章末尾有完整代码下载链接地址&#xff01;&#xff01;&#xff01;> Hello大家好&#xff01;今天我们来用最新的CocosCreat…

pringboot2集成swagger2出现guava的FluentIterable方法不存在

错误信息 Description: An attempt was made to call a method that does not exist. The attempt was made from the following location: springfox.documentation.spring.web.scanners.ApiListingScanner.scan(ApiListingScanner.java:117) The following method did not ex…

PHP运算符与流程控制

华子目录 运算符赋值运算符算术运算符比较运算符逻辑运算符连接运算符错误抑制符三目运算符自操作运算符 计算机码位运算符 运算符优先级流程控制控制分类顺序结构分支结构if分支switch分支 循环结构for循环while循环continuebreak 运算符 运算符&#xff1a;operator&#xf…

谷歌留痕霸屏要怎么做?

谷歌留痕霸屏&#xff0c;就是让你的网站或者页面在谷歌搜索结果里尽可能多地出现&#xff0c;就像是在你的潜在客户眼前留下深刻印象一样&#xff0c;你要做的就是在一些高权重平台发布有价值的信息&#xff0c;同时巧妙地留下你的品牌名、产品名或者任何你想要推广的关键词&a…

谷歌不收录怎么办?

谷歌不收录首先你要确认自己网站有没有出问题&#xff0c;比如你的网站是否已经公开&#xff0c;rboot是否允许搜索引擎进来&#xff0c;网站架构有没有问题&#xff0c;面包屑的结构是否有问题&#xff0c;确保你的网站没问题 接下来就是优化这个过程&#xff0c;有内容&#…

python|drop的应用

drop 删除列B 删除索引为1的行 删除列为‘A’&#xff0c;‘C’的列&#xff0c;axis表示方向 删除时保留原始 DataFrame&#xff08;使用 inplaceFalse&#xff09; 删除时直接修改原始 DataFrame&#xff08;使用 inplaceTrue&#xff09;

SEO优化艺术:精细化技巧揭示与搜索引擎推广全面战略解读

SEO&#xff08;搜索引擎优化&#xff0c;Search Engine Optimization&#xff09;是一种网络营销策略&#xff0c;旨在通过改进网站内外的各项元素&#xff0c;提升网站在搜索引擎自然搜索结果中的排名&#xff0c;从而吸引更多目标用户访问网站&#xff0c;增加流量&#xff…

如何快速开启一个项目-ApiHug - API design Copilot

ApiHug101-001开启篇 &#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin |…

少儿编程 2024年3月电子学会图形化编程等级考试Scratch二级真题解析(判断题)

2024年3月scratch编程等级考试二级真题 判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 26、下列积木块运行结果为false 答案&#xff1a;错 考点分析&#xff1a;考查积木综合使用&#xff0c;重点考查逻辑或积木的使用&#xff0c;或运算是只…

用Echarts词云数据可视化热词表白​​

目录 1、使用前准备 2、准备工作 3、盒子搭建 4、整体展现 1、使用前准备 找到表白对象&#xff08;重中之重&#xff01;&#xff09;&#xff0c;不要一见钟情&#xff08;个人觉得&#xff1a;一见钟情属于见色起意&#xff01;&#xff09;&#xff0c;因为数据可视化需…

中颖51芯片学习3. 定时器

中颖51芯片学习3. 定时器 一、SH79F9476定时器简介1. 简介2. 定时器运行模式 二、定时器21. 说明&#xff08;1&#xff09;时钟&#xff08;2&#xff09;工作模式 2. 寄存器&#xff08;1&#xff09;控制寄存器 T2CON&#xff08;2&#xff09;定时器2模式控制寄存器 T2MOD …

sql注入方式之联合注入

1.1 靶场环境 系统centos7 IP地址192.168.1.24 1.2 联合注入原理 联合查询注入是联合两个表进行注入攻击&#xff0c;使用关键词 union select 对两个表进行联合查询。两个表的字段要数要相同&#xff0c;不然会出现报错。 1.3 找注入点 找注入点&#xff0c;当输入id1 an…

你知道哪几种当前流行的lisp语言的方言?

估计很多人都看过《黑客与画家》这本书&#xff0c;这本书主要介绍黑客即优秀程序员的爱好和动机&#xff0c;讨论黑客成长、黑客对世界的贡献以及编程语言和黑客工作方法等所有对计算机时代感兴趣的人的一些话题。作者保罗格雷厄姆字里行间不经意间向大家推介Lisp是最好的编程…

【linux】set ff=unix、linux设置文件格式

文章目录 一、文件格式二、如何查看文件格式三、设置文件格式、set ffunix四、查看unix与dos的区别 一、文件格式 当我们打开sh脚本时发现有时候格式是unix(LF) ,有时候是windows(CR LF) 。如下图&#xff1a; 文件格式影响了文件中的换行符 linux中sh类型的文件一般要设置为…

[dvwa] CSRF

CSRF 0x01 low 跨站&#xff0c;输入密码和确认密码直接写在url中&#xff0c;将连接分享给目标&#xff0c;点击后修改密码 社工方式让目标点击短链接 伪造404页&#xff0c;在图片中写路径为payload&#xff0c;目标载入网页自动请求构造链接&#xff0c;目标被攻击 http…

Python | Leetcode Python题解之第18题四数之和

题目&#xff1a; 题解&#xff1a; class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:quadruplets list()if not nums or len(nums) < 4:return quadrupletsnums.sort()length len(nums)for i in range(length - 3):if i > 0 …

222222222222222222222222

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

DevOps已死?2024年的DevOps将如何发展

随着我们进入2024年&#xff0c;DevOps也发生了变化。新兴的技术、变化的需求和发展的方法正在重新定义有效实施DevOps实践。 IDC预测显示&#xff0c;未来五年&#xff0c;支持DevOps实践的产品市场继续保持健康且快速增长&#xff0c;2022年-2027年的复合年增长率&#xff0…

标定系列——Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译(二十)

Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译 说明下载安装步骤1.更新2.安装必要的依赖包3.下载源码包并解压4.终端运行如下命令5.添加配置路径6.验证安装是否成功 说明 Ubuntu18.04下对opencv-4.5.3与opencv_contrib-4.5.3源码编译 下载 CSDN下载 安装步骤 …