JavaSE-Adventure(VII) Java & OOP 面向对象程序设计
Object-oriented programming (OOP)
概述
OOP 是围绕数据, 对象, 而非过程和逻辑的一种软件设计模型的计算机编程范式。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承和多态四大特性,作为代码设计和实现的基石。
面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
面向对象是面向对象的程序设计的核心,它由描述状态的属性(变量)和用来实现对象行为的方法(函数)组成,完成了从数据模型到处理模型的结合与统一。
面向对象方法论的出发点和基本原则是尽可能模拟人类习惯的思维方式,使开发软件的方法与过程尽可能接近人类认识世界解决问题的方法与过程。
也就是使描述问题的问题空间(也称为问题域)与实现解法的解空间(也称为求解域)在结构上尽可能一致。这样就解决了系统在分析过程中获得的分析模型与设计过程中所获得的设计模型进行转换时,由于理解上的差异而造成的系统不稳定性。
面向对象方法论中产生的设计模型是分析模型的进一步完善和细化,使得模型之间的转换成为一种平滑的过渡。
背景
了解OOP的诞生过程, 演进, 同样帮助我们更加了解这种编程思想。
面临的问题
面向对象是在
结构化设计方法
出现很多问题的情况下应运而生的。
结构化设计方法求解问题的基本策略是从功能的角度审视问题域。它将应用程序看成实现某些特定任务的功能模块,其中子过程是实现某项具体操作的底层功能模块。在每个功能模块中,用数据结构描述待处理数据的组织形式,用算法描述具体的操作过程。面对日趋复杂的应用系统,这种开发思路在下面几个方面逐渐暴露了一些弱点。
1. 审视问题域的视角
在现实世界中存在的客体是问题域中的主角,所谓客体是指客观存在的对象实体和主观抽象的概念。
例如,对于一个学校学生管理系统来说,始终是围绕学生和老师这两个客体实施。在自然界,每个客体都具有一些属性和行为,例如学生有学号、姓名、性别等属性,以及上课、考试、做实验等行为。因此,每个个体都可以用属性和行为来描述。
- 人类观察问题的视角: 客体
- 客体的属性: 反应客体在某一时刻的状态
- 客体的行为: 反映客体能从事的操作, 这些操作附在客体之上并能用来设置、改变和获取客体的状态。
任何问题域都有一系列的客体,因此解决问题的基本方式是让这些客体之间相互驱动、相互作用,最终使每个客体按照设计者的意愿改变其属性状态。
- 结构化设计方法
结构化设计方法所采用的设计思路不是将客体作为一个整体,而是将依附于客体之上的行为抽取出来(属性与操作分离, 相互独立),以功能为目标来设计构造应用系统。
这种做法导致在进行程序设计的时候,不得不将客体所构成的现实世界映射到由功能模块组成的解空间中,这种变换过程,不仅增加了程序设计的复杂程度,而且背离了人们观察问题和解决问题的基本思路。
另外,再仔细思考会发现,在任何一个问题域中,客体是稳定的,而行为是不稳定的。
例如,不管是国家图书馆,还是学校图书馆,还是国际图书馆,都会含有图书这个客体,但管理图书的方法可能是截然不同的。
结构化设计方法将审视问题的视角定位于不稳定的操作之上,并将描述客体的属性和行为分开,使得应用程序的日后维护和扩展相当困难,甚至一个微小的变动,都会波及到整个系统。
2. 抽象级别
抽象是人类解决问题的基本法宝。良好的抽象策略可以控制问题的复杂程度, 增强系统的通用性和可扩展性。
抽象主要包括: 过程抽象和数据抽象。
结构化设计方法应用的是过程抽象。所谓过程抽象是将问题域中具有明确功能定义的操作抽取出来,并将其作为一个实体看待。这种抽象级别对于软件系统结构的设计显得有些武断,并且稳定性差,导致很难准确无误地设计出系统的每一个操作环节。一旦某个客体属性的表示方式发生了变化,就有可能牵扯到已有系统的很多部分。
而数据抽象是较过程抽象更高级别的抽象方式,将描述客体的属性和行为绑定在一起,实现统一的抽象,从而达到对现实世界客体的真正模拟。
3. 封装体
封装是指将现实世界中存在的某个客体的属性与行为绑定在一起,并放置在一个逻辑单元内。该逻辑单元负责将所描述的属性隐藏起来,外界对客体内部属性的所有访问只能通过提供的用户接口实现。
这样做既可以实现对客体属性的保护作用,又可以提高软件系统的可维护性。只要用户接口不改变,任何封装体内部的改变都不会对软件系统的其他部分造成影响。
结构化设计方法没有做到客体的整体封装,只是封装了各个功能模块,而每个功能模块可以随意地对没有保护能力客体属性实施操作,并且由于描述属性的数据与行为被分割开来,所以一旦某个客体属性的表达方式发生了变化,或某个行为效果发生了改变,就有可能对整个系统产生影响。
4. 可重用性
可重用性标识着软件产品的可复用能力,是衡量一个软件产品成功与否的重要标志。
当今的软件开发行业,人们越来越追求开发更多的、更有通用性的可重用构件,从而使软件开发过程彻底改善,即从过去的语句级编写发展到构件组装,从而提高软件开发效率,推动应用领域迅速扩展。
然而,结构化程序设计方法的基本单位是模块,每个模块只是实现特定功能的过程描述,因此,它的可重用单位只能是模块。例如,在C语言编写程序时使用大量的标准函数。但对于现代的软件开发来说,这样的重用力度显得微不足道,而且当参与操作的某些数据类型发生变化时,就不能够再使用那些函数了。因此,渴望更大力度的可重用构件是如今应用领域对软件开发提出的新需求。
类与对象
类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。
对象是具有类类型的变量。类和对象是面向对象编程技术中的最基本的概念。
类与对象的关系:
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
程序执行期间具有不同的状态(成员变量值不同),而其他方面都相似的对象会被分组到对象的类中。
类描述了具有相同特性(数据元素)和行为(功能)的对象集合。
由类构造 (construct) 对象的过程称为创建类的实例 (instance)。
对象的含义
对象的含义是指具体的某一个事物,即在现实生活中能够看得见摸得着的事物。
在面向对象程序设计中,对象包含两个含义,其中一个是数据,另外一个是动作。
- 万物皆对象
把对象看作是特殊的变量。- 对象的状态:这个变量可以存储数据(数据)
- 对象的行为:可以在自身上执行操作 (方法)
OOP 的思想就是将所有待求解的问题的任何概念化构件 都抽取成对象,由对象去解决问题。
-
程序是对象的集合
程序是对象的集合,对象之间通过发送消息告知彼此的目的(消息传递: 对某个对象的方法的调用请求) -
可以有由其他对象构成的存储
可以创建包含现有对象的方式来创建新类型的对象 -
每个对象都有其类型
每个对象都是某个类(class) 的一个实例(instance)。 -
类(类型)
类区别于其他类的特性就是可以发什么消息给它。 -
某一特定类型的对象可以接收同样的消息
圆形对象一定能接收发送给几何形对象的消息。这意味着可以编写与几何形交互并处理所有与几何形性质相关的事务的代码。(可替代性 substitutability)
static 关键字
主要修饰目标:成员变量、成员方法、代码块
成员变量、方法被static 修饰后表示隶属该类,可通过类名直接调用。
一个既是final 又是 static 的域只占据一段不能修改的存储空间(编译期常量)。
深拷贝与浅拷贝
- 浅拷贝
浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。 - 深拷贝
深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
面向对象特征
对象唯一性
每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变,不同的对象不能有相同的标识。
抽象性
抽象性是指将具有一致的数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。任何类的划分都是主观的,但必须与具体的应用有关。
在Java 世界,使用 interface 或 abstract 来实现抽象。
抽象的意义:良好的抽象策略可以控制问题的复杂程度, 增强系统的通用性和可扩展性。
封装性
基本概念
封装是类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。
封装需要对类的访问进行控制的前提是编程语言提供了访问权限控制的语法机制
封装的意义:
-
提高代码的可读性,可维护性。如果没有封装,对类中属性的修改逻辑会散落在代码中的各个角落
-
提高类的易用性。每个类只提供有限的方式暴露其必要的操作,如果把类中所有的属性都暴露给了调用者,想要正确的使用属性,就必须对业务有较深的认识,对调用者来说也是一种负担
访问控制权限
-
public
它具有最大的访问权限,可以访问任何一个在CLASSPATH下的类、接口、异常等。它往往用于对外的情况,也就是对象或类对外的一种接口的形式。 -
protected
它主要的作用就是用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西。(或同一包内可以访问被protected 修饰的域或方法) -
default
有的时候也称为friendly,它是针对本包访问而设计的,任何处于本包下的类、接口、异常等,都可以相互访问,即使是父类没有用protected修饰的成员也可以。 -
private
它的访问权限仅限于类的内部,是一种封装的体现,例如,大多数的成员变量都是修饰符为private的,它们不希望被其他任何外部的类访问。
注意:Java的访问控制是停留在编译层的,也就是它不会在.class文件中留下任何的痕迹,只在编译的时候进行访问控制的检查。其实,通过反射的手段,是可以访问任何包下任何类中的成员,例如,访问类的私有成员也是可能的。
继承性
基本概念
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
采用继承性,提供了类的规范的等级结构。通过类的继承关系,使公共的特性能够共享,提高了软件的重用性。
继承的意义:代码复用
继承带来的问题:
- 继承层次过深,影响代码的可读性和可维护性
- 继承使得父类和子类耦合,针对父类的修改,也会影响到子类
继承的替代方案:
一般认为,组合优于继承,使用松耦合的组合来替换强耦合的继承是一种主流的实现方式。
继承的特性
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。(Override 重写)
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
super / this 关键字
我们可以通过super
关键字来实现对父类成员的访问,用来引用当前对象的父类。
super.父类成员变量
super.父类实例方法
super注意点:
- super调用父类的构造方法,必须在构造方法的第一个
- super必须只能出现在子类的方法或者构造方法中!
- super和this 不能同时调用构造方法!
this
关键字指向的是当前对象的引用。
this.当前类成员变量
this.当前类实例方法
super 与 this 在构造器中的使用
类的构建过程是从基类向外扩散的,直到Object类的构造。也就是通过new 构建一个对象时,其实在构造器的第一行会隐式的加上super() 去调用父类的构造方法,对父类进行初始化(如果父类没有无参构造时会编译报错)。
如果使用super显示的调用父类构造,就表示根据super() 参数列表查找父类构造函数,并调用。
但该类构造中出现了this()时,会在该构造中调用本类的其他构造,但最终还是会在调用链的底端调用到父类的构造。所以如果super()和this()同时存在,那么就会出现两次初始化父类。第一次是super()调用父类构造,第二次是this()调用链底端的子类构造里调用父类构造,这样就造成两次调用super。