Java编程思想:第一章:对象导论

第一章:对象导论

    我们之所以将自然界分解,组织成各种概念,并按其含义分类,主要是因为我们是整个口语交流社会共同遵守的协议的参与者,这个协定以语言的形式固定下来...除非赞成这个协定中规定的有关语言信息的组织和分类,否则我们根本无法交流。


抽象过程

    所为的编程语言都提供抽象机制。可以认为:人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。所谓的“类型”指“所抽象的是什么?”,比如汇编语言是对底层机器的轻微的抽象。接着出现的命令式语言就是汇编语言的抽象。

    面向对象方式通过向程序员提供标示问题空间中的元素的工具而进了一步。这种标示方式非常通用,使得程序员不会受限于任何特定类型的问题。我们将问题空间中的元素及其在解空间中的标示称为对象。这种思想的实质是:程序可以通过添加新类型的对象使自身适应于某种特定的问题,因此,当你在阅读描述解决方案的代码的同时,也是在阅读问题的描述。所以,OOP允许根据问题来描述问题,而不是根据运行解决方案计算机来描述问题。但是仍然与计算机有关联:每个对象都看起来有点像一台微型计算机:具有状态,还有操作,用户可以要求对象执行这些操作。

    面向对象的5个基本特性:

    • 万物皆为对象,将对象视为奇特的变量,它可以存储数据,还可以要求它自身上执行一些操作。理论上讲,你可以抽取待求解问题的任何概念化结构(狗)。将其表示为程序中的对象。

    • 程序是对象的集合,它们通过发送消息来告知彼此所要做的。

    • 每个对象都有自己的由其他对象所构成的存储。可以通过创建包含现有对象的包的方式来创建新类型的对象。

    • 每个对象都拥有其类型。每个对象都是某个类的实例,类就是类型的同义词。

    • 某一个特定类型的所有对象都可以接收相同的消息。比如子类能处理的消息,父类也能处理,面向接口编程的概念。

    对象具有状态,行为和标识。意味着每一个对象都可以拥有内部数据,方法,并且每一个对象都可以唯一的与其他对象区分开来,具体来说,每一个对象在内存中有唯一的的地址。

每一个对象都有一个接口

    在程序执行期间具有不同的状态而其他方面都相似的对象会被分组到对象的类中,这就是关键字Class的由来。创建抽象数据类型(类)是面向对象程序设计的基本概念之一。抽象数据类型的运行方式与内置(built-in)类型几乎完全一致。你可以创建某一类型的变量,然后操作这些变量。每个类的成员或元素都具备某种共性。同时,每一个成员都有自己的状态,每一个对象都属于定义了特性和行为的某个特定的类。

    面向对象用class来表示数据类型。因为类描述了具有相同特性(数据元素)和行为(功能)的对象集合,所以一个类实际上就是一个数据类型。

    一担类被建立,就可以随心所欲的创建类的任意个对象,然后去操作它们。事实上,面向对象语言中的挑战之一,就是在问题空间元素和解空间的对象之间一对一的映射。每个对象只能满足某些请求,这些请求由对象的接口(interface)所定义,决定接口的便是类型。接口确定了对某一特定对象所能发出的请求。但是,在程序中必须满足这些请求的代码,这些代码月隐藏的数据在一起构成了实现。通常为概括为,向某个对象发送请求,此对象便知道次请求的目的,然后执行对应的程序代码。


每个对象都提供服务

    对象就是服务提供者。提供的服务尽量单一职责哦。


被隐藏的具体实现

    将程序开发人员按角色分:

    • 类创建者

    • 客户端程序员

    类中某些部分需要隐藏,这样的隐藏部分是程序中脆弱的部分,这样类创建者可以随意的修改这些隐藏的部分。也避免客户端程序员直接调用这类隐藏部分,这对客户端的bug也大大的减少。

    在任何的交互环境中,就有关系所涉及的各方多遵守的边界是十分重要的。当创建一个类库时,就建立了与客户端程序之间的关系,他们同样也是程序员,但是他们是使用你的类库来构建应用,或者构建更大的类库的程序员。如果所有的类成员都任何人都可用的,那么客户端程序员对类库做任何事情,而不受任何约束。

    因此,访问控制存在原因是:

    • 让客户端程序员无法接触及他们不应该接触的部分:这些部分对数据类型的内部操作来说必须的,但并不是用户解决特定问题所需要的接口的一部分。这对客户端程序员来说是一种服务,因为他们很容易的看出那些东西对他们来说很重要,而那些东西可以忽略。

    • 允许库设计者可以改变类内部的工作方式而不用担心影响到客户端程序员。


复用具体实现

    产生一个可复用的对象需要丰富的经验和观察力的,但是你一旦有了这样的设计,它就可以复用。代码复用时面向对象提供的最了不起的优点之一。

    最简单的复用某个类的方式就是直接使用该类的一个对象,因此也而已将那个类的一个对象置于某个新的类中。比如:创建一个成员对象。使用现有的类合成新的类称为:组合(composition)。如果组合是动态发生的,那么它通常称为聚合(aggregation)。组合通常被视为拥有(has-is)关系,比如:汽车拥有引擎。

    组合带来极大的灵活性。新类的成员对象通常都被声明为private,使得使用新类的客户端程序员不能访问他们。开发时优先考虑组合,而不是继承。


