面向对象的编程---java的概述

1>每个对象都有一个接口
接口里面只有方法声明,没有方法定义,即:只有方法签名,无方法体。
在软件构造的规范里面,接口里面必须写函数规约(用来生成java文档),接口实际上就是一个完全抽象类。接口定义了规范,意思就是说所有实现这个接口的类都必须满足一定的条件(实现接口里面的方法),这些类都必须长成我这个接口的样子。接口提供了一个统一的规范。
2>每个对象都提供服务
当正在试图开发或理解一个程序设计时,最好的方法之一就是将对象想象为一个"服务提供者"。程序本身向用户提供服务,它将通过调用其他对象提供的服务来实现这一目的。你的目标就是创建(最好是在现有代码库里面找)能够提供服务来解决问题的一系列对象。
这种视角另一个好处就是:它有助于提高对象的内聚性。高内聚是软件设计的基本质量要求之一:这意味着一个软件构件的各个方面组合得很好。
3>被隐藏的具体实现
将程序开发人员按照角色分为类创建者和客户端程序员是大有裨益的。前者构建类,后者收集并使用类来构建一个应用。类创建者构建的类只向客户端程序员暴露必须的部分,而隐藏其他的部分。为什么要这样呢?因为如果加以隐藏,那么客户端程序员将不能够访问它,这意味着类创建者可以任意修改被隐藏的部分,而不用担心对其他任何人造成影响。被隐藏的部分通常代表对象内部脆弱的部分,它们很容易被粗心的或不知内情的客户端程序员所毁坏,因此将实现隐藏起来可以减少程序bug。
访问控制的第一个存在原因就是让客户端程序员无法触及他们不应该触及的部分一一这些部分对数据类型的内部操作来说是必需的,但并不是用户解决特定问题所需的接口的一部分。这对客户端程序员来说其实是一项服务,因为他们可以很容易地看出哪些东西对他们来说很重要,而哪些东西可以忽略。
访问控制的第二个存在原因就是允许库设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员。例如,你可能为了减轻开发任务而以某种简单的方式实现了某个特定类,但稍后发现你必须改写它才能使其运行得更快。如果接口和实现可以清晰地分离并得以保护,那么你就可以轻而易举地完成这项工作。
4>复用具体实现
一旦类被创建并被测试完,那么它就应该(在理想情况下)代表一个有用的代码单元。事实证明,这种复用性并不容易达到我们所希望的那种程度,产生一个可复用的对象设计需要丰富的经验和敏锐的洞察力。但是一旦你有了这样的设计,它就可供复用。代码复用是面向对象程序设计语言所提供的最了不起的优点之一
最简单地复用某个类的方式就是直接使用该类的一个对象,此外也可以将那个类的一个对象置于某个新的类中。我们称其为“创建一个成员对象”。新的类可以由任意数量、任意类型的其他对象以任意可以实现新的类中想要的功能的方式所组成。因为是在使用现有的类合成新的类,所以这种概念被称为组合(composition),如果组合是动态发生的,那么它通常被称为聚合(aggregation)o组合经常被视为"has-a”(拥有)关系,就像我们常说的“汽车拥有引擎"
组合带来了极大的灵活性。新类的成员对象通常都被声明为private,使得使用新类的客户端程序员不能访问它们。这也使得你可以在不干扰现有客户端代码的情况下,修改这些成员。也可以在运行时修改这些成员对象,以实现动态修改程序的行为。下面将要讨论的继承并不具备这样的灵活性,因为编译器必须对通过继承而创建的类施加编译时的限制。
由于继承在面向对象程序设计中如此重要,所以它经常被高度强调,于是程序员新手就会有这样的印象:处处都应该使用继承。这会导致难以使用并过分复杂的设计。实际上,在建立新类时,应该首先考虑组合,因为它更加简单灵活。如果采用这种方式,设计会变得更加清晰。一旦有了一些经验之后,便能够看出必须使用继承的场合了。
5>继承
对象这种观念,本身就是十分方便的工具,使得你可以通过概念将数据和功能封装到一起,因此可以对问题空间的观念给出恰当的表示,而不用受制于须使用底层机器语言.
继承通过关键字extends来实现,子类继承自父类。子类具有父类的所有公共接口,并且子类可以增加一些自己的额外接口,且可以覆盖父类的方法。可以用父类的引用去指向一个子类的实例,这称作向上转型。根据LISP原则,父类能够实现的操作,子类都应该能实现,且父类满足的规约,子类也必须满足。
继承使得面向对象的编程更加高效,方便,也有一个更加清晰的层次结构。
6>伴随多态的可互换对象
在处理类型的层次结构时,经常想把一个对象不当作它所属的特定类型来对待,而是将其当作其基类的对象来对待。这使得人们可以编写出不依賴于特定类型的代码。在“几何形"的例子中,方法操作的都是泛化(generic)的形状,而不关心它们是圆形、正方形、三角形还是 其他什么尚未定义的状。所有的几何形状都可以被绘制、擦除和移动,所以这些方法都是直接对一个儿何形对象发送消息;它们不用担心对象将如何处理消息。
这样的代码是不会受添加新类型影响的,而且添加新类型是扩展一个面向对象程序以便处理新情况的最常用方式。例如,可以从“几何形”中导出一个新的子类型“五角形”,而并不需要修改处理泛化几何形状的方法。通过导出新的子类型而轻松扩展设计的能力是对改动进行封装的基本方式之一。这种能力可以极大地改善我们的设计,同时也降低软件维护的代价。
面向对象程序设计语言使用了后期绑定的概念。当向对象发送消息时, 被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查〈无法提供此类保证的语言被称为是弱类型的),但是并不知道将被执行的确切代码。
正是因为多态才使得事情总是能够被正确处理。编译器和运行系统会处理相关的细节,你需要马上知道的只是事情会发生,更重要的是怎样通过它来设计。当向一个对象发送消息时,即使涉及向上转型,该对象也知道要执行什么样的正确行为。
7>单根继承结构
在OOP中,自c++面世以来就已变得非常瞩目的一个问题就是,是否所有的类最终都继承自单一的基类。在Java中〈事实上还包括除C++以外的所有OOP语言),答案是yes,这个终极基类的名字就是Object。事实证明,单根继承结构带来了很多好处。
单根继承结构保证所有对象都具备某些功能。因此你知道,在你的系统中你可以在每个对象上执行某些基本操作。所有对象都可以很容易地在堆上创建,而参数传递也得到了极大的简化。
单根继承结构使垃圾回收器的实现变得容易得多,而垃圾回收器正是Java相对C++的重要改进之一。由于所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷人僵局。这对于系统级操作(如异常处理)显得尤其重要,并且给编程带来了更大的灵活性。
8>容器
通常说来,如果不知道在解决某个特定问题时需要多少个对象,或者它们将存活多久,那么就不可能知道如何存储这些对象。如何才能知道需要多少空间来创建这些对象呢?答案是你不可能知道,因为这类信息只有在运行时才能获得。
对于面向对象设计中的大多数问题而言,这个问题的解决方案似乎过于轻率:创建另一种对象类型。这种新的对象类型持有对其他对象的引用。当然,你可以用在大多数语言中都有的数组类型来实现相同的功能。但是这个通常被称为容器的新对象,在任何需要时都可扩充自己以容纳你置于其中的所有东西。因此不需要知道将来会把多少个对象置于容器中,只需要创建一个容器对象,然后让它处理所有细节。
从设计的观点来看,真正需要的只是一个可以被操作,从而解决问题的序列。如果单一类型的容器可以满足所有需要,那么就没有理由设计不同种类的序列了。然而还是需要对容器有所选择,这有两个原因。第一,不同容器提供了不同类型的接口和外部行为。堆栈相比于队列就具备不同的接口和行为,也不同于集合和列表的接口和行为。它们之中的某种容器提供的解决方案可能比其他容器要灵活得多。第二,不同的容器对于某些操作具有不同的效率。最好的例子就是两种List的比较:ArrayList和LinkedList。它们都是具有相同接口和外部行为,但是它们对某些操作所花费的代价却有天壤之别。在ArrayList中,随机访问元素是一个花费固定时间的操作;但是,对LinkedList来说,随机选取元素需要在列表中移动,这种代价是高昂的,访问越靠近表尾的元素,花费的时间越长。而另一方面,如果想在序列中间插人一个元素,LinkedList的开销却比ArrayList要小。上述操作以及其他操作的效率,依序列底层结构的不同而存在很大的差异。我们可以在一开始使用LinkedList构建程序,而在优化系统性能时改用 ArrayList接口List所带来的抽象,把在容器之间进行转换时对代码产生的影响降到最小限度。
容器也可以通过泛型的技术指明容器里面的对象引用的类型,如List表明该容器里面均为整型。
9> 对象的创建和生命期
在使用对象时,最关键的问题之一便是它们的生成和销毁方式。每个对象为了生存都需要资源,尤其是内存。当我们不再需要一个对象时,它必须被清理掉,使其占有的资源可以被释放和重用。在相对简单的编程情况下,怎样清理对象看起来似乎不是什么挑战:你创建了对象,根据需要使用它,然后它应该被销毁。然而,你很可能会遇到相对复杂的情况。
对象的数据位于何处?怎样控制对象的生命周期?c+ +认为效率控制是最重要的议题,所以给程序员提供了选择的权力。为了追求最大的执行速度,对象的存储空间和生命周期可以在编写程序时确定,这可以通过将对象置于堆栈(它们有时被称为自动变量(automatic variable) 或限域变量(scopedvariable))或静态存储区域内来实现。这种方式将存储空间分配和释放置于优先考虑的位置,某些情况下这样控制非常有价值。但是,也牺牲了灵活性,因为老须在编写程序时知道对象确切的数量、生命周期和类型。如果试图解决更一般化的问题,例如计算机辅助设计、仓库管理或者空中交通控制,这种方式就显得过于受限了。
第二种方式是在被称为堆(heap)的内存池中动态地创建对象。在这种方式中,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。这些问题的答案只能在程序运行时相关代码被执行到的那一刻才能确定。如果需要一个新对象,可以在需要的时刻直接在堆中创建。因为存储空间是在运行时被动态管理的,所以需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间。在堆栈中创建存储空间和释敖存储空间通常各需要一条汇编指令即可,分别对应将栈顶指针向下移动和将栈顶指针向上移动。
创建堆存储空间的时间依赖于存储机制的设计。
动态方式有这样一个一般性的逻辑假设:对象趋向于变得复杂,所以查找和释放存储空间的开销不会对对象的创建造成重大冲击。动态方式所带来的更大的灵活性正是解决一般化编程问题的要点所在。
Java完全采用了动态内存分配方式。每当想要创建新对象时,就要使用new关键字来构建此对象的动态实例。
还有一个议题,就是对象生命周期。对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。然而,如果是在堆上创建对象,编译器就会对它的生命周期一无所知。在像C++这样的语言中,必须通过编程方式来确定何时销毁对象,这可能会因为不能正确处理而导致内存泄漏(这在c++程序中是常见的问题) Java提供了被称为“垃圾回收器"的机制,它可以自动发现对象何时不再被使用,并继而销毁它。垃圾回收器非常有用因为它减少了所必须考虑的议题和必须编写的代码。更重要的是,垃圾回收器提供了更高层的保护,可以避免暗藏的内存泄漏问题,这个问题已经使许多c++项目折戟沉沙。
Java的垃圾回收器被设计用来处理内存释放问题(尽管它不包括清理对象的其他方面)。垃圾回收器“知道"对象何时不再被使用,并自动释放对象占用的内存。这一点同所有对象都是继承自单根基类Object以及只能以一种方式创建对象(在堆上创建)这两个特性结合起来,使得使用Java编程的过程较之用C++编程要简单得多,所要做出的决策和要克服的障碍也要少得多。
10>异常处理.处理错误
自从编程语言问世以来,错误处理就始终是最困难的问题之一。因为设计一个良好的错误处理机制非常困难,所以许多语言直接略去这个问题,将其交给程序库设计者处理,而这些设计者也只是提出了一些不彻底的方法,这些方法可用于许多很容易就可以绕过此问题的场合,而且其解决方式通常也只是忽略此问题。大多数错误处理机制的主要问题在于,它们都依赖于程序员自身的警惕性,这种警惕性来源于一种共同的约定,而不是编程语言所强制的。如果程序员不够警惕一一一通常是因为他们太忙,这些机制就很容易被忽视。
异常处理将错误处理直接置于编程语言中,有时甚至置于操作系统中。异常是一种对象,它从出错地点被“抛出",并被专门设计用来处理特定类型错误的相应的异常处理器“捕获"。异常处理就像是与程序正常执行路径并行的、在错误发生时执行的另一条路径。因为它是另一条完全分离的执行路径,所以它不会于扰正常的执行代码。这往往使得代码编写变得简单,因为不需要被迫定期检查错误。此外,被抛出的异常不像方法返回的错误值和方法设置的用来表示错误条件的标志位那样可以被忽略。异常不能被忽略,所以它保证一定会在某处得到处理。最后需要指出的是:异常提供了一种从错误状况进行可靠恢复的途径。现在不再是只能退出程序,你可以经常进行校正,并恢复程序的执行,这些都有助于编写出更健壮的程序。
Java的异常处理在众多的编程语言中格外引人注目,因为Java一开始就内置了异常处理,而且强制你必须使用它。它是唯一可接受的错误报告方式。如果没有编写正确的处理异常的代码,那么就会得到一条编译时的出错消息。这种有保障的一致性有时会使得错误处理非常容易。值得注意的是,异常处理不是面向对象的特征一一尽管在面向对象语言中异常常被表示成为一个对象。异常处理在面向对象语言出现之前就已经存在了。
11>并发编程
在计算机编程中有一个基本概念,就是在同一时刻处理多个任务的思想。许多程序设计问题都要求,程序能够停下正在做的工作,转而处理某个其他问题,然后再返回主进程。有许多方法可以实现这个目的。最初,程序员们用所掌握的有关机器底层的知识来编写中断服务程序,主进程的挂起是通过硬件中断来触发的。尽管这么做可以解决问题,但是其难度太大,而且不能移植,所以使得将程序移植到新型号的机器上时,既费时又费力。
有时中断对于处理时间性强的任务是必需的,但是对于大量的其他问题,我们只是想把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称之为线程,上述厩念被称为“并发"。并发最常见的例子就是用户界面。通过使用任务,用户可以在揿下按钮后快速得到一个响应,而不用被迫等待到程序完成当前任务为止。
通常,线程只是一种为单一处理器分配执行时间的手段。但是如果操作系统支持多处理器,那么每个任务都可以被指派给不同的处理器,并且它们是在真正地并行执行。在语言级别上,多线程所带来的便利之一便是程序员不用再操心机器上是有多个处理器还是只有一个处理器。由于程序在逻辑上被分为线程,所以如果机器拥有多个处理器,那么程序不需要特殊调整也能执行得更快。
所有这些都使得并发看起来相当简单,但是有一个隐患:共享资源。如果有多个并行任务都要访问同一项资源,那么就会出问题。例如,两个进程不能同时向一台打印机发送信息。为了解决这个问题,可以共享的资源,例如打印机,必须在使用期间被锁定。因此,整个过程是:某个任务锁定某项资源,完成其任务,然后释放资源锁,使其他任务可以使用这项资源。
java的并发是内置于语言中的,JavaSE5已经增添了大量额外的库支持。
12>Java与Internet
如果Java仅仅只是众多的程序设计语言中的一种,你可能就会问:为什么它如此重要?为什么它促使计算机编程语言向前迈进了革命性的一步?如果从传统的程序设计观点看,问题的答案似乎不太明显。尽管Java对于解决传统的单机程序设计问题非常有用,但同样重要的是,它解决了在万维网(WWW)上的程序设计问题。
客户/服务器系统的核心思想是:系统具有一个中央信息存储池(central repository of information),用来存储某种数据,它通常存在于数据库中,你可以根据需要将它分发给某些人员或机器集群。客户/服务器概念的关键在于信息存储池的位置集中于中央,这使得它可以被修改,并且这些修改将被传播给信息消费者。总之,信息存储池、用于分发信息的软件以及信息与软件所驻留的机器或机群被总称为服务器。驻留在用户机器上的软件与服务器进行通信,以获取信息、处理信息,然后将它们显示在被称为客户机的用户机器上。
客户/服务器计算技术的基本概念并不复杂。问题在于你只有单一的服务器,却要同时为多个客户服务。通常,这会涉及数据库管理系统,因此设计者把数据“均衡”分布于数据表中,以取得最优的使用效果。此外,系统通常允许客户在服务器中插人新的信息。这意味着必须保证一个客户插人的新数据不会糧盖另一个客户插人的新数据,也不会在将其添加到数据库的过程中丢失(这被称为事务处理)如果客户端软件发生变化,那么它必须被重新编译、调试并安装到客户端机器上,事实证明这比想像的要更加复杂与费力。如果想支持多种不同类型的计算机和操作系统,问题将更麻烦。最后还有一个最重要的性能问题:可能在任意时刻都有成百上千的客户向服务器发出请求,所以任何小的延迟都会产生重大影响。为了将延迟最小化,程序员努力减轻处理任务的负载,通常是分散给客户端机器处理,但有时也会使用所谓的中间件将负载分散给在服务器端的其他机器。(中间件也被用来提高可维护性)
分发信息这个简单思想的复杂性实际上是有很多不同层次的,这使得整个问题可能看起来高深莫测。但是它仍然至关重要:算起来客户/服务器计算技术大概占了所有程序设计行为的一半,从制定订单、信用卡交易到包括股禀市场、科学计算、政府、个人在内的任意类型的数据分发。过去我们所做的,都是针对某个问题发明一个单独的解决方案,所以每一次都要发明一个新的方案。这些方案难以开发且难以使用,而且用户对每一个方案都要学习新的接口。因此,整个客户/服务器问题需要彻底解决。
Web实际上就是一个巨型客户/服务器系统,但稍微差一点,因为所有的服务器和客户机都同时共存于同一个网络中。你不需要了解这些,因为你所要关心的只是在某一时刻怎样连接到一台服务器上,并与之进行交互(即便你可能要满世界地查找你想要的服务器)。
服务器端编程是Java已经取得巨大成功的因素之一。当提出对服务器的请求后,会发生什么呢?大部分时间,请求只是要求“给我发送一个文件",之后浏览器会以某种适当的形式解释这个文件,例如将其作为HTML页面、图片、Java applet或脚本程序等来解释。
更复杂的对服务器的请求通常涉及数据库事务。常见的情形是复杂的数据库搜索请求,然后服务器将结果进行格式编排,使其成为一个HTML页面发回给客户端。(当然,如果客户端通过Java或脚本程序具备了更多的嚮能,那么服务器可以将原始的数据发回,然后在客户端进行格式编排,这样会更快,而且服务器的负载将更小。)另一种常见情形是,当你要加人一个团体或下订单时,可能想在数据库中注册自己的名字,这将涉及对数据库的修改。这些数据库请求 须通过服务器端的某些代码来处理,这就是所谓的服务器端编程。过去,服务器端编程都是通过使用Perl、Python、C++或其他某种语言编写CGI程序而实现的,但却造成了从此之后更加复杂的系统。其中就包括基于Java的Web服务器,它让你用Java编写被称为se et的程序来实现服务器端编程。servlet及其衍生物JSP,是许多开发网站的公司迁移到Java上的两个主要的原因,尤其是因为它们消除了处理具有不同能力的浏览器时所遇到的问题。
13>总结
你知道过程型语言看起来像什么样子:数据定义和函数调用。想了解此类程序的含义,你必须忙上一阵,需要通读函数调用和低层概念,以在脑海里建立一个模型。这正是我们在设计过程式程序时,需要中间表示形式的原因。这些程序总是容易把人搞糊涂,因为它们使用的表示术语更加面向计算机而不是你要解决的问题。
因为OOP在你能够在过程型语言中找到的概念的基础上,又添加了许多新概念,所以你可能会很自然地假设:由此而产生的Java程序比等价的过程型程序要复杂得多。但是,你会感到很惊喜:编写良好的Java程序通常比过程型程序要简单得多,而且也易于理解得多。你看到的只是有关下面两部分内容的定义:用来表示问题空间概念的对象(而不是有关计算机表示方式的相关内容),以及发送给这些对象的用来表示在此空间内的行为的消息。面向对象程序设计带给人们的喜悦之一就是:对于设计良好的程序,通过阅读它就可以很容易地理解其代码。通常,其代码也会少很多,因为许多问题都可以通过重用现有的类库代码而得到解决。
00P和Java也许并不适合所有的人。重要的是要正确评估自己的需求,并决定Java是否能够最好地满足这些需求,还是使用其他编程系统(包括你当前正在使用的)才是更好的选择。如果知道自己的需求在可预见的未来会变得非常特殊化,并且Java可能不能满足你的具体限制,那么就应该去考察其他的选择。即使最终仍旧选择Java作为编程语言,至少也要理解还有哪些选项可供选择,并且对为什么选择这个方向要有清楚的认识。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值