城堡游戏(代码优化)

2018-12-17  本文已影响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 三个命令解析,依旧是硬编码的;优化成框架和数据的形式。

优化做法:

字符串对应关系用 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

上一篇下一篇

猜你喜欢

热点阅读