面向对象程序设计——Java语言 3

设计原则

即使类的设计很糟糕,也还是有可能实现一个应用程序,使之运行并完成所需的工作。一个已完成的应用程序能够运行,但并不能表明程序内部的结构是否良好。当维护程序员想要对一个已有的软件做修改的时候,问题才会浮现出来。比如,程序员试图纠正已有软件的缺陷,或者为其增加一些新的功能。显然,如果类的设计良好,这个任务就可能很轻松;而如果类的设计很差,那就会变得很困难,要牵扯大量的工作。在大的应用软件中,这样的情形在最初的实现中就会发生了。如果以不好的结构来实现软件,那么后面的工作可能变得很复杂,整个程序可能根本无法完成,或者充满缺陷,或者花费比实际需要多得多的时间才能完成。在现实中,一个公司通常要维护、扩展和销售一个软件很多年,很可能今天在商店买到的软件,其最初的版本是在十多年前就开始了的。在这种情形下,任何软件公司都不能忍受不良结构的代码。 既然很多不良设计的效果会在试图调整或扩展软件时明显地展现出来,那么就应该以调整或扩展软件来鉴别和发现这样的不良设计。本周将使用一个叫作城堡游戏的例子,这个例子很简单,基本实现了一个基于字符的探险游戏。起初这个游戏并不十分强大,因为还没全部 完成。不过,在本章结束的时候,你就可以运用你的想像力来设计和实现这个的游戏,让它更有趣更好玩。

5.1 城堡游戏

下载要用到的代码castle.zip,仔细的阅读一下这里的两个类,一个Game,一个Room,理解一下这里每一行要做的事情,,然后思考一下,这里有什么问题?这些代码存在什么样的问题?接下来要做的事情是去找出这里面的问题,然后去解决这里面的问题。

我们从main开始对吧,我们要去理解一个代码,从他的main开始,在main里面做了一个game的对象Game game = new Game();,让game去printWelcome。在printWelcome()这个函数上上鼠标右键选择Open Declaration 或者F3,就到了 private void printWelcome()这里。

Open Declaration是打开该方法的接口文件

 

然后就输出了欢迎来到什么,然后他告诉你说现在你在加上一个currentRoom,currentRoom是什么?然鼠标停留在这里,他也会告诉你说这是一个Room对象。

 

 

就进入一个死循环while ( true ) { },处理死循环,死循环里面每次都读语句,读了语句进来以后,对一个字符串做split。因为我们输进去的可能比如说go south,那么它要根据中间空格,把它分成两个单词,第一个单词如果是help做什么,如果是go做什么,如果是bye做什么,是吧?那么如果是help就输出一个help,如果是by就结束,这都这很容易理解。如果是go它调用了一个叫做goRoom的函数。

5.2 消除代码复制

程序中存在相似甚至相同的代码块,是非常低级的代码质量问题。

代码复制存在的问题是,如果需要修改一个副本,那么就必须同时修改所有其他的副本,否则就 存在不一致的问题。这增加了维护程序员的工作量,而且存在造成错误的潜在危险。很可能发 生的一种情况是,维护程序员看到一个副本被修改好了,就以为所有要修改的地方都已经改好 了。因为没有任何明显迹象可以表明另外还有一份一样的副本代码存在,所以很可能会遗漏还 没被修改的地方。

我们从消除代码复制开始。消除代码复制的两个基本手段,就是函数和父类。

这个程序其实问题是非常多,这里面暴露出来的问题,正好就是我们今天要说的在面向对象程序设计当中的一些非常基本的原则。原则第一条,其实我们之前见过的代码复制,我们要消除代码复制,代码复制是设计不良的一种表现,在这个程序当中有非常明显的代码复制。

 

有一段,现在你在什么地方,然后出口有什么?然后往下看goRoom。当我们选择了要去某一个房间的时候,又来了你在什么地方?出口有什么?这就是非常明显的代码复制,所以修改的方案也非常的简单,我们可以把这一段提取出来,把这一段作为是进入到一个房间以后,给玩家看的那么一段提示,所以把它 ctrl +x提取出来,然后加一个函数,public void showPrompt ()。ctrl+v把这个放进来。你在什么地方?然后在这个地方去调一下这个函数,在前面把这一段也去掉,换上这个函数。

 

 

package castle;

public class Room {
    public String description;
    public Room northExit;
    public Room southExit;
    public Room eastExit;
    public Room westExit;

    public Room(String description) 
    {
        this.description = description;
    }

    public void setExits(Room north, Room east, Room south, Room west) 
    {
        if(north != null)
            northExit = north;
        if(east != null)
            eastExit = east;
        if(south != null)
            southExit = south;
        if(west != null)
            westExit = west;
    }

    @Override
    public String toString()
    {
        return description;
    }
}
package castle;

import java.util.Scanner;

public class Game {
    private Room currentRoom;
        
