本文是观看翁恺老师《面向对象程序设计Java》时所做原创笔记
尽管这个类的设计很差劲,但是它仍然可以执行它,并且执行它需要做的事情。一个已经完成的应用可以正常工作,但是它不会显示程序的内部构造。问题出现在维修人员希望对现有的软件进行更改时。
例如,程序员试图修改现有的软件,或添加新特性。很明显,如果类设计得很好,那么这个任务就会变得简单;而如果类的设计不够好,那么它将会非常的困难,并且需要花费很多的精力。在大型应用程序中,这种情况在初始实施时就会出现。如果软件的结构很差,那么接下来的工作就会非常复杂,整个软件可能完全不能完成,或者存在大量的漏洞,或者花费的时间远远超过了它的需求。实际上,一家公司往往要多年维护、扩展和销售一款软件,而现在在商店里购买的软件,其原版早在十年之前就已经存在了。在这样的情况下,没有哪个软件公司能够容忍代码结构不好。因为许多不好的设计在尝试修改或者扩充软件时都会有明显的影响,所以我们需要通过修改或者扩充软件来识别和识别这些不好的设计。
本周,我们将会用到一个叫做“城堡”的游戏,这是一个非常简单的,可以说是一个以人物为基础的冒险游戏。一开始,这款游戏并不是很厉害,因为它还没有完全开发出来。但是,到了最后,你可以发挥你的想象力去设计和实施这款游戏,使之更加有趣。代码在MOOC课程里面,CSDN下载里也有,我就不上传了
1. 消除代码复制
程序中存在相似甚至相同的代码块,是非常低级的代码质量问题。
代码复制
存在的问题是,如果需要修改一个副本,那么就必须同时修改所有其他的副本,否则就 存在不一致的问题。这增加了维护程序员的工作量,而且存在造成错误的潜在危险。很可能发 生的一种情况是,维护程序员看到一个副本被修改好了,就以为所有要修改的地方都已经改好 了。因为没有任何明显迹象可以表明另外还有一份一样的副本代码存在,所以很可能会遗漏还 没被修改的地方。
我们从消除代码复制开始。消除代码复制的两个基本手段,就是函数
和父类
。
2. 封装
2.1 聚合和耦合
为了判断某个设计优于其它设计,必须在类别的设计中定义一些关键词汇,以便讨论设计的优点和缺点。在类的设计中,有两个关键字:连接和聚集。连接是指类别与类别的连接。前面几章已经提及,程式设计的目的是透过定义清楚的介面通讯而共同工作的一组类别。耦合程度是指这种类型连接的紧密程度。我们试图得到一个低耦合,也就是所谓的松散耦合。
耦合性决定了你的应用程式是否易于更改。在紧密结合的结构中,对一个类别的更改也会引起其它类别的变更。要尽量避免这种情况,否则,一个微小的变化就会导致整个应用软件的变化。此外,要找出全部的改动之处,一一修正,则是一项艰巨而耗时的工作。另一方面,在一个松散的系统中,经常可以对一个类进行修改,而不会对其它的类进行修改,并且整个程序仍然能够运行。
这周我们将会讲到紧密和松散的耦合。集合与一个程序中单个单位的工作数目和类型相关,对于象类或者方法那样的程序单位,最好是由一个代码单位来完成一个集合(即,可以把一个任务看成一个逻辑单位)。一个方法应当执行一个逻辑运算,而类应当表示特定的实体。聚合原理的关键在于复用:如果某个方法或者类仅仅负责一项明确的任务,它很有可能会被用于其他的环境。遵循这种原理还有一个好处,那就是当程式码的某一部份程式码需要变更时,就可以在特定的程式码单位内发现所有需要变更的程式码。
增加可扩展性
- 可以运行的代码 != 好代码
- 对代码做维护的时候最能看出代码的质量
2.2 用封装来降低耦合
- Room类和Game类都有大量的代码和出口相关
- 尤其是Game类中大量使用了Room类的成员变量
- 类和类之间的关系称作耦合
- 耦合越低越好、保持距离是形成 良好代码的关键
public class Room {
public String getExitDesc(){
String ret = "";
if( northExit != null )
ret += "north ";
if( eastExit != null )
ret += "east ";
if( southExit != null )
ret += "south ";
if( westExit != null )
ret += "west ";
return ret;
}
}
2.3 用接口来实现聚合
- 给Room类实现的新方法,把方向的细节彻底隐藏在Room类内部了
- 今后方向如何实现就与外部无关了
3. 可扩展性
可扩展性的意思就是代码的某些部分不需要经过修改就能适应将来可能的变化。
用容器来实现灵活性
Room.java
public class Room {
private String description;
private HashMap<String, Room> exits = new HashMap<>();
public Room(String description)
{
this.description = description;
}
public void setExit(String dir, Room room)
{
exits.put(dir, room);
}
@Override
public String toString()
{
return description;
}
public String getExitDesc(){
StringBuffer sb = new StringBuffer();
for( String dir : exits.keySet() ){
sb.append(dir);
sb.append(' ');
}
return sb.toString();
}
public Room getExit(String direction){
Room ret = exits.get(direction);
return ret;
}
}
4. 框架加数据
从程序中识别出框架和数据,以代码实现框架,将部分功能以数据的方式加载,这样能在很大程度上实现可扩展性。
- 命令的解析是否可以脱离if-else
- 定义一个Handler来处理命令
public class Handler {
protected Game game;
public Handler(Game game){
this.game = game;
}
public void goCmd(String word){ }
public boolean isBye(){
return false;
}
}
- 用Hash表来保存命令和Handler之间的关系
public Game()
{
handlers.put( "go", new HandlerGo(this));
handlers.put( "help", new HandlerHelp(this));
handlers.put( "bye", new HandlerBye(this));
createRooms();
}
改造完的整体项目结构: