[置顶] 老米吧严禁向诈骗等违法客户出售域名,国内在严打诈骗

[置顶] 老米吧长期出售百度加V认证老域名、权重外链老域名

[置顶] 出售企业个人历史BA老域名,事业单位,社会团体历史BA老域名

[置顶] 出售10年以上Godaddy老域名、Godaddy历史BA老域名

[置顶] 老米吧长期出售各种外链权重、BA历史老域名、15年以上老域名

用继承来进行设计

一旦理解了多态性,你就会觉得所有东西应该都是继承下来的,因为多态性实在是太聪明了。但是这样做会加重设计的负担;实际上,如果你一遇到“要用已有的类来创建新类”的情况就想到要用继承的话,事情就会毫无必要地变得复杂起来了。

较好的办法还是先考虑合成,特别是当你不知道该继承那个类的时候。合成并不强求你把设计搞成一个类系。此外它还更灵活,因为使用合成的时候,你可以动态地选择成员的类型(以及它们的行为)而使用继承的话,就得在编译时指明对象的确切类型。

但是程序运行时函数的reference可以连到另一个对象上,因为可以用其它对象来替换它,于是构造函数的行为就发生了变化。这样你就在运行时获得了高度的灵活性。这也被称为状态模式。反观继承,它不能让你在运行时继承不同的类;这个问题在编译的时候就已经定下来了。

本地内部类

你也可以在代码段里,通常就是方法的正文部分创建内部类。本地内部类不能有访问控制符,因为它不属于宿主类,但是它确实可以访问当前代码段的final变量,以及宿主类的所有成员。一个程序分别用本地内部类和匿名内部类实现了这个接口,两者有着相同的行为和功能。由于本地内部类的名字在这个方法外面是没法访问的,因此用本地内部类来代替匿名内部类的唯一正当的理由就是,你需要一个有名字的构造函数,并且/或者要重载这个构造函数,因为匿名内部类只能进行实例初始化。选择本地内部类而不是匿名内部类的唯一原因就是,你必须创建多个哪种类的对象。

Java接口(interface)的应用

Interface关键词进一步强化了abstract的概念。你可以把它想象成纯的abstract类。它能让开发人员定义类的形式:方法,参数列表,返回值的类型,但是却没有方法的正文。Interface也可以包含数据成员,但是它天生就是static和final的。Interface只提供形式,不谈实现。

Interface的意思是“所有实现这个接口的类都应该长这个样子”。因此任何程序,只要用到了这个interface就都知道它有哪些方法可提供调用了,仅此而已。因此,interface会被用做定义类之间的协议。

下传与运行时的类型鉴别

由于“上传”(沿着继承关系向上移动)之后,类的具体信息丢了,因此“用下传——也就是沿着继承关系重新向下移动——来提取类型的信息”就成了顺理成章的事情了。你知道上传总是安全的;基类的接口不可能比派生类的更大。因此它肯定能收到那些通过基类接口发送的消息。但是碰到下传的时候,你就不能肯定“这个形状是不是圆了”(只是举个例子)。它可以是一个三角形,一个矩形或是其它什么形状。

要想解决这个问题,必须要有办法能够确保下传是正确的,这样你就不会把对象误传给另一个类型了,于是也不会向它发送什么它不能接受的消息了,这是相当不安全的。某些语言(像C++)需要经过使用特别处理,才能安全地进行下传。但是Java类型传递要经过检查!所以,尽管看上去只是用一对括号做了些普通的类型转换,但是运行的时候,系统会对这些转换做检查,以确保它确实是你想要的转换类型。如果不是,你就会得到一个classcastException。这种运行时的类型检查被称为“运行时的类型鉴别”。

Java中的常量分组

由于interface的数据成员自动就是static和final的,因此interface是一种非常方便的,创建一组常量的工具。这点同C和C++的enum很相似。注意,Java的编程风格是,用全部大写字母(用下划线分隔同一个标识符里的各个单词)来表示,用常量进行初始化的static和final变量。

Interface的数据成员自动就是public的,因此就不必再注明了。你可以想对别的package那样,用importc08.*或者c08.Months把它引进来,这样就能在这个package的外面用Months.JANUARY之类的表达式来使用这些常量了。当然,你得到的是一个int,因此它没有像C++的enum那样的类型安全,但是这种很常见手法要比直接在程序里面用数字要好得多。这种方法通常被称为使用神奇数字,并且使得代码的维护变得非常困难。

用继承扩展interfere

实现多个接口的时候可能会遇到一些小问题。例如,Can和Action都有一个一模一样的fight()方法。这里没有问题,因为它们使用的是同一个方法。但是,有个难题要归因于覆写、实现和重载的不期而遇,以及“不能仅通过返回值来辨别重载的方法。如果把最后两行的注释去掉,就会出现错误。而且在要并的接口里面放上同名的方法,通常也会破坏程序的可读性,所以别这么做。

你可以用继承,往interface里面添加新的方法,也可以用继承把多个interface合并成一个新的interface。在这两种情况下,你所得到的都只是一个新的interface。但是我们用Dang对Monster做了一点扩展,然后生成一个新的interface,这样就实现了这个接口。

内部类与上传

到目前为止,内部类还没有表现出什么非常惊人的特质。毕竟,如果你所追求的只是隐藏机制,那么Java已经有了——只要赋予类package权限(只能在package内部访问)就行了,何必要把它做成内部类。

但是,当你将它上传到基类,特别是interface的时候,就会发现,内部类还是有它自己的特性的。(实际上,将对象上传给它所实现的接口与将它上传给基类是完全相同。)这样,任何人都不能看到或者访问到内部类了——也就是interface的实现了,于是隐藏实现就变得轻而易举了。你所得到的,只是一个基类或interface的reference。

Closure与回调

Closure是一种能调用的对象,它记录了创建它的那个作用域信息。读过这段定义你就能看出,内部类就是一种面向对象的closure,因为它不仅保存了宿主类的所有信息(创建它作用域),而且还自动保存指向那个宿主类对象的reference,更何况它还有权操控这个对象的所有成员,即使它们是private的。

在要求Java提供指针机制的众多议论中,最具有吸引力一条就是,要让它呢能进行回调。有了回调,你就能给别的对象一段信息,然后在未来某个时点,让它反过来调用发起方对象了。但是,如果是用指针来实现回调,那你就只能指望程序员了,希望它们不要误用指针。正如你所看到的,Java在这个问题上更为谨慎,因此它没有指针,内部类所提供的closure是一个完美的解决方案——它比指针更灵活,也更安全。

加在异常上面的限制

覆写方法的时候,你只能抛出这个方法在基类中的版本所声明的异常。这是一个有用的限制,因为同基类打交道的代码能自动地同它的派生类,包括它抛出地异常打交道。异常方面的限制对构造函数不起作用。你可以从stormyInning看出,派生类的构造函数根本不看基类抛出的是什么类型的异常,它只会根据它自己的需要抛出异常。然而,由于派生类的构造函数总是会以这样或那样的形式调用基类的构造函数(这里调用的是默认的构造函数),派生类的构造函数必须在异常说明中声明,它有可能会抛出其基类构造函数所抛出的异常。注意,派生类的构造函数不能捕获任何由基类构造函数抛出的异常。

内部类与控制框架

内部类还有一种更实际的用法,我把它称为“控制框架”,应用程序框架是一个或一组为解决某种特定类型的问题而设计的类。如果向使用应用程序框架,通常情况下只要继承其中的一个或多个类,再覆写某些方法就可以了。应用程序框架为你提供了一套解决问题的通用方案,而你只要覆写方法就可以根据你的特殊要求定制这个方案了(这就是一种模板方法的设计)。

控制框架是应用程序框架中的一种,主要用于响应事件。如果系统的首要任务就是对事件做出响应,那么它就被称为事件驱动系统。图形用户界面(GUI)是创建应用程序时要解决的最棘手的问题之一,它差不多就是完全由时间驱动的。Java的swing类库就是一个控制框架。它通过频繁的使用内部类,非常潇洒的解决了GUI的难题。