继承

    现有的类为基础,复制它,然后通过添加和修改这个副本来创建新类就称为:继承。当源类发生改变时,被修改的副本(子类)也会反映出这些变动。

    类型不仅仅与描述了作用于一个对象集合上的约束条件,同时还有与其他类型之间的关系。两个类型可以有相同特性和行为,但是其中一个特性可能比另一个含有更多的特性,而且可以处理更多的消息。继承使用基类型和子类型的概念表示了这种类型之间的相似性。一个基类型包含其所有子类型所共享的特性和行为。可以创建基类型来表示系统中某些对象的核心概念,子类型来表示实现的各种不同方式。

    当继承现有的类型时,也就是创造了新的类型。这个新的类型不仅包含现有类型的所有成员,而且更重要的就是它复制了基类的接口。也就是说,所以可以发给基类对象的消息同时也可以发送给子类对象。由于通过发送给类的消息的类型可知类的类型,意味着子类与基类具有相同的类型。改变基类的方法的行为称:覆盖。


是“一个”和“像一个”的关系

    子类对象来替代基类对象,称为:纯碎替代。通常称为替代原则。在某种意义上,这是处理继承的理想方法。我们经常将这种情况下的基类与子类的关系称为:is-a是一个关系。判断是否继承,就是要确定是否可以使用is-a来描述类之间的关系。

    有些子类需要扩展基类,这个新的类型也可以替代基类,但是这种替代并不完美,因为基类无法访问子类的新的方法。这种称为像一个(is-liek-a)关系。子类有新的方法,所以说子类和基类不是完全相同。

    当你看到替代原则时,很容易会认为纯碎替代是唯一可行的方式,而且事实上,用这种方式设计是很好的。但是你会时常发现,同样显然的是你必须在子类的接口中添加新方法,两种方法的使用场景应该是相当明显的。


伴随多态的可互换对象

    在处理类型的层次结构时,经常想把一个对象不当做它所属的特定类型来对待,而是将其当做其基类的对象来对待。使得可以写出不依赖于特定类型的代码。这样的代码是不会受添加新类型影响的,而且添加新类型是扩展一个面向对象程序一遍处理新情况的最常用方式。

    但是,子类对象看待基类对象时,仍然存在一些问题。编译器在编译时不可能知道执行那一个代码的。这就是关键所在,当发送这样的消息时,程序员并不想知道那一段代码被执行,如果不需要知道那一段代码被执行,那么添加新类型时,不需要改变调用它的方法,它就能运行不同的代码。

    编译器不可能产生传统意义上的函数调用,在OOP中,程序运行时才能够确定代码的地址。所以,当消息发送到一个泛化的对象时,必须采用其他的机制。这既是后期绑定。Java使用一小段特殊的代码来替代绝对地址调用。这段代码使用在对象中存储信息来计算方法体的地址。这样,根据这一段代码的内容,每一个对象都可以具有不同的行为表现。当向一个对象发送消息时,该对象就能够知道这条消息应该做些什么。

    将子类看做是它的基类的过程称为:向上转型。


单根继承结构

    Java中所有的类最终都继承自单一的基类Object,如下好处:

    • 所有的对象都具有一个公共的接口,所以它们归根到底都是相同的基类类型。

    • 单根继承结构保证所有对象都具有某些功能。

    • 单根继承让垃圾回收期的实现容易的多了。因为所有的对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵局。


容器

    通常来说,如果不知道解决某个问题需要多少个对象时,或者它们将存活多久,那么就不可能知道如何存储这些对象。如何才能知道多少个空间来创建这些对象?答案是你不可能知道,因为这些信息运行时才能确定的。

    容器,比如List,Map,Set等。

    从设计的角度来看呢,真正需要的只是一个可以被操作,从而解决问题的序列。需要选择容器,原因如下:

    • 不同的容器提供了不同类型的接口,与外部行为。

    • 不同的容器,对某些不用类型的操作具有不同的效率。


参数化类型

    以前用Object来表示通用类型,未知类型,但是向上转换时失去类型信息,需要获取时强制类型转换。泛型,就是向下转换,一般向上转换是安全的。除非明确知道所要处理的对象的类型,否则向下转换是不安全的。比如某个list放入一个元素,取出来时必须知道类型,做个强制类型转换。否则抛出异常。

    向下转型和运行期检查需要额外的运行时间,也需要程序员提供更多的心血,那么创建这样的容器,它知道自己所保存的对象的类型,从而不需要向下转型以及消除犯错误的可能,这就是泛型机制。


对象的创建和生命期

    在使用对象时,最关键的问题之一便是它们的生成与销毁方式。每个对象需要生存都需要资源,尤其是内存。当我们不在需要一个对象时,它必须被清理掉,便其占用的资源被释放和重用。

    怎样才能知道何时销毁这些对象?当处理完某个对象后,系统某个其他部分可能还在处理它:

    • C++一样,编写程序时确定。给程序员提供了选择权。

    • 堆在内存池中动态的创建,直到运行时才确定需要多少个对象,它们声明周期如何,以及它们的具体类型是什么。

    动态方式有这样一般性的逻辑假设:对象趋向于变的复杂,所以查找和释放存储空间的开销不会对对象的创建造成重大冲击。Java采用的就是动态内存分配方式。当需要创建对象时,就要用new关键字来构建此对象的动态实例。

    对象的生命周期,垃圾回收期的工作了。Java的垃圾回收器用来解决内存的释放问题。它知道对象何时不再被使用,并自动释放对象占用的内存。结合了如下两种特性:

    • 继承与单一基类Object。

    • 只能只用方式创建对象(在堆上创建)。


异常处理,处理错误

    异常处理将错误处理直接置于编程语言中,有时甚至置于操作系统中。Java引入了异常处理,而且强制你必须使用它。它是唯一可接受的错误报告方式。如果编写错误的异常处理代码,就编译时就有出错信息。

    异常处理不是面向对象的特征。


并发编程

    Java的并发内置于语言中。


Java与Internet

  • 客户端,服务端编程

  • 浏览器,服务端编程


猜你喜欢

转载自blog.51cto.com/u2r2otkit/2328296