以前一直做J2EE的程序,对J2SE的程序不很了解非凡是UI设计。因此虽然如此小的一个程序,对我来说都是一个很大的挑战。可能网上的计算器很多,Java版本的也不少,但是似乎实现的功能都是比较简单(应该是我没有发现),于是就想自己写一个。从上一个星期开始设计,花费了将近两个星期的时间,终于在昨天把它作完了。在这期间我碰到不少的问题,同时也学到不少的知识,在这里希望可以和大家共享。
一、设计思想
1、界面与逻辑相分离的原则。应用程序都需要有操作界面和用户互动,并且需要有后台逻辑对用户的输入作相应的处理。面向对象的编程语言最大的好处,就是提高了程序的可重用程度,降低维护成本。因此,假如我们把用户界面和业务逻辑混在一起的话,也同样可以实现的功能,但是到了最后,你会发现这个程序,就像一个摇摇欲坠的楼房一样,根本就不能碰,跟别说升级和维护。但是假如把界面和逻辑分开,界面或者逻辑的修改都不会干扰到对方,并且这个业务逻辑的程序同样可以在另外一个具有相同逻辑的系统中使用,因此大大提高了程序的可重用性和维护性。
2、单一职责原则。我们在设计时总是在降提高内聚性,降低耦合性,而这个原则恰恰就是降低耦合性的一个很好的原则。每一个类只承担属于本身的责任,也就是仅有一个可以引起它变化的原因。在这个计算器的开发过程中,很好的遵循了这个原则。就用户界面的设计而言,它包含了很多不同功能的按钮,但是仔细研究就会发现,有些按钮的作用是相同的,例如对于数字键来说,它们的功能就是按下时在显示器显示相关的数字,虽然小数点和正负号不是数字键,但是它们和数字键的功能是一致的,因此就把它们放在了一个面板中(NumberPanel)。剩余的按键也是按照这个原则划分为了几个不同的面板,例如运算符面板(OperatorPanel)、设置位宽的面板(BitWidthPanel)、各进制之间的切换面板(SystemPanel)等等。
3、开放—封闭原则(OCP)。这是援引Robert C. Martin所著的《灵敏软件开发——原则、模式与实践》一书中的话。它的意思是说,根据这个原则设计的模块有两个特征:对于扩展是开放的,也就是模块可以随时根据需求的变化进行扩展;对于修改是封闭的,对模块进行扩展时不需要修改模块的任何代码,并且模块内部任何修改都不应该影响到外部程序。因此在程序的设计中,一个类对于客户程序员无论何时都提供一个相同的接口,外部传入一个参数,这个类就像相应的传回一个结果。计算器中的业务逻辑程序,例如数学计算类(MathCompute)和各进制之间的转换类(Convertion)都使用了这个原则。
4、保证提供给外部的每一个方法都可以正常运行。在以往的开发中,总是一直得写呀写呀,并不管写出的类能不能正确运行。于是到了最后的集成阶段,才忽然发现程序根本就不能通过。这时的程序已经是一个很庞大的怪物,要想跟踪一个小小的错误,要花费不少的心机。因此在开发过程一定注重单元测试,要保证提供给外部的每一个方法都可以正确的运行。在这个过程,JUnit帮了很大的忙,在后面再具体介绍它的使用方法。
以上的这些设计思想和原则给整个开发过程带了很大的帮助,并且使开发变得有趣起来。
二、具体开发过程
这个开发的工程都是由设计、编码和测试三部分组成。下面就各进制之间的转化类Convertion来说明开发过程。
1、设计。
首先应该分析这个说要完成的功能,它的主要任务是完成十六进制、十进制、八进制和二进制之间的相互转化,需要包含负数,但不用考虑小数。
其次,确定接口参数。根据功能可知,它传递给外部的就是一个转后的数据,而外部除了需要传递给转化前的数据以外,是否还需要其他参数呢?我们都知道,十进制的负数同样可以利用其他进制进行表示,例如二进制,转化方法是:求十进制数的绝对值对应的二进制数,最高位为1;然后对二进制逐位求反,最高位除外;接着最低一位加1,需要进位的话依次进位,最高位除外。如今的问题就摆在了我们面前,我们如何知道这个数的最高位是第几位呢,也许有人会说,那就用转化后的前面再加上一位,好,即使这样可以成立,那么-1转化后就是11,3转化后也为11,现在假如需要现在的把这个数二进制数再转化为十进制,那么我们如何知道原来的是正数还是负数呢,假如要转化为十六进制,八进制又当如何呢?经过以上的分析我们发现,还应该有另外一个参数——二进制时的宽度,只有这样才能知道谁是最高位,因此我们就称这个参数为位宽。
最后,设计类。根据第二步的分析,因为位宽对每一个方法都是一样,所以可能把它作为类的成员变量,而另外两个参数转化前的值和转化后的值就分别作为输入参数和返回值。而类对外的接口只有各个转化的方法,因此这几个方法设计为public权限,而其它所有的辅助方法都为private权限。位宽通过构造函数传递,外部不能直接访问和修改,设置也设置为private权限。为了减少计算量,所有的转化都以二进制为中介。例如十六进制到十进制,是十六进制→二进制→十进制。
2、编码。经过第一步的简单设计后就可以开始编码了。关于编码中的具体问题就不再说明,可以代码内的注释。代码只包含类中一部分。
public class Convertion {
/**
* 当前的存储位宽
*/
private int bitWidth;
/**
* 构造函数,设置当前系统的位宽。
* @param bitWidth int 位宽
*/
public Convertion(int bitWidth) {
this.bitWidth = bitWidth * 8;
}