java编程思想学习日志——对象导论

本章介绍背景性和补充性的材料,帮助理解面向对象程序设计(Object-oriented Programming,OOP),了解对象的重要性,使用对象进行设计。

1. 对象

所有编程语言都提供抽象机制,人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。所谓的“类型”是指“所抽象的是什么”。程序员必须建立起在机器模型和实际待解问题的模型之间的关联(感觉这就是作为架构师的工作了,一个好的架构师可以让软件更简单明晰,而且对团队的开发效率有非常大的提升,其主要原因就在打好了一个“基础”)。还有其他针对待解问题、专门通过对图形符号操作来实现编程的语言,但都有针对性,超出特定领域就达不到较好的效果。

面向对象思想的实质是:程序可以通过添加新类型的对象使自身适用于某个特定问题。因此,在阅读解决问题的代码同时也是在阅读问题的表述。

Java基于的语言之一:smalltalk使其拥有的五个特性(也是面向对象语言的五个基础特性):
1) 万物皆为对象
2) 程序是对象的集合,它们通过发送消息来告诉彼此所要做的。(可以把消息想象为对某个特定对象的方法的调用请求)
3) 每个对象都有自己的由其它对象所构成的存储。(个人理解为基类的用法)
4) 每个对象都拥有其类型(class)。(每个类型最重要的区别于其他类的特性就是“可以发送什么样的消息给它”)
5) 某一特定类型的所有对象都可以接收同样的消息。相同类型间的可替代性(substitutability)是OOP中最强有力的概念之一。

Booch对对象提出了个更简洁的描述:对象具有状态行为标识。(指对象的内部数据和方法?)具体地说就是每个对象在内存当中都有一个唯一的地址。

2.类(class)

创建抽象数据类型(类)是面向程序设计的基本概念之一。抽象数据类型的运行方式与内置(built-in)类型几乎完全一致:你可以创建某一类型的变量,然后操作这些变量。每个对象都有某种共性,同时都有其自身的状态,每个对象都属于定义了特性和行为的某个特定的类。

面向对象技术的应用可以将大量的问题很容易的降解为一个简单的解决方案,面向对象程序设计的挑战之一,就是在问题空间的元素和解空间的对象之间创建一对一的映射。

每个对象都只能满足某些请求,这些请求由对象的接口(interface)所定义,决定接口的是类型。接口确定了对某一特定对象所能发出的请求,但是在程序中必须有满足这些请求的代码。这些代码与隐藏的数据一起构成了实现。当正在试图开发或理解一个程序设计时,最好的方法之一就是将对象想象为“服务提供者”。程序本身将向用户提供服务,它将通过调用其他对象提供的服务实现这一目的。你的目标就是去创建能够提供理想的服务来解决问题的一系列对象。

将对象看做是服务提供者还有一个附带的好处:有助于提高对象的内聚性。高内聚是软件设计的基本质量要求之一(创建更多准确的对象,每个对象拥有其更明晰专项的功能)。每个对象都可以很好地完成一项任务,但是他并不试图做更多的事。这种做法也对二次开发的人十分友好,会使调整对象以适应其设计的过程变得简单很多。

开发程序时分工为类创建者和客户端程序员是大有裨益的。后者收集各种用来实现快速应用开发的类。类创建者的目标是构建类,这种类只向客户端程序员暴露必须的部分,隐藏对象内部脆弱的部分而不用担心其对他人产生影响。这就是访问控制,其第一个存在原因就是让客户端程序员无法触及他们不应该触及的部分,第二个是允许库设计者可以改变类内部的工作方式,而不用担心会影响到客户端程序员(你可能为了减轻开发任务而以某种简单的方式实现了某个特定类,而后发现你必须改写它才能使其运行得更快)。

Java用三个关键字在类的内部设定边界:
1、public对任何人可用
2、private除类型创建者和类型的内部方法之外的任何人都不能访问
3、protected与private作用相当,差别仅在于继承的类可以访问protected成员

Java还有一种默认的访问权限,当没有使用前面提到的任何访问指定词时,它将发挥作用。这种权限被称为包访问权限,在这种权限下,类可以访问在同一个包中的其他类的成员,但是在包之外,这些成员如同指定了private一样。

3.组合与继承

一旦类被创建并被测试结束,他就代表一个可以被复用的有用的代码单元。最简单的复用方式就是直接使用该类的一个对象,也可以将那个类的一个对象置于某个新的类中。我们称其为“创建一个成员对象”。将现有的类合成新的类被称为组合(composition),如果组合是动态发生的,那么通常被称为聚合(aggregation)。

