查看原文
其他

通过王者荣耀来学习策略模式

張文靖同學 郭霖 2019-04-29



今日科技快讯


据报道,早在今年2月28日,电动汽车制造商特斯拉公司就宣布,开始接受备受期待的3.5万美元基础版Model 3(Standard Range版本)订单。当时,该公司估计,客户需要等待大约2至4周才能收到货。然而现在已经过去四周,特斯拉似乎依然没有发货,许多下订单的客户被告知,这些汽车的交付已被无限期推迟。


作者简介


本篇文章来自 張文靖同學 的投稿,和大家分享了自己对策略模式的理解,希望对大家有所帮助!

張文靖同學 博客地址:

https://www.jianshu.com/u/197319888337


前言


本文讲述了策略模式的基础以及应用的探索,为什么叫王者荣耀之「策略模式」呢,是因为在设计模式的基础上通过王者荣耀这款游戏实际举例,并且根据王者荣耀开始游戏的流程,通过策略模式进行优化改造。可以说扩展一下思维,在不同的业务上使用策略模式。

Java设计模式 王者荣耀之「建造者模式」是之前发布的一篇设计模式的文章。这回把它做成一个系列。

进入本文的主题:王者荣耀之「策略模式」


什么是策略模式


在软件开发中常常遇到这种情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如果将这些算法或者策略抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现类,这样在程序客户端就可以通过注入不同的实现对象来实现算法或者策略的动态替换,这种模式的可扩展性和可维护性也就更高,这就是策略模式

Strategy模式的定义

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

为什么用Strategy模式,他的优缺点

简化逻辑和结构的同时,增强了系统的可读性、稳定性、可扩展性,对于较为复杂的业务逻辑显得更为直观,扩展也更为方便
优点

  • 结构清晰明了、使用简单直观

  • 耦合度相对而言较低,扩展方便

  • 操作封装更为彻底,数据更为安全

缺点

  • 随着策略的增加,子类也会变的繁多。

传统策略模式的UML类图

角色介绍:

  1. Context 用来操作策略的上下文环境

  2. Strategy策略的抽象

  3. Concrete StrategyA Concrete StrategyB 具体策略的实现。

在android源码中使用的到的策略模式

在android源码中最常用到的策略模式模式就是使用动画中的差值器。插值器线性插值器、加速减速插值器、减速插值器等。在这里每种插值器内部都提供了具体的算法,但是使用的时候可以选择不同的插值器来使用。

附一些源码

public interface Interpolator extends TimeInterpolator {
}
public interface TimeInterpolator {
    float getInterpolation(float input);
}

abstract public class BaseInterpolator implements Interpolator {
    private @Config int mChangingConfiguration;
    /**
     * @hide
     */

    public @Config int getChangingConfiguration() {
        return mChangingConfiguration;
    }

    /**
     * @hide
     */

    void setChangingConfiguration(@Config int changingConfiguration) {
        mChangingConfiguration = changingConfiguration;
    }
}
//加速插值器
public class AccelerateInterpolator extends BaseInterpolator {
 //... 此处省略具体内容
}
//加速减速插值器
public class AccelerateDecelerateInterpolator extends BaseInterpolator{
 //... 此处省略具体内容
}


王者荣耀中的策略模式


说到王者荣耀玩过的都知道,在玩游戏之前有很多种玩法供选择。如果我们将从打开app到开始进行游戏的流程开发成一个程序的话流程大概是这样。打开app-->登陆-->选择对战模式(实战对抗、娱乐模式、人机对战、训练营、开房间)-->进入游戏。

为了逻辑清晰,供UML作为参考:

实现流程的第一代代码

在这里,选择对战模式时有很多种模式,可能写出来的代码会是这样:

Ps:以下代码简单实现了以下对战选择模式的流程不需要细看,不必浪费时间和精力,他只告诉你是一个臃肿不利于维护的代码

