本篇文章将对 Java
中面向对象的一些概念做出详细讨论。
一、为什么要面向对象?
老是在说面向对象,面向对象中的这个对象到底是什么呢?
1、什么是对象?
对象是人们可以进行研究的一切事物,从最简单的一支铅笔到极其复杂的航空母舰,都可以看作是对象。对象不仅仅能表示具体的事物,还能表示抽象的规则、计划或者事件等。
因此,面向对象的这个对象,指的是客体。所谓客体指的是客观存在的对象实体和主观抽象的概念。
客体或对象(
Object
)在哲学上指可感知或可想像到的任何事物,既包括客观存在并可观察到的事物(如人物、树木、房屋,抽象的如物价、自由),也包括想像的事物(如神化人物)。—— 《维基百科》
2、结构化编程
最早的程序开发,使用的都是结构化(面向过程)程序设计语言。
计算机是帮助人们解决问题的,然而计算机终究是个机器,它只会按照人所写的代码,一步一步的执行下去,最终得到了结果。**结构化编程,就是按照计算机的思维写出的代码。**随着时间的推移,软件的规模逐渐扩大,业务逻辑变得越来越复杂的时候,当人再看到这些逻辑时,就无法维护和扩展了。
同时,结构化设计是以功能为目标来设计构造应用系统的,这种做法导致我们设计程序时,不得不将客体所构成的现实世界映射到由功能模块组成的软件系统中,这种转换过程,背离了人们观察和解决问题的基本思路。
3、面向对象编程
可见结构化编程在设计系统的时候,无法解决重用、维护、扩展的问题,而且会导致逻辑过于复杂,代码晦涩难懂的问题。
于是人们就想,能不能让计算机直接模拟现实的环境,用人类解决问题的思路、习惯和步骤来设计相应的应用程序呢?
于是,程序设计者们将另一种开发思想引入程序中 —— 面向对象编程(Object-Oriented Programming
)。 人们在阅读使用面向对象编程思想编写的程序时,会更容易理解,也不需要再在现实世界和程序世界之间来回做转换。
举个下五子棋的例子。
结构化编程(面向过程)的设计思路就是分析问题的步骤,把每个步骤分别用函数来实现:
-
开始游戏
-
黑子先走
-
绘制画面
-
判断输赢
-
轮到白子
-
绘制画面
-
判断输赢
-
返回步骤 2
-
输出最后结果
而面向对象的设计思路是:
- 黑白双方:这两方的行为是一模一样的(玩家对象);
- 棋盘系统:负责绘制画面(棋盘对象);
- 规则系统:负责判定诸如犯规、输赢等(规则对象)。
玩家对象负责接受用户输入,并告知棋盘对象棋子布局的变化,棋盘对象接收到了棋子的变化,负责在屏幕上面显示出这种变化;同时利用规则对象来对棋局进行判定。
也就是说:先找到对象,然后把它的状态(属性)、行为(方法)全部封装到一起,随时调用。
4、面向对象的优点
面向对象程序设计有以下优点:
- 可重用性:代码重复使用,减少代码量,提高开发效率;
- 可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改;
- 可维护性:能够将功能与数据结合,方便维护。
在下面的讨论中,我会一一讲解这些优点在 Java
中是如何体现的。
二、对象
对象与事物(可感知或可想像到的)是一一对应的,也就是说每一个客体都是一个对象,因此对象具有唯一性(即使是完全相同的两个对象,也并非同一个对象)。对象是一个具体的概念。
对象具有两个特征:
- 状态(
State
) - 行为(
Behavior
)
以小狗这个对象来举例:
- 状态:名字、年龄等;
- 行为:吠叫、奔跑等。
三、类
什么是类?
**类是描述了一组有相同特征(属性)和相同行为(方法)的一组对象的集合。**类可以被视为蓝图(或者模板),使用类可以创建任意多的对象。因此,对象是类的实例。
以小狗为例,中华田园犬是个对象、柴犬是个对象、哈士奇是个对象、牧羊犬也是个对象,这些小狗具有相同的状态(属性)和行为(方法)。这些一个个的对象属于同一个种类 —— 狗类,它是一个逻辑实体,而不是一个具体的实物。
用 Java
语言来描述这个类就是:
public class Dog {
String name;
int age;
void barking() {
System.out.println("Woof woof woof!");
}
void running() {
System.out.println("I run fast!");
}
}
Dog
这个类拥有 name
、age
这些属性和 barking
、running
这些方法,那么通过 Dog
这个类(模板)创建的对象(实例)都会具有这些属性(状态)和方法(行为)。也就是说类定义了属于这个类的对象所应该具有的状态和行为。
下面对上述代码示例做一下说明:
public
是类的修饰符(后面会讲到),表明该类是公共类,可以被其他类访问;class
是声明类的关键字;Dog
是类名称;name
、age
是类的成员变量(实例变量),也叫属性(后面会介绍其他类型的变量);barking()
、running()
是类中的函数,也叫方法。
有了类,怎么生成实例呢?—— 通过 new
关键字。一般有以下三个步骤:
- 声明:声明一个对象,包括对象名称和对象类型;
- 实例化:使用关键字
new
来创建一个对象。 - 初始化:使用
new
创建对象时,会调用构造函数(下面会讲到)初始化对象。
Dog myDog = new Dog();
myDog
就是通过 Dog
这个类实例化成的一个对象。
现在我们知道了什么是对象、什么是类。那回到最开始的问题,面向对象编程是如何解决重用、维护、扩展的问题的呢?
四、封装
前面我们已经知道了对象有属性和方法两个特征。在编程时,把一些数据(属性)和有关数据的一些操作(方法)绑定在一起,形成一个不可分开的集合(类),这个过程就是封装(Encapsulation
)。也就是说,如果你正在创建类,那么你就是在进行封装。
封装背后的思想其实是在向用户隐藏实现细节。我们把类里面的属性私有化,那么这些属性就只能在同一个类中访问,任何外部类都不能访问;然后向外提供操作数据的公开的方法。当外部需要访问时,可以不用管类内部的具体逻辑关系,只需要通过调用类内部对应的方法就可以了。这样可以保证数据的安全性。
同时,封装也有助于建立各个系统之间的松耦合关系,提高系统的独立性。当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。
比如我们使用 ATM
机取钱的时候,只需要插入银行卡,输入密码即可取钱,不用关心是它的内部如何运行或者发生了什么改变,只要我们可以插卡,可以输入密码,就不会影响正常的操作。
当类发生变化时,我们只需要找到变化并且把它继续封装起来,就可以在不影响其它部分的情况下修改或扩展被封装的变化部分,因此就解决了程序的可扩展性。
五、继承
就像生活中,子女可以继承父母所拥有的的全部财产一样,Java
里的**继承(Inheritance
)**是指子类拥有父类的全部特征和行为,这也就是类之间的关系。
可以试想一下,以小狗为例,定义一个哈士奇类和柴犬类,如果不采用继承的方式,那么这两个类里需要定义的特性(属性)和行为(方法)很多都是相同的,代码会有很多重复和冗余的地方。如果将一些公有的特性(属性)和行为(方法)拿出来封装在一个类(父类)中,其他类可以通过继承这个类来得到这些属性和方法,这样就不用每次都重新定义了。当然,父类也可以去继承其它的类,比如狗类可以再去继承哺乳动物类。因此,继承也解决了系统的重用性和扩展性。
但是继承也破坏了封装,因为父类是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,所以,我们也需要谨慎使用继承。优先使用组合,而不是继承,是面向对象开发中一个重要的经验。
注意,Java
中的继承是单继承,也就是说一个子类最多只能有一个父类。
六、多态
理解多态之前,需要先了解另外一个概念 —— 接口(Interface
)。什么是接口呢?比如彩色打印机可以打印,黑白打印机也可以打印,我们可以把打印这个行为抽离出来,变成一个接口,为两个类提供通用的处理服务。也就是说接口是对行为的抽象。
所以,简单来说,多态(Polymorphism
)就是同一个行为具有多个不同表现形式或形态的能力。对应到应用程序中就是同一个接口,使用不同的实例对象而执行不同操作。
如下图打印机的例子:
我们把打印文件这个方法变成一个接口,让彩色打印机和黑白打印机都实现这个接口,同一个事件发生在不同的对象上会产生不同的结果。父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。这样就实现了系统的可扩展性,可维护性也会变强。
七、总结
通过上面的描述,我们知道了面向对象是如何实现系统的可维护性,可扩展性,可重用性,不过只是一些概念性的讲解,并没有落地到代码层面进行阐述,后面我会结合代码再做详细讨论。
其实,面向对象是一种编程思想,跟语言没有什么关系,只不过有的语言在语言的设计层面,就天然地与面向对象的思想联系起来,有类,有继承,如 Java
、C++
;而像 JavaScript
中并没有类的概念,也只是通过构造函数和原型链的方式来实现继承,但是我们在使用 JavaScript
编程的时候,依然可以使用面向对象编程思想来编程,让代码的可维护性、可扩展性、可重用性变得更好。
来源:CSDN
作者:IDeepspace
链接:https://blog.csdn.net/Deepspacece/article/details/104317061