王者荣耀之「策略者模式」

2019-03-03  本文已影响76人  张文靖同学

目录

  1. 什么策略模式(Strategy)
    1.1 Strategy模式的定义
    1.2 为什么用Strategy模式,他的优缺点
    1.3 传统策略模式的UML类图
    1.4 在android源码中使用的到的策略模式

  2. 通过王者荣耀的例子带大家熟悉一下 策略模式
    2.1 实现流程的第一代代码
    2.2 如何利用策略模式进行重构(共五步)

  3. 总结

前言

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

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

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

1 首先我们了解一下什么策略模式(Strategy)

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

1.1 Strategy模式的定义

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

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

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

缺点

1.3 传统策略模式的UML类图


Strategy UML

角色介绍:

  1. Context 用来操作策略的上下文环境
  2. Strategy策略的抽象
  3. Concrete StrategyA Concrete StrategyB 具体策略的实现。

1.4 在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{
 //... 此处省略具体内容
}

2 接下来通过王者荣耀的例子带大家熟悉一下 策略模式

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


王者荣耀策略模式UML

2.1 实现流程的第一代代码

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

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呢,这样会越来越难以维护。

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

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

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

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

public interface IBattleMap {
     String map();
}

public interface IBattleRule {
    String rule();
}

2.2.2 第二步 实现具体的地图类和对战规则类

/**
 * 王者峡谷地图
 */
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人对战,拆掉对方基地后获胜,对战结束后增加少量经验和金币";
    }
}

2.2.3 第三步 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;
    }
}

2.2.4 第四步 列举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("本局选择将进酒,杯莫停的李白");
    }
}

2.2.5 最后 创建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人对战,拆掉对方基地后获胜,对战结束后增加少量经验和金币
本局选择将进酒,杯莫停的李白


3 总结

好了这就是变种策略(Strategy)模式。通过上述的两个例子可以看出,前者通过if-else来解决问题。虽然实现起来简单,类行层级单一,但是代码臃肿,逻辑复杂,难以维护,没有结构可言。
使用了策略模式通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换。在简化逻辑结构的同时,增强了系统的可读性、稳定性、可扩展性。更利于在更庞大的项目中。

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

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

上一篇下一篇

猜你喜欢

热点阅读