这是我为公司设计的一道Java语言的面试题……嗯……也可以说是两道啦:
写一个Money类,包含金额和币种两个属性,以及加、减两个方法。
附加题:将上题中的Money类实现为一个ValueObject。
先来看本题。
第一个要考察的方面是为两个属性选择正确的数据类型。(不知道什么是属性?……回去换个被程序员更有前途的职业吧。)
对金额而言,最自然的选择是float或者double。但这不是最好的选择。在计算机中,float和double都是有误差的。比如1有可能是1.00000001。所以对金钱这么重要而敏感的东西来说,还是BigDecimal比较好。当然,对于后面的加减运算来说比较麻烦一些。这时还是让人比较怀念C++的运算符重载的。
用int怎么样呢?如果用int来表示以分为单位的金额的话,到也不是不可以。这样精度有保证,而且运算也比较方便。不过如果要表示5厘……就没办法了。
对币种来说,很多人多自然选择了String。不过我以为String不如int,或者short。虽然在Java中,String几乎可以当成基本数据类型使用,但是String毕竟是对象,还是经常要考虑null不null的问题,而且字符串匹配总是不如int(或short)方便和快捷。
到了JDK 1.5的时代,我们又多了一个选择:enum。不过如果是在一个信息系统中,货币种类是可以由用户维护的话,那么enum就不能用了。
除此以外,从OO的角度来说,还有更好的设计吗?有。不过这个问题我留到下一篇讨论。
然后是方法的设计。因为有币种,因此加、减时自然都要考虑同币种的金额才能相加(暂时不考虑汇率换算的问题)。如果币种不相同怎么办?这就是考察的第二个重点: 错误处理。
有不少人使用返回值表示计算的成功与否。比较奇怪的是他们大部分都没有C,C++程序员的经历,不知道是不是学校教育的结果。也有人直接使用System.out.println输出错误信息,这个就更离谱了。
正解应该是使用Exception。偷懒的话,可以使用IllegalArgumentException。如果知道自己定义一个Exception,比如IncompatibleCurrencyException,那如果是我面试,这样的人技术上基本就算过关了。
所以两个方法的原型应该是:
public void add (Money operand)
throws IncompatibleCurrencyException
public void subtract (Money operand)
throws IncopatibleCurrencyExcepiton
如果使用BigDecimal表示金额,那么这里还有一个BigDecimal的加减方法的名称的问题。不过这倒不重要。写程序的时候总有API可以查的。
对于参数是否可以为null,有两种选择。一种是参数为null时,什么也不做。另一种是抛出异常,比如NullPointerException。我认为第二种选择为好。大部分情况下,用null做参数调用add和subtract方法恐怕不是程序员的本意,而是其它程序出错的结果。直接抛出异常有助于及早发现其它部分的错误。
除了属性和加,减方法以外,构造函数以及getter/setter函数虽然没有提到,但作为一个完整的类,还是应该要有的。从健壮性的角度考虑,在构造函数中应该对输入参数有一定判断。比如金额不能为null等。
接下来是附加题。什么是ValueObject。简单说,ValueObject的值(状态)一旦创建以后就不会改变了。所以可以当基本数据类型用。比如Java中的String, BigDecimal, BigInteger都是ValueObject。StringBuffer就不是。
要把Money变成ValueObject,首先不能有setter。构造函数是从类外部设定属性值的唯一途径。
其次,add和subtract方法不能修改this的属性,运算结果要以新的Money对象返回,这样其原型就要变成:
public Money add (Money operand)
throws IncompatibleCurrencyException
public Money subtract (Money operand)
throws IncopatibleCurrencyExcepiton
第三,最好是能重新定义equals和hashCode两个方法。如何重新定义,请看Pearson Education的《Effective Java Programming Language Guide》7,8两章。
遗憾的是,到目前为止能全部达到以上要求的应聘者还没有出现过。更不用说我心中一个非常不现实的奢望了,就是看到一个因聘者首先写下这一行:
public class MoneyTest extends TestCase {
......