美文网首页
城堡游戏(代码优化)

城堡游戏(代码优化)

作者: 旦暮何枯 | 来源:发表于2018-12-17 13:18 被阅读0次

    城堡中有多个房间,用户通过输入north, south, east, west等来确认去哪一个房间(此时窗口会有提示转到哪个房间),如果此时多出一个房间,需要使用up, down才能到达,修改代码则需要代码具有可扩展性,对原来的代码进行优化来实现这个功能。

    优化前代码结构:


    优化前.png

    优化路程

    将类中相同的代码抽离,转为一个公用的方法

    如图将注释的代码抽离成一个方法,供调用:

    private void nowRoom(){
            System.out.println("现在你在" + currentRoom);
            System.out.print("出口有:");
    //        将出口信息封装在Room类中,降低程序之间的耦合
    //        if(currentRoom.northExit != null)
    //            System.out.print("north ") ;
    //        if(currentRoom.eastExit != null)
    //            System.out.print("east ");
    //        if(currentRoom.southExit != null)
    //            System.out.print("south ");
    //        if(currentRoom.westExit != null)
    //            System.out.print("west ");
            System.out.println(currentRoom.getExitDesc());
            System.out.println();
        }
    

    Room 类中的变量转为私有变量

    Room 类和 Game 类有大量的代码出口相关,Game 类中大量使用了 Room 类中的成员变量;利用封装来降低类和类之间的耦合;便于代码后续的维护。

    在将 Room 类的变量转为私有后,发现 Game 类中有几处报错;说明 Game 类中调用到了 Room 类的成员变量;
    第一处是上一步中提取的方法,阅读代码功能;是显示当前 Room 的出口信息

    优化思想:调用者不做被调用类的逻辑处理

    出口信息能否只在 Room 中做逻辑处理,Game 类中只负责调用就好。

    做法:

    在 Room 类中增加 getExitDesc 方法。

      public String getExitDesc(){
    //        使用 StringBuffer 是因为 String 对象是管理者,每次对 String 对象的修改都是新增一个 String 对象,
    //        对系统开销比较大,但是 StringBuffer支持修改
            StringBuffer sb = new StringBuffer();
            if (northExit != null)
                sb.append("north ");
            if (eastExit != null)
                sb.append("east ");
            if (westExit != null)
                sb.append("west ");
            if (southExit != null)
                sb.append("south ");
            return sb.toString();
        }
    

    这样在 Game类中的 nowRoom 方法中,只需要调用 getExitDesc 方法就能返回出口信息。

    第二处错误是在 Game 类中, goRoom 方法中,显示的是玩家进入下一个房间后,显示出口信息。
    同样的,想办法让 Room 类中就处理其中的逻辑,直接返回给 Game 类下一个房间的出口信息。

    在 Room 类中新增一个 getExit 方法,直接将 Game 类中的逻辑处理复制过来用。

        public Room getExit(String direction){
            Room ret = null;
            if(direction.equals("north")) {
                ret = northExit;
            }
            if(direction.equals("east")) {
                ret = eastExit;
            }
            if(direction.equals("south")) {
                ret = southExit;
            }
            if(direction.equals("west")) {
                ret = westExit;
            }
            return ret;
        }
    

    减少代码中的硬编码

    优化思想:使用容器来代替硬编码

    Room 类有四个成员变量表示四个方向。使用 HashMap 容器来装载方向,这样方便以后新增方向变量,提高代码灵活性。

    做法:

    去掉原有变量,新增一个 HashMap 容器变量。
    private HashMap<String,Room> exit = new HashMap<String,Room>();
    同步修改类方法中的 setExits 方法。因为使用了容器的原因,所以只能为 Room 对象一个个出口添加。

    //    录入房间空间位置的方式改变,由原来的对一个房间的四个方向分别定义,改为对一个房间
    //    自定义方向以及该方向上的新房间
    public void setExit(String dir, Room room){
            exit.put(dir,room);
        }
    

    发现 Room类 中的 getExit 方法出现报错,可以通过容器直接返回房间的方向信息:

    public Room getExit(String direction){
    //        Room ret = null;
    //        if(direction.equals("north")) {
    //            ret = northExit;
    //        }
    //        if(direction.equals("east")) {
    //            ret = eastExit;
    //        }
    //        if(direction.equals("south")) {
    //            ret = southExit;
    //        }
    //        if(direction.equals("west")) {
    //            ret = westExit;
    //        }
            return exits.get(direction);
        }
    

    getExitDesc 方法也要同步做修改,var.keyset() 方法获取 map 的 key 的集合。

    public String getExitDesc(){
    //        使用 StringBuffer 是因为 String 对象是管理者,每次对 String 对象的修改都是新增一个 String 对象,
    //        对系统开销比较大,但是 StringBuffer支持修改
            StringBuffer sb = new StringBuffer();
            for (String dir :
                    exits.keySet()) {
                sb.append(dir+" ");
            }
    //        if (northExit != null)
    //            sb.append("north ");
    //        if (eastExit != null)
    //            sb.append("east ");
    //        if (westExit != null)
    //            sb.append("west ");
    //        if (southExit != null)
    //            sb.append("south ");
            return sb.toString();
        }
    

    因为修改了 Room 类中的房间出口信息设置方法,Game 类中的 creatRooms 方法报错。

    private void createRooms()
        {
            Room outside, lobby, pub, study, bedroom;
          
            //  制造房间
            outside = new Room("城堡外");
            lobby = new Room("大堂");
            pub = new Room("小酒吧");
            study = new Room("书房");
            bedroom = new Room("卧室");
            
            //  初始化房间的出口
    //        outside.setExits(null, lobby, study, pub);
    //        lobby.setExits(null, null, null, outside);
    //        pub.setExits(null, outside, null, null);
    //        study.setExits(outside, bedroom, null, null);
    //        bedroom.setExits(null, null, null, study);
            
            outside.setExit("eastExit",lobby);
            outside.setExit("southExit",study);
            outside.setExit("westExit",pub);
            lobby.setExit("westExit",outside);
            pub.setExit("eastExit",outside);
            study.setExit("northExit",outside);
            study.setExit("eastExit",bedroom);
            bedroom.setExit("westExit",study);
            
            currentRoom = outside;  //  从城堡门外开始
        }
    

    到这里,如果想要增加一个新的出口信息,只要在 Game 类中初始化一个新的房间,增加新的出口信息类型就可以了;

    以框架+数据来提高可扩展性

    优化思想:将硬编码改成 框架 + 数据

    城堡游戏中 main 函数中的 go,help,bye 三个命令解析,依旧是硬编码的;优化成框架和数据的形式。

    优化做法:

    • 命令的解析脱离 if-else
    • 定义一个 Handle 来处理命令
    • 用 Hash 表来保存命令和 Handler 之间的关系

    字符串对应关系用 HashMap[为什么字符串对应一个东西通常都是用 HashMap?]

    定义一个 HashMap,key 和 value 都必须是对象,城堡游戏中有的是,String 和调用一个函数。因此需要创建一个中间类 Handle,通过 handle 类的对象调用对应的函数。
    如下创建四个类。

    //父类 Handler
    public class Handler {
        public void doCmd(String word){
    
        }
    
        public boolean isBye() {
            return false;
        }
    }
    // HandlerGo
    public class HandlerGo extends Handler {
        private Game game;
    
        public HandlerGo(Game game){
            this.game = game;
        }
    
        @Override
        public void doCmd(String word) {
            game.goRoom(word);
        }
    }
    
    // HandlerHelp
    public class HandlerHelp extends Handler {
        @Override
        public void doCmd(String word) {
            System.out.println("迷路了吗?你可以做的命令有:go bye help");
            System.out.println("如:\tgo east");
        }
    }
    
    // handlerBye
    public class HandlerBye extends Handler{
        @Override
        public boolean isBye() {
            return true;
        }
    }
    

    在 Gmae 类初始化 Handle 类的 HashMap 对象,并在构造器中构建 不同功能的 Handle 对象放入 map 中;

    public class Game {
        private Room currentRoom;
        /*用Hash表保存命令与Handler之间的关系*/
        private HashMap<String, Handler> handlers = new HashMap<String, Handler>();
    
            
        public Game() 
        {
            handlers.put("go", new HandlerGo(this));
            handlers.put("bye", new HandlerBye());
            handlers.put("help",new HandlerHelp());
            createRooms();
        }
        ...
    

    将原来 main 函数中的循环体,提取成一个新的方法:

    public void play(){
            Scanner in = new Scanner(System.in);
    
            while ( true ) {
                    String line = in.nextLine();
                    String[] words = line.split(" ");
    //                利用Hash表<K, V>的特性,如果用户输入"bye",通过handler.get()得出handler
    //                类型下面这句就相当于:Handler handler = new HandlerBye();
                    Handler handler = handlers.get(words[0]);
                    String value = " ";
                    if (words.length > 1){
                        value = words[1];
                    }
                    if (handler != null){
    //                    此时handler为HandlerBye型,没有value值
                        handler.doCmd(value);
    //                    HandlerBye继承了Handler中的isBye()方法并将其覆盖,此时handler.isBye()返回true
                        if (handler.isBye()){
                            break;
                        }
                    }
    //              if ( words[0].equals("help") ) {
    //                  game.printHelp();
    //              } else if (words[0].equals("go") ) {
    //                  game.goRoom(words[1]);
    //              } else if ( words[0].equals("bye") ) {
    //                  break;
    //              }
            }
            in.close();
        }
    

    优化完成,代码运行正常;优化完成后,当需要新增动作命令时,只需要新增一个 handler类型,并在构造器里新增新类型的 map 键值对。而不用对 play 函数做改动。这就是通过 框架 + 数据 实现的可扩展性

    博客地址:http://www.wengfe.win/2018/12/14/2018-12-14/#more
    代码地址:https://github.com/wengfe/JAVA/tree/master/castle

    相关文章

      网友评论

          本文标题:城堡游戏(代码优化)

          本文链接:https://www.haomeiwen.com/subject/lriqkqtx.html