面向对象编程,再见!

面包板社区 2020-05-12 00:00

来源 :CSDN知识库 

作为程序员,你是使用函数式编程还是面向对象编程方式?

在本文中,拥有 10 多年软件开发经验的作者从面向对象编程的三大特性——继承、封装、多态三大角度提出了自己的疑问,并深刻表示是时候和面向对象编程说再见了。


几十年来我都在用面向对象的语言编程。我用过的第一个面向对象的语言是 C++,后来是 Smalltalk,最后是 .NET 和 Java。
我曾经对使用继承、封装和多态充满热情。它们是范式的三大支柱。
我渴望实现重用之美,并在这个令人兴奋的新天地中享受前辈们积累的智慧。
想到将现实世界的一切映射到类中,使得整个世界都可以得到整齐的规划,我无法抑制自己的兴奋。
然而我大错特错了。

 01 
继承,倒塌的第一根支柱

乍一看,继承似乎是面向对象范式的最大优势。所有新手教程讲解继承时都会拿出最简单的继承的例子,而这个例子似乎很符合逻辑。
然后就是满篇的重用了。甚至以后的一切都是重用了。
我囫囵吞下这一切,然后带着新发现兴冲冲地奔向世界了。
香蕉猴子丛林问题
带着满腔的信仰和解决问题的热情,我开始构建类的层次结构然后写代码。似乎一切皆在掌控中。
我永远不会忘记我准备从已有的类继承并实现重用的那一天。那是我期待已久的时刻。
后来有了新的项目,我想起了另一个项目里我很喜欢的那个类。
没问题,重用拯救一切。我只需要把那个类拿过来用就好了。
嗯……其实……不仅是那一个类。还得把父类也拿过来。但……应该就可以了吧。
额……不对,似乎还需要父类的父类……还有……嗯,我们需要所有的祖先类。好吧好吧……搞定了。没问题。
不错。但编译不过,怎么回事?哦我知道了……这个对象还需要另一个对象。所以那个也得拿过来。没问题……
等等……我不仅需要那个对象,还需要那个对象的父类,和父类的父类,和……包含的所有对象的所有祖先……
唉……
Erlang 的创建者 JoeArmstrong 有句名言:
面向对象语言的问题在于,它们依赖于特定的环境。你想要个香蕉,但拿到的却是拿着香蕉的猩猩,乃至最后你拥有了整片丛林。
香蕉猴子丛林的解决方法
这个问题的解决方法是,不要把类层次建得那么深。但如果继承是重用的关键,那么给继承机制添加的任何限制都会限制重用。对吧?
没错。
那我们可怜的面向对象程序员该怎么办?指望一杯三聚氰胺奶维系我们的健康吗?
答案就是:包含和委托(Contain and Delegate)。一会儿会详细解释。
菱形继承问题
早晚你会遇到下面这种恶心的问题,有些语言甚至根本解决不了。
大多数面向对象语言都不支持这种情况,尽管看上去似乎很符合逻辑。为什么面向对象语言支持这种情况如此困难?
来看看下面的伪代码:
    
Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
  function start() {
  }
}
Class Printer inherits from PoweredDevice {
  function start() {
  }
}
Class Copier inherits from ScannerPrinter {
}
注意 Scanner 和 Printer 类都实现了名为 start 方法。
那么问题来了,Copier继承哪个start?是Scanner的还是Printer的?肯定不可能同时继承啊。
菱形继承的解决
解决方案很简单:不要这样做。
没错。大多数面向对象都不让你这么干。
但是,但是……要是必须这样建模该怎么办?我需要重用!
那就必须使用包含和委托
    
Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
  function start() {
  }
}
Class Printer inherits from PoweredDevice {
  function start() {
  }
}
Class Copier {
  Scanner scanner
  Printer printer
  function start() {
    printer.start()
  }
}
注意现在 Copier 类包含一个 Printer 实例和一个 Scanner 实例。然后将 start 函数委托给 Printer 类的实现。要委托给 Scanner 也很简单。
这个问题是继承这根支柱上的另一条裂缝。
脆弱的基类问题
好吧,那我尽量使用较浅的类层次结构,并保证里面没有环,这样就不会出现菱形继承了。
似乎一切都解决了。直到我们发现……
我前一天工作得好好的代码今天出错了!关键是,我没有改任何代码!
嗯也许是个 bug……但等等……的确有些改动……
但改动的不是我的代码。似乎改动来自我继承的那个类。
为什么基类的改动会破坏我的代码?
原来是这样……
看看下面这个基类(用Java写的,但就算你不懂Java,应该也很容易看懂):
    
