上篇我们讲了类设计的5大步骤,那么这次我们就来用实际例子来实现这5大步骤。
采用用户的视角(Adopting the client perspective)
作为Rational类设计的第一步,你需要考虑客户可能需要哪些功能。在一家大型公司中,您可能会有各种实施团队需要使用合理的数字,并且可以很好地了解他们需要什么。在这种情况下,与这些客户合作并达成一致的设计目标通常很有用。
鉴于我们这里没有具体的客户,我们假设客户希望我们实现下图的功能:
对应的是我们分数的加减乘除。
Rational类的私有部分
对于Rational类,私有状态很容易指定。有理数被定义为两个整数的商。因此,每个有理数对象必须由这两个整数产生。 因此,将进入私有部分的实例变量的声明为:
int num;
int den;
这些变量的名称是数学术语分子(numerator)和分母(denominator)的缩写版本,它们是指分数的上部和下部。
Rational类的构造函数
假定一个有理数字代表两个整数的商,其中一个构造函数可能假定代表分数中的两个整数。有了这样一个构造函数就可以通过调用Rational(1,3)来定义理性数字三分之一(1/3)。构造函数的这种形式的原型如下所示:
Rational(int x, int y);
虽然在这个阶段没有必要考虑怎么去实现这个功能,但在你的头脑中一直在考虑怎么实现问题有时可以让你头痛。 在这种情况下,值得一提的是,以下列形式实现此构造方法是不合适的:
Rational(int x, int y) {
num = x;
den = y;
}
这个实现的问题是位置约束对分子和分母约束的值需要考虑进去构造函数中。最明显的约束是分母的值不能为零,并且构造函数应该检查这种情况,并发出错误信号。 然而,这是一个更微妙的问题。如果客户被给予分子和分母的不受约束的选择,则将有许多不同的方式来表示相同的有理数字。 例如,有理数三分之一可以用以下写成下列的任一种形式:
假设这些分数都表示相同的有理数,那么我们应该将其视为不合法的并不允许在Rational对象中允许使用分子和分母值的任意组合。如果每个合理的数字都具有一致,独特的表示,它将大大简化我们的实现。
数学家们通过以下规则实现这一目标:
- 分数总是以最低的值表示(即最简方式),这意味着任何常见因素都从分子和分母中消除。在实践中,将分数减少到最低项的最简单方法是将分子和分母除以最大公约数,我们用gcd函数来计算。
- 分母总是为正,这意味着该值的符号与分子一起存储。
- 有理数0始终表示为0/1的分数。
所以我们可以用下面的代码来实现构造函数:
Rational(int x, int y) {
if (y == 0) error("Rational: Division by zero");
if (x == 0) {
num = 0;
den = 1;
} else {
int g = gcd(abs(x), abs(y));//abs函数为取绝对值
num = x / g;
den = abs(y) / g;
if (y < 0) num = -num;
}
}
作为一般规则,每个类都应该有一个默认构造函数,当声明中没有提供任何参数时,该构造函数将被使用。默认有理数的适当数学值为零,表示为分数0/1。因此,默认构造函数的代码看起来像这样;
Rational() {
num = 0;
den = 1;
}
最后,最终定义构造函数的第三个版本是有用的,它接受一个表示整数的参数:
Rational(int n) {
num = n;
den = 1;
}
为Rational类定义方法
鉴于之前我们将Rational类的功能限于算术运算符的决定,找出导出的方法是一个比较容易的任务,特别是在C++中。在许多面向对象的语言(包括Java)中,例如,定义算术运算的唯一方法是导出名为add,subtract,multiply和divide的方法来实现四个算术运算。更糟糕的是,您必须使用接收器语法来应用这些运算符。而不是写令人满意的声明。
Rational sum = a + b + c;
这句话在java中你得这样写:
Rational sum = a.add(b).add(c);
虽然解决这个表达意味着什么并不困难,但是对比C++它在重新定义算术运算符上却没有强大的灵活性和表达力。
在C++中情况好多了。 在C++中,通过重载运算符+, - ,*和/来处理Rational对象来实现有理数的运算。正如的Point类一样,将这些运算符定义为自由函数比方法更容易,这意味着四个运算符的原型如下所示:
Rational operator+(Rational r1, Rational r2);
Rational operator-(Rational r1, Rational r2);
Rational operator*(Rational r1, Rational r2);
Rational operator/(Rational r1, Rational r2);
与Point类中的==运算符一样,这些算术运算符将需要访问r1和r2的字段,这意味着这些操作符方法必须声明为Rational类的朋友。 放置这些声明的最方便的地方在rationalpriv.h文件中,隐藏在该类的私有部分。
尽管在Rational类的专业实现中还有许多其他方法和操作符是有意义的,但本示例中包含的唯一附加功能是一个toString方法和<<操作符的重载版本,主要是为了让你习惯 将这些设施纳入你设计的每个类中。能够以人类可读的形式显示对象的值对于测试和调试非常重要,这是开发过程的重要阶段。
实现Rational类
接下来的代码我就在下一篇博客贴出来,老规矩。