组合带来了极大的灵活性。新类的成员对象通常都被声明为private。使得使用新类的客户端程序员不能访问他们。这也使得你可以在不干扰现有客户端代码的情况下,修改这些成员。也可以在运行时修改这些成员对象,以实现动态修改程序的行为。

继承不具有组合的这种灵活性,因为编译器必须对通过继承而创建的类施加编译时的限制。使用继承过多会导致难以使用并过分复杂的设计。在建立新类时,应该首先考虑组合,因为它更加简单灵活

关于继承和组合的差别,在网上看到了一段很生动的比喻:
继承是说“我父亲在家里给我帮了很大的忙”。组合是说“我请了个老头在我家里干活”。组合是在一类类中引用另一个类。生成另一个类的实例。而继承只是继承了父类的变量和方法。区别:使用组合可以用到另一个类中私有的变量和方法,而继承就不可以用到父类的私有的变量和方法了。他们都有各自的好处,要灵活的运用。(摘自:http://bbs.csdn.net/topics/320116869

4.单根继承结构

在处理类型的层次结构时,经常把一个对象当作其基类的对象来对待,方法操作的都是泛化(generic)的对象,这些方法是直接对基类对象发送消息,不用担心对象如何处理消息。这样的代码不受添加新类型的影响,而且添加新类型是扩展一个面向对象程序以便处理新情况的最常用方式。通过导出新的子类型而轻松扩展设计的能力是对改动进行封装的基本方式之一。这种能力可以极大地改善我们的设计,同时也降低软件维护的代价

编译器不可能产生传统意义上的函数调用。一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然后再OOP中,程序直到运行时才能够确定代码的地址,所以当消息发送到一个泛化对象时,必须采用其他的机制。为了解决这个问题,面向对象程序设计语言使用了后期绑定的概念,党项对象发送消息是,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查(无法提供此类保证的语言被称为是弱类型的),但是并不知道将被执行的确切代码。

为了执行后期绑定,Java使用一小段特殊的代码来替代绝对地址调用。这段代码使用在对象中储存的信息来计算方法体的地址。这样,根据这一小段代码的内容,每一个对象都可以具有不同的行为表现。当向一个对象发送消息时,该对象就能够知道对这条消息应该做什么。(C++使用virtual实现,否则默认不是动态绑定的。而Java中动态绑定是默认行为,不需要添加额外的关键字来实现多态。)

把将导出类看做是它的基类的过程成为向上转型(upcasting)。转型(cast)这个名称的灵感来自于模型铸造的塑模动作;而向上(up)这个词源于继承图的典型布局方式:通常基类在顶部,而导出类在其下部散开。因此,转型为一个基类就是在继承图中的向上移动,即向上转型。一个面向对象程序肯定会在某处包含向上转型,因为这是将自己从必须知道确切类型中解放出来的关键。

在Java中,所有的类最终都继承自单一的基类。这个类就是Object。在单根继承结构中的所有对象都具有一个共用接口,所以他们归根到底都是相同的基本类型。单根继承结构保证所与对象都具备某些功能,在你的系统中你可以在每个对象上执行某些基本操作,所有对象都可以很容易的在堆上创建,而参数传递也得到了极大的简化。单根继承结构使垃圾回收器的实现变得容易很多,而垃圾回收器正是Java相对C++的重要改进之一。由于所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵局。这对系统级操作(如异常处理)显得尤其重要,并且给编程带来了更大的灵活性

C++所提供的结构无法确保所有对象都属于同一个基本类型。从向后兼容的角度看,这么做能够更好地适应C模型,而且受限较少,但是当要进行完全的面向对象程序设计时,则必须构建自己的继承体系,使得它可以提供其他OOP语言内置的便利。在所获得的任何新类库中,总会用到一些不兼容的接口,需要花力气(有可能要通过多重继承)来使新接口融入你的设计之中。这么做来换取C++额外的灵活性,如果在C上面投资巨大,这么做就很有价值;如果是刚刚从头开始,那么Java这类选择就会有更高的生产率

5.容器、参数化类型

在Java中,有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合,集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体Java(类库以不同的含义使用“集合”这个术语,所以这里使用“容器”这个词)。Java中具有满足不同需要的各种类型容器,List(用于储存序列),Map(也被称为关联数组,用来建立对象之间的关联),Set(每种对象类型只持有一个),以及诸如队列、树、堆栈等更多的构件。

关于拥有多种不同种类容器的原因:1、不同容器提供了不同类型的接口和外部行为,它们之中的某种容器提供的解决方案可能比其他容器要灵活得多。2、不同容器对于某些操作具有不同的效率,比如ArrayList和LinkedList。他们都是具有相同接口和外部行为的简单的序列,但是他们对某些操作所花费的代价却有天壤之别。在ArrayList中,随机访问元素是一个花费固定时间的操作,但是对LinkedList来说,随机选取元素需要在列表中移动,这种代价是高昂的,访问越靠近表尾的元素,花费的时间越长。而另一方面,如果想在序列中间插入一个元素,LinkedList的开销却比ArrayList要小。

单根继承结构意味着所有东西都是Object类型,所以可以存储Object的容器可以存储任何东西。这使得容器很容易被复用。但是当其它类型的对象向上转型为Object存储,再被取出使用时,就会失去其原来的“身份”,这时候就要用到向下转型。我们必须要确切的知道所要处理的对象的类型,否则这么做就是不安全的,如果转型为错误的类型就会得到异常。向下转型和运行时的检查需要额外的程序运行时间,此时创建专门的容器存储就会方便的多,这就是参数化类型机制。参数化类型就是一个编译器可以自动定制作用于特定类型上的类,在Java中被称为范型。一对尖括号,中间包含类型信息,通过这些特征就可以识别对范型的使用。例如:ArrayList<Shape> shapes = new ArrayList<shape>();

7.生命周期与异常处理

在使用对象解决问题时,对象的生命周期是关键议题,这涉及解决问题的效率。C++认为效率控制比较重要,为了追求最大执行速度,对象的存储空间可以在编写程序时确定(通过堆栈活静态存储区域)。这种方式优先考虑存储空间的分配与释放,但是牺牲了很大的灵活性,因为必须在编写程序时知道对象确切的数量、生命周期和类型。

第二种方式是在被称为堆(heap)的内存池中动态的创建对象。这种方式中直到运行时才会知道需要多少对象、生命周期以及其类型。存储空间是在运行时被动态管理的,所以需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间(这就是为什么C++更快,而Java更方便)。在堆栈中创建存储空间和释放存储空间通常各需要一条汇编语言指令,分别对应将栈顶指针向下移动和将栈顶指针向上移动。创建对存储空间的时间依赖于存储机制的设计(于是模型架构格外重要,好的数据模型会提高整个项目的效率)。

动态方式有这样一个一般性的逻辑假设:对象趋向于变得复杂,所以查找和释放存储空间的开销不会对对象的创建造成重大冲击。动态方式所带来的更大的灵活性正是解决一般化编程问题的要点所在。Java完全采用了动态内存分配方式。每当想要创建新对象时,就要使用new关键字来构建此对象的动态实例。

关于对象的生命周期,Java使用垃圾回收器,自动发现对象何时不再被使用,并继而销毁它。它减少了所必须考虑的议题和必须编写的代码,而且提供了更高层的保障,避免了暗藏的内存泄漏问题。

异常处理也是OOP面对的问题之一,Java的异常处理在众多编程语言中格外引人瞩目,因为Java一开始就内置了异常处理,而且强制你必须使用它(其他语言很少这么做,而是将繁琐的检查异常工作交给细心地程序员)。如果没有编写正确的处理异常的代码,那么就会得到一条编译时的出错消息。这种有保障的一致性有时会使得错误处理非常容易。不过异常处理并不是面向对象的特征。异常处理在面向对象语言出现之前就已经存在了。

8.并发

计算机编程中有一个基本概念,就是在同一时刻处理多个任务的思想。许多程序设计问题都要求,程序能够停下正在做的工作,转而处理某个其它问题,然后再返回主进程。但是难度太大,而且不能移植,有时终端对于处理时间性强的任务是必须的,但是对于大量的其他问题,我们只是想把问题切分成多个可独立运行的部分(任务),从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称之为线程,上述概念被称为“并发”。如果机器拥有多个处理器,那么程序不需要特殊调整也能执行的更快。

所有这些都是的并发看起来相当简单,但是有一个隐患:共享资源。如果多个并行任务都要访问同一项资源,那么就会出问题。为了解决这个问题,可以共享的资源,必须在使用期间被锁定(例如操作数据库时要进行锁表)。因此,整个过程是:某个任务锁定某项资源,完成其任务。然后释放资源锁,是其他任务可以使用这项资源。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值