import java.util.ArrayList;

public class Array
{
  private ArrayList<Object> a = new ArrayList<Object>();

  public void add(Object element)
  
{
    a.add(element);
  }

  public void addAll(Object elements[])
  
{
    for (int i = 0; i < elements.length; ++i)
      a.add(elements[i]); // this line is going to be changed
  }
}
重要提示:注意加了注释的那一行。稍后这行的改动将会导致别的东西出错。 
这个类的接口上有两个函数:add() 和 addAll()。add() 函数负责添加一个元素,addAll() 函数会调用 add 函数添加多个元素。 
下面是继承的类:
    
public class ArrayCount extends Array
{
  private int count = 0;

  @Override
  public void add(Object element)
  
{
    super.add(element);
    ++count;
  }

  @Override
  public void addAll(Object elements[])
  
{
    super.addAll(elements);
    count += elements.length;
  }
}
ArrayCount类是通用的Array类的特化。两者行为上的唯一区别就是ArrayCount会维护一个count,记录元素的个数。
我们来仔细看看这两个类。
Array的add()给局部的ArrayList添加一个元素。
Array的addAll()针对每个元素调用局部的ArrayList的add方法。
ArrayCount的add()调用父类的add()然后增加count。
ArrayCount的addAll()调用父类的addAll()然后给count增加相当于元素个数的数。
一切都很正常。
现在是出问题的地方。基类中加注释的那行代码现在改成这样:
    
public void addAll(Object elements[])
  
{
    for (int i = 0; i < elements.length; ++i)
      add(elements[i]); // this line was changed
  }
从基类的作者的角度来看,这个类实现的功能完全没有变化。而且所有自动化测试也都通过来了。
但是基类的作者忘记了继承的类。而继承类的作者被错误吵醒了。
现在ArrayCount的addAll()调用父类的addAll(),后者在内部调用add(),而add()被继承类重载了。
因此,每次继承类的add()被调用时,count都会增加,然后在继承类的addAll()被调用时再次增加。
count被增加了两次。
既然会发生这种现象,那么继承类的作者必须清楚基类是怎样实现的。而且,基类的每个改动必须要通知所有继承类的作者,因为这些改动可能会以不可预知的方式破坏继承类。
唉!这个巨大的裂隙威胁到了整个继承支柱的稳定。
脆弱的基类的解决方法
这个问题还得要包含和委托来解决。
使用包含和委托,可以从白盒编程转到黑盒编程。白盒编程的意思是说,写继承类时必须要了解基类的实现。
而黑盒编程可以完全无视基类的实现,因为不可能通过重载函数的方式向基类注入代码。只需要关注接口即可。
这种趋势太讨厌了……
继承本应带来最好用的重用。
在面向对象语言中实现包含和委托并不容易。它们是为了继承方便而设计的。
如果你和我一样,你就会开始反思这个继承了。但更重要的是,这些问题应当引起你对于通过层次结构进行分类的反思。
层次结构的问题
每到一个新公司时,我都要为在哪儿保存公司文档(即员工手册)而纠结。
是应该建一个Documents文件夹,然后在里面建个Company呢?
还是应该建个Company文件夹,然后在里面建个Documents呢?
两者都可以。但哪个是正确的?哪个更好?
层次分类的思想是因为基类(父类)更通用,继承类(子类)更专用。沿着继承链越往下走,概念就越专用(见上面的形状层次)。
但如果父节点和子节点能随意交换位置,那么显然这种模型是有问题的。
层次结构的解决
真正的问题出在……
层次分类是错误的。
那层次分类应该用在哪里?
包含关系。
真实世界里有很多包含关系(或者叫做独占关系)的层次结构。
但你找不到层次分类。仔细想一下。面向对象范式是根据充满了各种对象的真实世界建立的。但它用错了模型——层次分类在真实世界中没有类比。
但真实世界里到处都是层次包含关系。层次包含关系的一个非常好的例子就是你的袜子。袜子放在装袜子的抽屉里,然后抽屉包含在衣柜里,衣柜包含在卧室里,卧室包含在房子里,等等。 
硬盘上的目录也是层次包含关系的另一个例子——它们包含文件。
那我们该怎样分类呢?
仔细想一下公司文档,就会发现其实放在哪儿都无所谓。我可以放在Documents目录下或者放在Stuff目录下也可以。
我选择的分类法是标签。我给它加上不同的标签。
    
