《架构整洁之道》第 5 章 面向对象编程

2023-05-22 10:47:03 浏览数 (4)

均为个人读书笔记,精读并整理出来各个章节的知识点。

什么是面向对象设计?

常见的回答是数据与函数的组合,我在很久以前写过对象的理解,对象的本质与延迟绑定,该文显然只描述了对象的结构是什么,但是却没有对面向对象设计做深入了解学习。

数据与函数的组合,暗示了o.f()f(o)之间是有区别的,但这显然不是事实。面向对象理论是1966年提出来的,但是将数据结构作为函数的参数来进行传递,这件事情远在1966年以前就发生了。

这里不是很好理解,显然,o.f()表示的是面向对象的调用方式,f()函数可能会改变o对象中的属性值。而f(o),即是将数据结构作为f函数的参数,由f函数,去改变o对象这个数据结构的属性值。也就是说在作者看来,这是没有区别的。

另一种说法是,面向对象编程是一种对真实世界进行建模的方式,这种回答是避重就轻的,因为它无法回答对真实世界建模该如何进行,为何要如此做。

还有人回答时会搬出一些名词,如封装继承多态。隐含的意思是面向对象编程是这三项的有机组合,面向对象编程语言必须支持这三个特性。接下来会逐一分析这三个概念。

封装

封装体现在对象中的函数和成员变量可见性上,如公有函数和私有成员变量。

但是,这个特性并不是面向对象编程所独有的!C语言也支持完整的封装,可以使其结构体中部分函数对外部程序调用。反而是C 作为一门面向对象语言,破坏了C语言的完美封装性,即不完美的封装,但还是保有一定的封装性。

由于各种原因,我们很难说,强封装是面向对象编程的重要条件,事实上有很多面向对象编程语言对封装性没有强制性的要求。如PythonJavaScript等。

继承

继承体现在我们可以用一个对象中继承一部分属性或方法,也可以覆盖掉一部分属性和方法。

既然面向对象编程语言没有提供更好的封装性,那在继承性发面又如何呢?其实也很一般。因为早在面向对象语言发明之前,C程序员就已经在做类似的工作了,当时是以一种取巧的方式实现这个特性的,将某种数据结构伪装成另外一种数据结构的特性。

所以可以认为,面向对象在编程在继承方面并没有做出什么创新,但还是提供了一些便利,比如不用再取巧伪装特性了。也就是说,面向对象编程中的继承性,还是有点用的,但没有本质上的变化。

多态

在面向对象语言发明之前,就已经有多态性体现在当时的程序中了。如在UNIX操作系统中,强制要求每个IO设备提供5个标准函数,openclosereadwriteseekIO驱动程序就会提供这5个函数的实现。现在要读取一样东西,系统就会调用STDIN->read,那STDIN是什么呢?指的是哪个设备呢?它是FILE类型,相当于IO驱动程序要实现STDIN这个接口。这时STDIN只需要指向FILE数据结构体中的read函数指针,就可以完成read调用了。也就是说STDIN依赖于具体的FILE类型。

这正是多态的基础,归根结底,多态不过是函数指针的一种应用,从冯诺依曼架构诞生那天起,程序员就在使用函数指针模拟如今的多态特性了。所以面向对象编程依然没有提出任何新的概念。

用函数指针实现多态最大的问题是它很危险,面向对象编程语言让其变得安全和便于使用了。

多态的强大性

多态的优势在于,它分离了程序和具体实现,相当于使用接口来进行调用,而不是创建一个对象这个具体实现来进行调用,提供了极大的灵活性。

依赖反转

在没有安全的多态出现前,函数调用是一层依赖一层,底层函数必须要对高层函数可见,高层才能调用到底层函数,高层不可避免地要依赖下层,无论是源代码还是控制流。

这样,系统功能决定了控制流(层级调用控制),而控制流决定了源代码依赖关系。别无选择。

但是一旦使用了多态,情况就变得不一样了,因为有接口,源代码不再依赖于具体实现,而是依赖接口,控制流再也决定不了源代码的依赖了,具体实现可以随意变换了。

依赖反转,不仅解决了强依赖,从解除依赖后还可以获得独立部署的能力。

例如用户界面->业务逻辑->数据库。我们可以将这三者独立部署,通过接口进行依赖的隔离,让用户界面和数据库成为业务逻辑的插件,因为用户界面和数据库是多变的,业务逻辑一般不会变,使用接口,如果用户界面变了,不用整体打包编译,只需要重新编译用户界面即可,而不需要更改其他的组件。

总结

可以看出来,面向对象编程就是以多态的手段来对源码中的依赖关系进行控制的能力,这种能力可以构建出插件式的架构,使其各部分能够独立开发和部署。

1 人点赞