public class StartGame {
    //不同的玩法
    public static final int 实战对抗 = 101
    public static final int 娱乐模式 = 102
    public static final int 排位模式 = 103
    //不同的地图
    public static final int 王者峡谷 = 201
    public static final int 深渊大乱斗 = 202
    public static final int 长平攻防战 = 203;
    public static final int 墨家机关道 = 204;
    public static final int 无限乱斗 = 205;
    public static final int 克隆大作战 = 206;
    public static void main(String[] args) {
        StartGame startGame=new StartGame();
        System.out.println("我要进入的对战模式是:"+startGame.selectBattle(实战对抗, 王者峡谷));
    }
    public String selectBattle(int way,int map) {
        if (way==实战对抗) {
            return arena(map);
        }else if (way==娱乐模式) {
            return recreation(map);
        }else if (way==排位模式) {
            return rank(map);
        }{
            return "待开发新模式";
        }
    }
    private String arena(int map) {
        if (map==王者峡谷) {
            return "匹配模式:3条对战线路,野区有野怪。10人对战5人一个组队,对战结束后增加经验和金币";
        }else if (map==深渊大乱斗) {
            return "大乱斗模式,1条线路,无野区。10人对战5人一个组队,对战结束后增加经验和金币";
        }else if (map==长平攻防战) {
            return "3v3模式,1条线路,野区有野怪。6人对战3人一个组队,对战结束后增加经验和金币";
        }else {
            return "其他";
        }
    }

    private String recreation(int map) {
        if (map==无限乱斗) {
            return "匹配模式:3条对战线路,野区有野怪。10人对战5人一个组队,对战结束后增加经验和金币";
        }else if (map==克隆大作战) {
            return "克隆大作战:3条对战线路,野区有野怪。每个队伍智能选择一个英雄,对战结束后增加经验和金币";
        }else  {
            return "暂未开发此功能";
        }
    }
    private String rank(int map) {
        if (map==王者峡谷) {
            return "匹配模式:3条对战线路,野区有野怪。10人对战5人一个组队,对战结束后增加经验和金币";
        }else  {
            return "暂未开发此排位模式";
        }   
    }
}

在这里,虽然现在的代码不多,逻辑稍有混乱,如果项目庞大了,出现了更多的内容当前的类就会更加臃肿。当前最明显的问题就是StartGame这个类不是单一原则,内部的功能太多。另一个是通过了if else来判断了,如果增加了其他的游戏模式,那是不是要写更多的if else呢,这样会越来越难以维护。

如何利用策略模式进行重构。

不用慌,策略模式So easy ! 此流程 五步走~

  • 第一步 首先根据业务写出interface

根据各种游戏模式有不同的对战规则和对战地图,这里需要2个接口

public interface IBattleMap {
     String map();
}

public interface IBattleRule {
    String rule();
}
  • 第二步 实现具体的地图类和对战规则类

/**
 * 王者峡谷地图
 */

public class MapKingsCanyon implements IBattleMap {
    @Override
    public String map() {
        return "使用的王者峡谷地图,有野区,9座防御塔,1个主基地";
    }
}
/**
 * 墨家机关道地图
 */

public class MapMohistMechanical implements IBattleMap{
    @Override
    public String map() {
        // TODO Auto-generated method stub
        return "使用墨家机关道地图,无野区,1防御塔,1主基地";
    }
}

/**
 * 匹配模式
 */

public class RuleArena implements IBattleRule {

    @Override
    public String rule() {
        // TODO Auto-generated method stub
        return "10人对战5人一个组队,拆掉对方基地后获胜,对战结束后增加经验和金币";
    }
}
/**
 * 排位模式
 */

public class RuleRank implements IBattleRule {
    @Override
    public String rule() {
        // TODO Auto-generated method stub
        return "10人对战5人一个组队,拆掉对方基地后获胜,对战结束后增加经验和金币,以及排位星星";
    }
}
/**
 * 1v1 单挑模式
 */

