【思考】再谈面向过程与面向对象

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42322103/article/details/100171578

在这里插入图片描述


【思考】再谈面向过程与面向对象


在我博客创作早期,写了一篇博文,名字是【Java核心技术卷】面向过程与面向对象对比

这篇文章试图对比描述了关于 面向过程与面向对象 的内容。为什么还要再谈呢?

一方面原因是深度不够,另一方面原因要从对各种编程语言的感知说起 (涵盖面向对象、面向过程):

  • 编译执行的C语言是静态语言、弱类型语言。
  • 解释执行的JavaScript语言是动态语言、弱类型语言。
  • 混合编译执行的Java是静态语言、强类型语言。

如果你不太明白静态语言和动态语言以及强类型与弱类型,看文末的补充内容。

似乎有很多独特的“语言”,而且每一种语言背后都有非常深的“技术”蕴含其中。

之前也曾就C语言,Java,Python,JavaScript这四种语言对比过它们的跨平台能力,翻译成机器码执行的过程,详情参见【Java核心技术卷】面向对象与面向过程语言对比

为了帮助你复习一遍,这里仅仅展示文章里面的四张图:

👀 C语言
在这里插入图片描述

👀 Java语言
在这里插入图片描述
👀 JavaScript语言
在这里插入图片描述
👀 Python语言
在这里插入图片描述

但是无论是 面向过程,还是面向对象, 肯定都有相通之处,也有区别所在。

面向过程就不多说,基本是C的天下了。

那么对于面向对象呢?

🐘 我们这里首先谈论一下面向对象的相通之处:


面向对象有着三大基本特征

  1. 封装
  2. 继承
  3. 多态

这个你就比较熟悉了。

封装:
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
类将成员变量和成员函数封装在类的内部,根据需要设置访问权限,通过成员函数管理内部状态。

继承:
继承所表达的是类之间相关的关系,这种关系使得对象可以继承另外一类对象的特征和能力。
继承的作用:避免公用代码的重复开发,减少代码和数据冗余。

多态
多态性可以简单地概括为“一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。

但是插句题外话,你知道你所熟悉的语言的"继承"与“多态”是如何实现的嘛?
关于Java的话可以参考这三篇文章
【Java核心技术卷】了解Java的内存逻辑对象模型
【Java核心技术卷】理解Java的继承与多态重要概念
【Java核心技术卷】深入理解Java的动态绑定,静态绑定和多态


不知道你是否学过设计模式的相关内容,像UML、七大软件设计原则、二十三种设计模式 它们中有很多的东西都是面向对象所通用的,里面深刻地体现着面向对象的思想。

有一句话说的很好:“使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样”

建议每个人都要好好琢磨琢磨。


那你听说过面向对象的五大基本原则吗?

  1. 单一职责原则(Single-Responsibility Principle)
  2. 开放封闭原则(Open-Closed principle)
  3. Liskov替换原则(Liskov-Substituion Principle)
  4. 依赖倒置原则(Dependency-Inversion Principle)
  5. 接口隔离原则(Interface-Segregation Principle)

其实这五条也是七大软件设计原则中的内容,我们看吧~

七大软件设计原则 可以参考【Java设计模式】软件设计七大原则
实现语言是 Java哈,因为有举例所以更好理解一些。


面向对象的五大基本原则 文字叙述部分

这部分内容参考了网上的资料,但是因为来源过多,无法注明出处了。

一、 单一职责原则(Single-Resposibility Principle)

其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。

单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。

职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。

通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。

单一是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭牵一发而动全身,有失美感和必然导致丑陋的系统错误风险。

二、开放封闭原则(Open-Closed principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。

开放封闭原则主要体现在两个方面:
1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。

实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。

“需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。

三、Liskov替换原则(Liskov-Substituion Principle)

其核心思想是:子类必须能够替换其基类。

这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。

在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。

Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。

实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。

Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。

Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

四、 依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。

我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。

抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。

依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。

五、接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。

具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。

接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。

分离的手段主要有以下两种:
1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。
2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。

以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”

为了让代码更加完美,我们往往会重构它,如果能够很好遵守这5个基本的面向对象设计原则,并且有着良好的单元测试习惯,那么重构将不会一下子变得无比艰难。



🐘 那面向对象的语言的区别呢?

这范围可就广了,用我熟悉的Java和C++说一下吧,通过对比,我们是能够学到东西的:

🍓

C++ 被设计成主要用在系统性应用程序设计上的语言,对C语言进行了扩展。对于C语言, C++ 特别加上了以下这些特性的支持:静态类型的面向对象程序设计的支持、异常处理、RAII以及泛型。另外它还加上了一个包含泛型容器和算法的C++库函数。

Java 依赖一个虚拟机来保证安全和可移植性。Java包含一个可扩展的库用以提供一个完整的的下层平台的抽象。Java是一种静态面向对象语言,它使用的语法类似C++,但与之不兼容。为了使更多的人到使用更易用的语言,它进行了全新的设计。

🍓

C++是编译型语言(首先将源代码编译生成机器语言,再由机器运行机器码),执行速度快、效率高;依赖编译器、跨平台性差些。

Java是混合型语言(源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。),执行速度慢、效率低;依赖解释器、跨平台性好。

🍓

C++是平台相关的

Java是平台无关的。

🍓

C++对所有的数字类型有标准的范围限制,但字节长度是跟具体实现相关的,不同操作系统可能。

Java在所有平台上对所有的基本类型都有标准的范围限制和字节长度。

🍓

C++除了一些比较少见的情况之外和C语言兼容 。

Java没有对任何之前的语言向前兼容。但在语法上受 C/C++ 的影响很大

🍓

C++允许直接调用本地的系统库 。

Java要通过JNI调用, 或者 JNA

🍓
C++允许过程式程序设计和面向对象程序设计 。

Java必须使用面向对象的程序设计方式

🍓

C++支持指针,引用,传值调用 。

Java只有值传递。

Java只有值传递 , 这个 你不好奇吗?

🍓

C++需要显式的内存管理,但有第三方的框架可以提供垃圾搜集的支持。支持析构函数。

Java 是自动垃圾收集的。没有析构函数的概念。

🍓
C++支持多重继承,包括虚拟继承 。

Java只允许单继承,需要多继承的情况要使用接口。

千万不要把自己限制死了,通过比较能拓宽我们的见识。




最后补充一下上面需要参考的内容:

静态类型语言、动态类型语言分析:

静态类型语言:变量定义时有类型声明的语言。
1)变量的类型在编译的时候确定
2)变量的类型在运行时不能修改
这样编译器就可以确定运行时需要的内存总量。
例如:C/C++/Java/C#语言是静态类型语言。

动态类型语言:变量定义时无类型声明的语言。
1)变量的类型在运行的时候确定
2)变量的类型在运行可以修改
例如:Javascript语言是动态类型语言。
由于动态类型和静态类型语言的特性衍生出强类型语言和弱类型、无类型语言。

强类型语言、弱类型、无类型语言:
弱/强类型指的是语言类型系统的类型检查的严格程度。弱类型相对于强类型来说类型检查更不严格,比如说允许变量类型的隐式转换,允许强制类型转换等等。

  • 强类型语言:例如Java/C#语言是强类型语言,强类型定义语言是类型安全的语言,是由编译器以及编译器生成的中间代码来保证类型安全。
  • 弱类型语言:C/C++/Javascript语言是弱类型语言,其类型安全由程序员来保证,Javascript语言的安全由程序员来保证。
    无类型语言:是动态语言,变量中既可以存放数据又可以存放代码。
展开阅读全文

没有更多推荐了,返回首页