Document
Company
Handbook
标签是没有顺序或层次的(这同时解决了菱形继承问题)。
标签可以类比为接口,因为同一份文档可以有多种类型。
但既然有了这么多裂缝,估计继承的支柱已经倒塌了。 
再见,继承。

 02  
封装,倒塌的第二根支柱 
乍一看,封装似乎是面向对象编程的第二大好处。
对象状态变量被保护起来防止外部访问,即它们被封装在对象内部。
我们不需要再操心那些可能被不知道谁访问的全局变量。
封装是变量的保险柜。
封装太伟大了!
封装万岁…… 
直到你遇到了这个问题……
引用问题
为了提高效率,对象传递给函数时传递的是引用,而不是值。
也就是说,函数不会传递对象本身,而是传递指向对象的一个引用或指针。
如果一个对象的引用被传递给另一个对象的构造函数,构造函数就能将这个对象引用放到私有变量中,用封装保护起来。
但这个传递的对象不是安全的!
为什么不是?因为其他代码也可能拥有指向该对象的指针,比如调用构造函数的那段代码。它必须有指向对象的引用,否则没办法传递给构造函数。
引用的解决
构造函数必须要复制传递过来的对象。而且不能是浅复制,必须是深复制,即传入的对象内包含的所有对象和所有对象中包含的所有对象……都必须要复制。
完全没有效率。
而且更糟糕的是,并非所有对象都能复制的。一些拥有操作系统资源的对象,最好的情况是复制无效,最糟糕的情况是根本不可能复制。
所有主流面向对象语言都有这个问题。 
再见,封装。

 03  
多态,倒塌的第三根支柱

多态是面向对象的三位一体中永远被人抛弃的那一位。
就像是三人组中的Larry Fine。
不管他们去哪儿都会带着他,但他永远是配角。
并不是因为多态不好,而是因为实现多态并不需要面向对象语言。
接口也能实现多态,而且不需要面向对象的负担。
而且,接口也不会限制你能混入的不同行为的数目。 
所以,无需多言,我们可以告别面向对象的多态,去迎接基于接口的多态吧。

 04  
破碎的承诺

当然,面向对象在早期承诺了许多。而直到今天,这些承诺依然在教室里、博客上和网上资源中传授给青涩的程序员们。
我花了多年才意识到面向对象的谎言。以前我也曾经青涩,曾经轻信。
然后我发现被骗了。
再见,面向对象编程。

 05  
那该怎么办?

去拥抱函数式编程吧。过去几年我用得非常舒服。
但话说在先,我并没有给你做出任何承诺。眼见为实。
一朝被蛇咬十年怕井绳。
你懂的!


-END-


欢迎关注@面包板社区
及时收看工程师技术干货
↓↓↓


#推荐阅读#

  • 工程师不得不知的PCB基本常识

  • MEMS陀螺仪工作原理

  • 四十年大戏——光刻机资本局

  • 如何使用示波器进行射频信号测试(深度好文)

  • 高人图解高速电路PCB回流路径

  • 警惕!CAF效应导致PCB漏电

  • 要吃透MOS管,看这个就够了!

  • 一个故事讲完CPU的工作原理

  • 深入了解上拉电阻和下拉电阻

  • 电容是如何工作的,这个动画看后基本懂了

  • 牛人教你开关电源各功能部分原理分析、计算与选型

  • MOSFET的驱动技术有哪些?图文并茂告诉你真相!

  • 电路板调不出来,一怒之下老子一刀劈开!

  • PCB制板基础知识

  • 电子工程师去大公司还是小公司?软件和硬件谁更重要?


