2.1.4 让实现安全
鉴于当今黑客及对重要软件系统未经授权入侵的现实情况,程序员必须在代码中添加安全措施,以使程序对使用者是安全的。虽然Java为你管理内存,检查数组下标的合法性,且是类型安全的,但一个错误会使你的代码易受攻击。实现ADT时应该时刻铭记安全性,尽管在已有的代码中增加安全机制可能是困难的。
注:你可以在程序中检查可能出现的错误来练习有安全机制的程序设计(fail-safe programming)。安全可靠程序设计(safe and secure programming)通过验证输入给方法的数据和参数的合法性,消除方法的副作用,对客户和使用者的行为不做任何假设,来扩展有安全机制的程序设计的概念。
安全说明:保护ADT实现的完整性
当实现一个ADT时,必须问自己的两个问题是
- 如果构造方法没有完全执行,那么可能会发生什么?例如,构造方法可能在完成初始化之前就抛出一个异常或错误。但是入侵者可能捕获异常或错误,并试图使用部分初始化的对象。
- 如果客户试图创建一个其容量超出给定范围的包,那么可能会发生什么?
如果这两个动作可能导致问题,则我们必须阻止它们。
对于类ArrayBag,我们想防范前面安全说明中所描述的两种情形。现在开始细化ArrayBag的不完整的实现,在类中增加下列两个数据域,以使代码更安全:
这两个修改都涉及构造方法。因为默认的构造方法调用带参数的构造方法,所以仅修改后者就足够了。为确保客户不能创建太大的包,构造方法应该检查客户所需包的容量与MAX_CAPACITY值。如果需要的容量太大,则构造方法可以抛出一个异常。
如果所需的容量处在允许范围内,则ArrayBag的构造方法为什么还不能正确完成呢?因为内存不足可能导致分配数组失败。这样一个事件会导致错误OutOfMemoryError。一般地,客户将这个错误看作致命事件。黑客可能捕获这个错误(就像你捕获异常一样),并试图使用部分初始化的对象。为防止这种情况,类的每个重要方法在执行其操作之前都可以检查域initialized的状态。这样,畸形对象就不会再有动作。对于正确初始化的对象,构造方法将把域initialized置为真。
下面是修改后的构造方法。
注意,构造方法在成功完成其他任务后,最后一个动作是将initialized赋值为真。还应注意,IllegalStateException是标准运行时异常。
下面来看看如何使用initialized。
在数组bag已成功分配的基础上,ArrayBag中的任何公有方法在继续执行之前都应该确保数据域initialized的值为真。如果initialized为假,这样的方法可以抛出一个异常。例如,可以如下所示修改方法add。
注:异常SecurityException和IllegalStateException都是包java.lang中的标准运行时异常。因此,不需要import语句。
因为我们将在多个方法中检查initialized,所以为避免代码重复定义下列私有方法。
方法add可以修改为:
应该以相同的方式修改核心方法toArray,因为它用到了ArrayBag的数据域bag。
安全说明:你所熟知的编写Java代码的某些常见准则,实际上增加了代码的安全性。这些准则是:
- 将类的大多数数据域声明为私有的,如果不是全部。任何公有数据域都应该是静态和终态的,且有常量值。
- 避免那些掩盖代码安全性的所谓聪明的逻辑。
- 避免重复代码。相反,将这样的代码封装为一个可供其他方法调用的私有方法。
- 当构造方法调用一个方法时,确保这个方法不能被重写。
安全说明:终态类。注意,我们将ArrayBag声明为一个终态类。因此,不会有从ArrayBag派生的其他类,即ArrayBag不能是另一个类的父类或基类。终态类比非终态类更安全,因为程序员不能使用继承来改变它的行为。稍后我们将细化这个方法,定义终态方法而不是整个类。