public class RuleSolo implements IBattleRule {
    @Override
    public String rule() {
        // TODO Auto-generated method stub
        return "2人对战,拆掉对方基地后获胜,对战结束后增加少量经验和金币";
    }
}
  • 第三步  BasePattern 具体的游戏模式基类

这里说明一下,这里增加的set方法是用来提高代码的扩展性。不然你要在每个具体的实现类的构造函数中创建具体的对战地图和对战规则对象了,现在你只需要在使用的地方直接set某个地图或者规则即可。如果我们现在的排位模式是王者峡谷地图,如果写死了,万一以后深渊大乱斗地图都出现排位了可怎么办...
而selectHero方法是由于每局游戏都是需要选择人物的,这里就认为他是一个通用的方法好了。

public abstract class BasePattern {
    IBattleMap mBattleMap;
    IBattleRule mBattleRule;
    public BasePattern() {

    }
    public void performMap() {
        if (mBattleMap != null) {
            System.out.println(mBattleMap.map());
        } else {
            System.err.println("请设置地图类型");
        }
    }
    public void performRule() {
        if (mBattleRule != null) {
            System.out.println(mBattleRule.rule());
        } else {
            System.err.println("请设置游戏类型");
        }

    }
    //公共方法
    public  abstract void selectHero();
    public void setmBattleMap(IBattleMap mBattleMap) {
        this.mBattleMap = mBattleMap;
    }
    public void setmBattleRule(IBattleRule mBattleRule) {
        this.mBattleRule = mBattleRule;
    }
}
  • 第四步 列举5V5匹配模式和Solo模式的具体实现

/**
 * Solo模式具体实现
 */

public class SoloPattern extends BasePattern {
    @Override
    public void selectHero() {
        System.out.println("本局选择老兵不死,只会逐渐凋零的黄忠");
    }
}

/**
 * 普通匹配模式具体实现
 */

public class ArenaPattern  extends BasePattern{
    @Override
    public void selectHero() {
        System.out.println("本局选择将进酒,杯莫停的李白");
    }
}
  • 最后 创建context角色类在这里实现他们

这里使用了Solo模式的玩法
/**
 * 优化后的StartGame类
 * 
 * @author PandaZwj
 * @date Mar 3, 2019
 */

public class OptimizeStartGame {
    public static void main(String[] args) {
        BasePattern mBasePattern=new ArenaPattern();
        mBasePattern.setmBattleMap(new MapMohistMechanical());
        mBasePattern.setmBattleRule(new RuleRank());
        mBasePattern.performMap();
        mBasePattern.performRule();
        mBasePattern.selectHero();
    }
}

打印结果如下:

使用墨家机关道地图,无野区,1防御塔,1主基地 

2人对战,拆掉对方基地后获胜,对战结束后增加少量经验和金币  

本局选择将进酒,杯莫停的李白


总结


好了这就是变种策略(Strategy)模式。通过上述的两个例子可以看出,前者通过if-else来解决问题。虽然实现起来简单,类行层级单一,但是代码臃肿,逻辑复杂,难以维护,没有结构可言。

使用了策略模式通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换。在简化逻辑结构的同时,增强了系统的可读性、稳定性、可扩展性。更利于在更庞大的项目中。

策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略。这个模式很好的演示了开闭原则(对修改关闭对扩展开放,让程序更稳定更灵活),定义抽象,注入不同的实现,从而达到很好的扩展性。

好了策略模式就到这里了,本篇文章构思的时候查看了具体研究了一下现在王者荣耀游戏模式的各种入口,而且没忍住诱惑开了好几把。。一篇文章下来排位掉了好几颗星。。。如果喜欢本文的话,欢迎点击一下 “喜欢”  给予鼓励支持!


推荐阅读:

一款对Toast,Snackbar进行优化与兼容封装的开源库

Android自定义IM聊天界面

你知道什么是Android中的函数插桩吗?


欢迎关注我的公众号,学习技术或投稿

长按上图,识别图中二维码即可关注

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存