码出高效:Java开发手册-第2章(1)

简介: 本章开始讲解面向对象思想,并以Java 为载体讲述面向对象思想在具体编程语言中的运用与实践。当前主流的编程语言有50 种左右,主要分为两大阵营:面向对象编程与面向过程编程。面向对象编程(Object-Oriented Programming,OOP)是划时代的编程思想变革,推动了高级语言的快速发展和工业化进程。OOP 的抽象、封装、继承、多态的理念使软件大规模化成为可能,有效地降低了软件开发成本、维护成本和复用成本。面向对象编程思想完全不同于传统的面向过程编程思想,使大型软件的开发就像搭积木一样隔离可控、高效简单,是当今编程领域的一股势不可......

第2章 面向对象

 “一树一菩提,一‘门’一世界。”一切皆对象,万物有三问:我是谁?我从哪里来?我到哪里去?

1.jpg

      本章开始讲解面向对象思想,并以Java 为载体讲述面向对象思想在具体编程语言中的运用与实践。当前主流的编程语言有50 种左右,主要分为两大阵营:面向对象编程与面向过程编程。

      面向对象编程(Object-Oriented Programming,OOP)是划时代的编程思想变革,推动了高级语言的快速发展和工业化进程。OOP 的抽象、封装、继承、多态的理念使软件大规模化成为可能,有效地降低了软件开发成本、维护成本和复用成本。面向对象编程思想完全不同于传统的面向过程编程思想,使大型软件的开发就像搭积木一样隔离可控、高效简单,是当今编程领域的一股势不可当的潮流。OOP 实践了软件工程的三个主要目标:可维护性、可重用性和可扩展性。

2.1 OOP 理念

      面向过程让计算机有步骤地顺序地做一件事情,是一种过程化的叙事思维。但是在大型软件开发过程中,发现用面向过程语言开发,软件维护、软件复用存在着巨大的困难,代码开发变成了记流水账,久而久之就成为“面条”代码,模块之间互相耦合,流程互相穿插,往往牵一发而动全身。面向对象提出一种计算机世界里解决复杂软件工程的方法论,拆解问题复杂度,从人类思维角度提出解决问题的步骤和方案。

      比如“开门”这个动作,面向过程是“open(Door door)”,动宾结构,“door”是被作为操作对象的参数传入方法的,方法内定义开门的具体步骤实现。而在面向对象的世界里,首先定义一个对象“Door”,然后抽象出门的属性和相关操作,属性包括门的尺寸、颜色、开启方式(往外开还是往内开)、防盗功能等;门这个对象的操作必然包括open() 和close() 两个必备的行为,主谓结构。面向过程的代码结构相对松散,强调如何流程化地解决问题;面向对象的思维更加内聚,强调高内聚、低耦合,先抽象模型,定义共性行为,再解决实际问题。

      但是,编程语言仅是一个工具,就像练武之人的剑,武功高者草木皆剑,武功差者即使倚天剑在身也依然平庸。所以,能否将工具的价值发挥得淋漓尽致,最终还是取决于开发工程师本身。优秀的开发工程师用面向过程的语言也能把程序写得非常内聚,可扩展性好,具备一定的复用性;而平庸程序员用面向对象语言一样能把程序写得松散随意、毫无抽象与建模、模块间耦合严重、维护性差。

      传统意义上,面向对象有三大特性:封装、继承、多态。本书明确将“抽象”作为面向对象的特性之一,支持面向对象“四大特性”的说法。抽象是程序员的核心素质之一,体现出程序员对业务的建模能力,以及对架构的宏观掌控力。虽然面向过程也需要进行一定的抽象能力,但是相对来说,面向对象思维,以对象模型为核心,丰富模型的内涵,扩展模型的外延,通过模型的行为组合去共同解决某一类问题,抽象能力显得尤为重要;封装是一种对象功能内聚的表现形式,使模块之间耦合度变低,更具有维护性;继承使子类能够继承父类,获得父类的部分属性和行为,使模块更有复用性;多态使模块在复用性基础上更加有扩展性,使系统运行更有想象空间。

      抽象是面向对象思想最基础的能力之一,正确而严谨的业务抽象和建模分析能力是后续的封装、继承、多态的基础,是软件大厦的无形基石。在面向对象的思维中,抽象分为归纳和演绎。前者是从具体到本质,从个性到共性,将一类对象的共同特征进行归一化的逻辑思维过程;后者则是从本质到具体,从共性到个性,逐步形象化的过程。在归纳的过程中,需要抽象出对象的属性和行为的共性,难度大于演绎。演绎也是一种抽象思维,并非是具像思维。如果人对理论的认知与理解存在误区,那么推理的过程一定会产生偏差,演绎的结果可能是一个抽象结果,并非一定是一个具体的对象或者物体,比如从化合物到食物,从食物到水果,都还是在抽象层面上。演绎是在已有问题多个解决方案的基础上,正确地找到合适的使用场景。在使用集合时,演绎错误比较常见,比如针对查多改少的业务场景,使用链表是非常不合理的;如果在底层框架技术选型时有错误,则有可能导致技术架构完全不适应业务的快速发展。

      Java 之父 Gosling 设计的Object 类,是任何类的默认父类,是对万事万物的抽象,是在哲学方向上进行的延伸思考,高度概括了事物的自然行为和社会行为。我们都知道哲学的三大经典问题:我是谁,我从哪里来,我到哪里去。在Object 类中,这些问题都可以得到隐约的解答:

(1)我是谁? getClass() 说明本质上是谁,而toString() 是当前我的名片。

(2)我从哪里来? Object() 构造方法是生产对象的基本方式,clone() 是繁殖对象的另一种方式。

(3)我到哪里去? finalize() 是在对象销毁时触发的方法。

      这里重点介绍clone() 方法,它分为浅拷贝、一般深拷贝和彻底深拷贝。浅拷贝只复制当前对象的所有基本数据类型,以及相应的引用变量,但没有复制引用变量指向的实际对象;而彻底深拷贝是在成功clone 一个对象之后,此对象与母对象在任何引用路径上都不存在共享的实例对象,但是引用路径递归越深,则越接近JVM 底层对象,会发现彻底深拷贝实现难度越大,因为JVM 底层对象可能是完全共享的。介于浅拷贝和彻底深拷贝之间的都是一般深拷贝。归根结底,慎用Object 的clone() 方法来拷贝对象,因为对象的clone() 方法默认是浅拷贝,若想实现深拷贝,则需要覆写clone() 方法实现引用对象的深度遍历式拷贝。

      另外,Object 还映射了社会科学领域的一些问题:

(1)世界是否因你而不同? hashCode() 和equals() 就是判断与其他元素是否相同的一组方法。

(2)与他人如何协调? wait() 和notify() 是对象间通信与协作的一组方法。随着时代的发展,当初抽象的模型已经有部分不适用当下的技术潮流,比如finalize() 方法在JDK9 之后直接被标记为过时方法。而wait() 和notify() 同步方式事实上已经被同步信号、锁、阻塞集合等取代。

      封装是在抽象基础上决定信息是否公开,以及公开等级,核心问题是以什么样的方式暴露哪些信息。抽象是要找到属性和行为的共性,属性是行为的基本生产资料,具有一定的敏感性,不能直接对外暴露;封装的主要任务是对属性、数据、部分内部敏感行为实现隐藏。对属性的访问与修改必须通过定义的公共接口来进行,某些敏感方法或者外部不需要感知的复杂逻辑处理,一般也会进行封装。封装使面向对象的世界变得单纯,对象之间的关系变得简单,各人自扫门前雪,耦合度变弱,有利于维护。智能化的时代,对封装的要求越来越高,产品使用更加简单方便、轻松自然。就像天猫精灵,与用户交互的唯一接口就是语音输入,隐藏了指令内部的细节实现和相关数据,这些信息外部用户无法访问,即大大降低了使用成本,又有效地保护内部数据安全。

      设计模式七大原则之一的迪米特法则就是对于封装的具体要求,即A 模块使用B模块的某个接口行为,对B 模块中除此行为之外的其他信息知道得尽可能少。比如:耳塞的插孔就是提供声音输出的行为接口,只需关心这个插孔是否有相应的耳塞标记,是否是圆形的,有没有声音即可,至于内部CPU 如何运算音频信息,以及各个电容如何协同工作,根本不需要去关注,这使模块之间的协作只需忠于接口、忠于功能实现即可。

      封装这件事情是由俭入奢易,由奢入俭难。属性值的访问与修改需要使用相应的getter/setter 方法,而不是直接对public 的属性进行读取和修改,可能有些程序员存在疑问,既然通过这两个方法来读取和修改,那与直接对属性进行操作有何区别?如果某一天,类的提供方想在修改属性的setter 方法上进行鉴权控制、日志记录,这是在直接访问属性的情形中无法做到的。若是将已经公开的属性和行为直接暴力修改为private,则依赖模块都会编译出错。所以,在不知道什么样的访问控制权限合适的时候,优先推荐使用private 控制级别。

      继承是面向对象编程技术的基石,允许创建具有逻辑等级结构的类体系,形成一个继承树,让软件在业务多变的客观条件下,某些基础模块可以被直接复用、间接复用或增强复用,父类的能力通过这种方式赋予子类。继承把枯燥的代码世界变得更有层次感,更有扩展性,为多态打下语法基础。

      人人都说继承是is-a关系,那么如何衡量当前的继承关系是否满足is-a 关系呢?判断标准即是否符合里氏代换原则(Liskov Substitution Principle,LSP)。LSP 是指任何父类能够出现的地方,子类都能够出现。从字面上很难深入理解,先打个比方,警察在枪战片中经常说:放下武器,把手举起来!而对面的匪徒们有的使用手枪,有的使用匕首,这些都是武器的子类。父类出现的地方,即“放下武器”,那么,放下手枪,是对的,放下匕首,也是对的!在实际代码环境中,如果父类引用直接使用子类引用来代替,可以编译正确并执行,输出结果符合子类场景的预期,那么说明两个类之间符合LSP 原则,可以使用继承关系。

      继承的使用成本很低,一个关键字就可以使用别人的方法,似乎更加轻量简单。想复用别人的代码,跳至脑海的第一反应是继承它,所以继承像抗生素一样容易被滥用,我们传递的理念是谨慎使用继承,认清继承滥用的危害性,即方法污染和方法爆炸。方法污染是指父类具备的行为,通过继承传递给子类,子类并不具备执行此行为的能力,比如鸟会飞,鸵鸟继承鸟,发现飞不了,这就是方法污染。子类继承父类,则说明子类对象可以调用父类对象的一切行为。在这样的情况下,总不能在继承时,添加注释说明哪几个父类方法不能在子类中执行,更不能覆写这些无法执行的父类方法,抛出异常,以阻止别人的调用。方法爆炸是指继承树不断扩大,底层类拥有的方法虽然都能够执行,但是由于方法众多,其中部分方法并非与当前类的功能定位相关,很容易在实际编程中产生选择困难症。比如某些综合功能的类,经过多次继承后达到上百个方法,造成了方法爆炸,因而带来使用不便和安全隐患。在实际故障中,因为方法爆炸,父类的某些方法签名和子类非常相似,在IDE 中,输入类名+ 点之后,在自动提示的极为相似的方法签名中选择错误,导致线上异常。综上所述,提倡组合优先原则来扩展类的能力,即优先采用组合或聚合的类关系来复用其他类的能力,而不是继承。

      多态是以上述的三个面向对象特性为基础,根据运行时的实际对象类型,同一个方法产生不同的运行结果,使同一个行为具有不同的表现形式。多态是面向对象天空中绚丽多彩的礼花,提升了对象的扩展能力和运行时的丰富想象力。我们来明确两个非常容易混淆的概念:“override”和“overload”,“override”译成“覆写”,是子类实现接口,或者继承父类时,保持方法签名完全相同,实现不同的方法体,是垂直方向上行为的不同实现。“overload”译成“重载”,方法名称是相同的,但是参数类型或参数个数是不相同的,是水平方向上行为的不同实现。多态是指在编译层面无法确定最终调用的方法体,以覆写为基础来实现面向对象特性,在运行期由JVM 进行动态绑定,调用合适的覆写方法体来执行。重载是编译期确定方法调用,属于静态绑定,本质上重载的结果是完全不同的方法,所以本书认为多态专指覆写。自然界的多态最典型例子就是碳家族,据说某化学家告诉他女朋友将在她的生日晚会上送她一块碳,女朋友当然不高兴,可收到的却是5 克拉的钻石。钻石就是碳元素在不断进化过程中的一种多态表现。严格意义上来说,多态并不是面向对象的一种特质,而是一种由继承行为衍生而来的进化能力而已。