点击阅读原文,免费申请开发板(共50枚)


面包板社区 面包板社区——中国第一电子人社交平台 面包板社区是Aspencore旗下媒体,整合了电子工程专辑、电子技术设计、国际电子商情丰富资源。社区包括论坛、博客、问答,拥有超过250万注册用户,加入面包板社区,从菜鸟变大神,打造您的电子人脉社交圈!
评论
  •         近日,广电计量在聚焦离子束(FIB)领域编写的专业著作《聚焦离子束:失效分析》正式出版,填补了国内聚焦离子束领域实践性专业书籍的空白,为该领域的技术发展与知识传播提供了重要助力。         随着芯片技术不断发展,芯片的集成度越来越高,结构也日益复杂。这使得传统的失效分析方法面临巨大挑战。FIB技术的出现,为芯片失效分析带来了新的解决方案。它能够在纳米尺度上对芯片进行精确加工和分析。当芯
    广电计量 2025-02-28 09:15 116浏览
  • 在物联网领域中,无线射频技术作为设备间通信的核心手段,已深度渗透工业自动化、智慧城市及智能家居等多元场景。然而,随着物联网设备接入规模的不断扩大,如何降低运维成本,提升通信数据的传输速度和响应时间,实现更广泛、更稳定的覆盖已成为当前亟待解决的系统性难题。SoC无线收发模块-RFM25A12在此背景下,华普微创新推出了一款高性能、远距离与高性价比的Sub-GHz无线SoC收发模块RFM25A12,旨在提升射频性能以满足行业中日益增长与复杂的设备互联需求。值得一提的是,RFM25A12还支持Wi-S
    华普微HOPERF 2025-02-28 09:06 143浏览
  • 更多生命体征指标风靡的背后都只有一个原因:更多人将健康排在人生第一顺位!“AGEs,也就是晚期糖基化终末产物,英文名Advanced Glycation End-products,是存在于我们体内的一种代谢产物” 艾迈斯欧司朗亚太区健康监测高级市场经理王亚琴说道,“相信业内的朋友都会有关注,最近该指标的热度很高,它可以用来评估人的生活方式是否健康。”据悉,AGEs是可穿戴健康监测领域的一个“萌新”指标,近来备受关注。如果站在学术角度来理解它,那么AGEs是在非酶促条件下,蛋白质、氨基酸
    艾迈斯欧司朗 2025-02-27 14:50 400浏览
  • 在2024年的科技征程中,具身智能的发展已成为全球关注的焦点。从实验室到现实应用,这一领域正以前所未有的速度推进,改写着人类与机器的互动边界。这一年,我们见证了具身智能技术的突破与变革,它不仅落地各行各业,带来新的机遇,更在深刻影响着我们的生活方式和思维方式。随着相关技术的飞速发展,具身智能不再仅仅是一个技术概念,更像是一把神奇的钥匙。身后的众多行业,无论愿意与否,都像是被卷入一场伟大变革浪潮中的船只,注定要被这股汹涌的力量重塑航向。01为什么是具身智能?为什么在中国?最近,中国具身智能行业的进
    艾迈斯欧司朗 2025-02-28 15:45 221浏览
  • 一、VSM的基本原理震动样品磁强计(Vibrating Sample Magnetometer,简称VSM)是一种灵敏且高效的磁性测量仪器。其基本工作原理是利用震动样品在探测线圈中引起的变化磁场来产生感应电压,这个感应电压与样品的磁矩成正比。因此,通过测量这个感应电压,我们就能够精确地确定样品的磁矩。在VSM中,被测量的样品通常被固定在一个震动头上,并以一定的频率和振幅震动。这种震动在探测线圈中引起了变化的磁通量,从而产生了一个交流电信号。这个信号的幅度和样品的磁矩有着直接的关系。因此,通过仔细
    锦正茂科技 2025-02-28 13:30 100浏览
  • 振动样品磁强计是一种用于测量材料磁性的精密仪器,广泛应用于科研、工业检测等领域。然而,其测量准确度会受到多种因素的影响,下面我们将逐一分析这些因素。一、温度因素温度是影响振动样品磁强计测量准确度的重要因素之一。随着温度的变化,材料的磁性也会发生变化,从而影响测量结果的准确性。因此,在进行磁性测量时,应确保恒温环境,以减少温度波动对测量结果的影响。二、样品制备样品的制备过程同样会影响振动样品磁强计的测量准确度。样品的形状、尺寸和表面处理等因素都会对测量结果产生影响。为了确保测量准确度,应严格按照规
    锦正茂科技 2025-02-28 14:05 134浏览
  • RGB灯光无法同步?细致的动态光效设定反而成为产品客诉来源!随着科技的进步和消费者需求变化,电脑接口设备单一功能性已无法满足市场需求,因此在产品上增加「动态光效」的形式便应运而生,藉此吸引消费者目光。这种RGB灯光效果,不仅能增强电脑周边产品的视觉吸引力,还能为用户提供个性化的体验,展现独特自我风格。如今,笔记本电脑、键盘、鼠标、鼠标垫、耳机、显示器等多种电脑接口设备多数已配备动态光效。这些设备的灯光效果会随着音乐节奏、游戏情节或使用者的设置而变化。想象一个画面,当一名游戏玩家,按下电源开关,整
    百佳泰测试实验室 2025-02-27 14:15 137浏览
  • 1,微软下载免费Visual Studio Code2,安装C/C++插件,如果无法直接点击下载, 可以选择手动install from VSIX:ms-vscode.cpptools-1.23.6@win32-x64.vsix3,安装C/C++编译器MniGW (MinGW在 Windows 环境下提供类似于 Unix/Linux 环境下的开发工具,使开发者能够轻松地在 Windows 上编写和编译 C、C++ 等程序.)4,C/C++插件扩展设置中添加Include Path 5,
    黎查 2025-02-28 14:39 140浏览
  •           近日受某专业机构邀请,参加了官方举办的《广东省科技创新条例》宣讲会。在与会之前,作为一名技术工作者一直认为技术的法例都是保密和侵权方面的,而潜意识中感觉法律有束缚创新工作的进行可能。通过一个上午学习新法,对广东省的科技创新有了新的认识。广东是改革的前沿阵地,是科技创新的沃土,企业是创新的主要个体。《广东省科技创新条例》是广东省为促进科技创新、推动高质量发展而制定的地方性法规,主要内容包括: 总则:明确立法目
    广州铁金刚 2025-02-28 10:14 103浏览
  • 应用趋势与客户需求,AI PC的未来展望随着人工智能(AI)技术的日益成熟,AI PC(人工智能个人电脑)逐渐成为消费者和企业工作中的重要工具。这类产品集成了最新的AI处理器,如NPU、CPU和GPU,并具备许多智能化功能,为用户带来更高效且直观的操作体验。AI PC的目标是提升工作和日常生活的效率,通过深度学习与自然语言处理等技术,实现更流畅的多任务处理、实时翻译、语音助手、图像生成等功能,满足现代用户对生产力和娱乐的双重需求。随着各行各业对数字转型需求的增长,AI PC也开始在各个领域中显示
    百佳泰测试实验室 2025-02-27 14:08 255浏览
  • 美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?‌美国加州CEC能效认证与美国DOE能效认证在多个方面存在显著差异‌。认证范围和适用地区‌CEC能效认证‌:仅适用于在加利福尼亚州销售的电器产品。CEC认证的范围包括制冷设备、房间空调、中央空调、便携式空调、加热器、热水器、游泳池加热器、卫浴配件、光源、应急灯具、交通信号模块、灯具、洗碗机、洗衣机、干衣机、烹饪器具、电机和压缩机、变压器、外置电源、消费类电子设备
    张工nx808593 2025-02-27 18:04 120浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