    public Game() 
    {
        createRooms();
    }

    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);

        currentRoom = outside;  //	从城堡门外开始
    }

    private void printWelcome() {
        System.out.println();
        System.out.println("欢迎来到城堡!");
        System.out.println("这是一个超级无聊的游戏。");
        System.out.println("如果需要帮助,请输入 'help' 。");
        System.out.println();
        showPrompt();
    }

    // 以下为用户命令

    private void printHelp() 
    {
        System.out.print("迷路了吗?你可以做的命令有:go bye help");
        System.out.println("如:\tgo east");
    }

    private void goRoom(String direction) 
    {
        Room nextRoom = null;
        if(direction.equals("north")) {
            nextRoom = currentRoom.northExit;
        }
        if(direction.equals("east")) {
            nextRoom = currentRoom.eastExit;
        }
        if(direction.equals("south")) {
            nextRoom = currentRoom.southExit;
        }
        if(direction.equals("west")) {
            nextRoom = currentRoom.westExit;
        }

        if (nextRoom == null) {
            System.out.println("那里没有门!");
        }
        else {
            currentRoom = nextRoom;
            showPrompt();
        }
    }
	   
    public void showPrompt() {
    	System.out.println("你在" + currentRoom);
        System.out.print("出口有: ");
        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();
    }
    
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		Game game = new Game();
		game.printWelcome();

        while ( true ) {
        		String line = in.nextLine();
        		String[] words = line.split(" ");
        		if ( words[0].equals("help") ) {
        			game.printHelp();
        		} else if (words[0].equals("go") ) {
        			game.goRoom(words[1]);
        		} else if ( words[0].equals("bye") ) {
        			break;
        		}
        }
        
        System.out.println("感谢您的光临。再见!");
        in.close();
	}

}

5.3 封装

要评判某些设计比其他的设计优秀,就得定义一些在类的设计中重要的术语,以用来讨论设计的优劣。对于类的设计来说,有两个核心术语:耦合和聚合。耦合这个词指的是类和类之间的联系。之前的章节中提到过,程序设计的目标是一系列通 过定义明确的接口通信来协同工作的类。耦合度反映了这些类联系的紧密度。我们努力要获得 低的耦合度,或者叫作松耦合(loose coupling)。

耦合度决定修改应用程序的容易程度。在一个紧耦合的结构中,对一个类的修改也会导致 对其他一些类的修改。这是要努力避免的,否则,一点小小的改变就可能使整个应用程序发生改变。另外,要想找到所有需要修改的地方,并一一加以修改,却是一件既困难又费时的事情。 另一方面,在一个松耦合的系统中,常常可以修改一个类,但同时不会修改其他类,而且整个程序还可以正常运作。

本周会讨论紧耦合和松耦合的例子。 聚合与程序中一个单独的单元所承担的任务的数量和种类相对应有关,它是针对类或方法 这样大小的程序单元而言的理想情况下,一个代码单元应该负责一个聚合的任务(也就是说,一个任务可以被看作是一个逻辑单元)。一个方法应该实现一个逻辑操作,而一个类应该代表一定类型的实体。聚合理论背后的要点是重用:如果一个方法或类是只负责一件定义明确的事情,那么就很有可能在另外不同的上下文环境中使用。遵循这个理论的一个额外的好处是,当程序某部分的代码需要改变时,在某个代码单元中很可能会找到所有需要改变的相关代码段。

我们的这个城堡游戏的代码是能运行的,能正常运行,一切功能都正常,也没有发现有bug,你怎么输入他反正也都能有一些正确的结果。但是一个能够正常运行的没有bug的代码,不等于它就是一个好的代码,评价一个代码是否好,标准是多元的,并不是只有唯一一个能运行没有bug就是标准。我们还有很多标准

,尤其是这个代码是否适用于将来的需要,什么是将来的需要?将来只有一种需要,就是维护,代码写出来,不是跑一次。不是现在用就好了。你要考虑他一年以后,2年以后5年以后甚至10年以后,这个代码还要有,其他人当然也可能是你自己要继续做下去,需求有了变更,要进一步发展下去了。这个时候你拿之前写的代码,你的代码能不能在今后还能够继续起作用,是不是让今后做维护的人,无论是你还是其他人,能够比较容易的在这个代码基础上做事情。

这是我们去考察一个代码质量非常重要的原则,或者叫做指标。就是代码是不是适合于扩展?比如说在我们现在代码基础上,如果我们想要给他增加新的方向怎么办?我们现在有东南西北4个方向,城堡,他可能有好几层楼的,那么如果我们想要up down,要增加这两个方向,在现在这个代码上面我们要怎么做呢?我们现在的城堡的方向在什么地方体现出来?你看对于room来说,他有4个成员变量,这4个成员变量是别的Room、北南东西4个成员变量。

 

在构造他的时候,我们给的是一个描述字符串。

    public Room(String description)

    {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值