相关文章
|
20天前
|
监控 JavaScript 前端开发
《理解 WebSocket:Java Web 开发的实时通信技术》
【4月更文挑战第4天】WebSocket是Java Web实时通信的关键技术,提供双向持久连接,实现低延迟、高效率的实时交互。适用于聊天应用、在线游戏、数据监控和即时通知。开发涉及服务器端实现、客户端连接及数据协议定义,注意安全、错误处理、性能和兼容性。随着实时应用需求增加,WebSocket在Java Web开发中的地位将更加重要。
|
1月前
JavaWeb 开发之 ServletContext 的和使用
JavaWeb 开发之 ServletContext 的和使用
23 1
|
5天前
|
IDE Java 数据库连接
使用 Java 进行桌面应用开发
【4月更文挑战第19天】Java 是一款广泛应用于企业级、网络和桌面应用开发的编程语言。其跨平台特性使Java程序能在不同操作系统上运行,而JDK提供了开发所需工具和库。使用Swing等GUI库构建用户界面,结合JDBC进行数据库操作,Socket实现网络通信。虽然面临性能和用户体验的挑战,但通过优化和选用合适的IDE,Java仍能开发出高效稳定的桌面应用。
|
6天前
|
前端开发 Java Go
开发语言详解(python、java、Go(Golong)。。。。)
开发语言详解(python、java、Go(Golong)。。。。)
|
6天前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
73 10
|
6天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
13天前
|
运维 NoSQL 算法
Java开发-深入理解Redis Cluster的工作原理
综上所述,Redis Cluster通过数据分片、节点发现、主从复制、数据迁移、故障检测和客户端路由等机制,实现了一个分布式的、高可用的Redis解决方案。它允许数据分布在多个节点上,提供了自动故障转移和读写分离的功能,适用于需要大规模、高性能、高可用性的应用场景。
16 0
|
15天前
|
人工智能 小程序 Java
JAVA开发智慧学校系统源码+人脸电子班牌布局
智慧校园是通过利用物联网,大数据技术来改变师生和校园资源相互交互的方式,以便提高交互的明确性、灵活性和响应速度,从而实现智慧化服务和管理的校园模式。
|
18天前
|
安全 前端开发 Java
Java Web开发知识点学习总结
Java Web开发涉及Java基础、Servlet、JSP、数据库操作(SQL+JDBC)、MVC设计模式、Spring框架、Hibernate ORM、Web服务(SOAP&RESTful)、安全认证(HTTP Basic/Digest/OAuth)及性能优化(缓存、异步、负载均衡)。
17 3
|
21天前
|
XML JSON JavaScript
使用JSON和XML:数据交换格式在Java Web开发中的应用
【4月更文挑战第3天】本文比较了JSON和XML在Java Web开发中的应用。JSON是一种轻量级、易读的数据交换格式,适合快速解析和节省空间,常用于API和Web服务。XML则提供更强的灵活性和数据描述能力,适合复杂数据结构。Java有Jackson和Gson等库处理JSON,JAXB和DOM/SAX处理XML。选择格式需根据应用场景和需求。