热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【译】再见,面向对象编程(一)

CharlesScalfani原文:我使用面向对象语言编程已经十几年了。我是用的第一个OO语言是C+

Charles Scalfani

原文: https://medium.com/@cscalfani...

我使用面向对象语言编程已经十几年了。我是用的第一个OO语言是C++,然后是Smalltak,最后是.NET和Java。

我迫切地想从面向对象的三大支柱,集成,封装和多态上得到收益。

我急于从这个到我面前的新领地得到对于重用的承诺。

我对于将现实对象映射到类的想法非常兴奋,并希望整件事能平滑迁移。

我想太多了。

继承,第一个跌落的支柱

最早,继承看起来是面向对象范式的最大收益。所有对新手灌输的关于形状继承的简化例子看起来逻辑上很合理。

【译】再见,面向对象编程(一)

我照单全收并且发现了新东西。

香蕉猴子雨林问题

我带着信仰和需要解决的问题,开始构建类继承和写代码,一切都很好。

我永远不会忘记那一天,当我打算开始从一个现有类使用继承来重用的时候,这是我一直在等待的时刻。

一个新项目来了,我想到在我上一个工程里的那个类。

没问题,重用来搞定。我从老工程里找到那个类并拷过来使用。

但是。。。 不只是那个类。我需要父类。但。。 但先这样。

啊。。。等下。。。看起来我们需要这个父类的父类。。。然后。。。 我们需要所有父类。好吧。。好吧。。我来解决这个。没问题。

我去。现在不能编译。为什么?哦,我知道了。。。 这个对象包含了其他对象。所以我也需要那些。 没问题。

等等。。。我不只是需要那个对象。我需要对象的父类和他父类的父类,然后每个包含的对象和他们所有的父类。。。

晕。

Joe Armstrong,Erlang之父曾说过:

面向对象语言的问题是他们隐式的包含了他们周围的环境。你需要一个香蕉但是你得到的是一个拿着香蕉的大猩猩和整个雨林。

香蕉猴子雨林解决方案

我可以通过不写太深的继承来解决这个问题。但复用的关键就是继承,任何我对这个机制上的限制都直接限制了重用,是吧?

是的。

所以可怜的面向对象程序员,who’s had a healthy helping of the Kool-aid, to do?

组合和委托,后面说这个。

钻石问题

以下问题迟早会遇到,取决于使用的语言。

【译】再见,面向对象编程(一)

大部分OO语言不支持这个,尽管这个看起来符合逻辑。让OO语言支持这个有多难?

想象下以下伪代码:

Class PoweredDevice {
}
Class Scanner inherits from PoweredDevice {
  function start() {
  }
}
Class Printer inherits from PoweredDevice {
  function start() {
  }
}
Class Copier inherits from Scanner, Printer {
}

注意Scanner类和Printer类都实现了一个start功能。

所以Copier累继承了哪一个start功能?Scanner?还是Printer?不可能两个都实现。

钻石问题的解决方案

方案很简单。不要这么做。

是的。大部分OO语言不让你这么做。

但是,如果我的建模就是这样呢?我需要我的重用!

那么你必须使用组合和委托。

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 a = new ArrayList();
 
  public void add(Object element)
  {
    a.add(element);
  }
 
  public void addAll(Object elements[])
  {
    for (int i = 0; i  
 

重要 :注意注释的那段代码。这段代码后面的变更会破坏逻辑。

这个类的接口有两个功能, 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。

ArrayCount add()调用父类的add()并且增加数量count。

ArrayCount addAll()调用父类的addAll()然后根据元素的数量增加数量count。

目前看起来都正常。

现在打破逻辑了。基类注释的代码变更成以下这样:

public void addAll(Object elements[])
  {
    for (int i = 0; i  
 

基类所有者关心的部分,功能还是按设想一样运转正常。并且所有自动化测试仍然可以通过。

但所有者显然没有关注到派生类。所以派生类的作者被粗暴干扰了。

现在ArrayCount addAll()调用父类的addAll(),其内部调用add()的逻辑已经被派生类覆盖了。

这样会导致数量count在每次派生类调用add()时增加,然后在派生类调用addAll()时再被增加一次。

这被计数了两次。

如果是这样,并且已经发生了,派生类的作者必须知道积累是被如何实现的。他们必须在每次基类变更时被通知到,因为这可能会导致派生类在不可预见的情况下工作。

太糟了!这个巨大的问题永久影响了继承范式的稳定性。

脆弱的基类解决方案

这次一样,包含和委托可以解决。

使用包含和委托,我们从白盒编程转化成黑盒编程。白盒编程时,我们需要关注基类的实现。

黑盒编程时,由于我们无法通过覆盖基类方法的方式来注入代码,我们可以完全忽略其实现。我们只需要关心接口。

这个趋势有点危险。。。

继承应该是重用最重要的手段。

OO语言没有设计成让包含和委托方便使用。他们是被设计成让继承方便易用。

如果你像我一样,你会开始对这个继承的问题开始惊奇。但更重要的是,这会让你对于继承的信心开始动摇。

继承问题

每次当我进入一家新公司,我都会对于找个地方放我公司文档的地方开始纠结,比如,员工手册。

我是建一个目录叫“文档”然后在里面建个目录叫“公司”?

或者我建一个目录叫“公司”然后在里面建个目录叫“文档”?

都可以。但是哪一个是正确的? 是最好的?

目录继承的想法是基类(父母)更加通用,派生类(子类)会更加具体。而且我们自己会在继承链上做更加具象化的版本。(看上面形状继承的例子)

但当一个父类和子类可以互相调换位置时,这个模型明显哪里出了问题。

继承问题解决方案

现在的问题是。。。

分类继承不工作了。

所以继承方式好在哪里?

包含。

如果你看下现实世界,你可以看到包含(或排他所有权)继承到处都是。

而你找不到的是分类继承。让那个先等一会。面向对象范式来源于于现实世界,对象被另一个对象填入。但他使用了一个有问题的模型。分类继承,没有现实世界的基础。

现实世界使用的是包含继承。一个容器包含继承的很好的例子是你的袜子。他们在袜子的抽屉里,然后被你衣服的抽屉包进去,然后又被你的卧室包含,然后又被你的房子包含。

你硬盘的目录是另一个容器包含继承的例子。他们保存文件。

所以我们如何对他们分类?

如果你考虑下公司目录,其实我放在哪里没什么太大关系。我可以把他们放在一个叫“文档”的目录或放在一个叫“东西”的目录。

我分类的方式是使用tag标签。我使用以下标签来给文件打标:

文档
公司
手册

标签没有顺序或继承。(这也解决了钻石问题)

tag与接口类似,你可以有多种类型与文档关联。

看到这么多问题,看起来继承范式已经完了。

再见,继承。

微信公众号「麦芽面包」,id「darkjune_think」

开发者/科幻爱好者/硬核主机玩家/业余翻译家/书虫

交流Email: zhukunrong@yeah.net


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 我们


推荐阅读
  • 在尝试对 QQmlPropertyMap 类进行测试驱动开发时,发现其派生类中无法正常调用槽函数或 Q_INVOKABLE 方法。这可能是由于 QQmlPropertyMap 的内部实现机制导致的,需要进一步研究以找到解决方案。 ... [详细]
  • 非线性门控感知器算法的实现与应用分析 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • QT框架中事件循环机制及事件分发类详解
    在QT框架中,QCoreApplication类作为事件循环的核心组件,为应用程序提供了基础的事件处理机制。该类继承自QObject,负责管理和调度各种事件,确保程序能够响应用户操作和其他系统事件。通过事件循环,QCoreApplication实现了高效的事件分发和处理,使得应用程序能够保持流畅的运行状态。此外,QCoreApplication还提供了多种方法和信号槽机制,方便开发者进行事件的定制和扩展。 ... [详细]
  • 本文介绍了UUID(通用唯一标识符)的概念及其在JavaScript中生成Java兼容UUID的代码实现与优化技巧。UUID是一个128位的唯一标识符,广泛应用于分布式系统中以确保唯一性。文章详细探讨了如何利用JavaScript生成符合Java标准的UUID,并提供了多种优化方法,以提高生成效率和兼容性。 ... [详细]
  • 本文介绍了如何使用Python的Paramiko库批量更新多台服务器的登录密码。通过示例代码展示了具体实现方法,确保了操作的高效性和安全性。Paramiko库提供了强大的SSH2协议支持,使得远程服务器管理变得更加便捷。此外,文章还详细说明了代码的各个部分,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 利用REM实现移动端布局的高效适配技巧
    在移动设备上实现高效布局适配时,使用rem单位已成为一种流行且有效的技术。本文将分享过去一年中使用rem进行布局适配的经验和心得。rem作为一种相对单位,能够根据根元素的字体大小动态调整,从而确保不同屏幕尺寸下的布局一致性。通过合理设置根元素的字体大小,开发者可以轻松实现响应式设计,提高用户体验。此外,文章还将探讨一些常见的问题和解决方案,帮助开发者更好地掌握这一技术。 ... [详细]
  • 大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式
    大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • 在 Axublog 1.1.0 版本的 `c_login.php` 文件中发现了一个严重的 SQL 注入漏洞。该漏洞允许攻击者通过操纵登录请求中的参数,注入恶意 SQL 代码,从而可能获取敏感信息或对数据库进行未授权操作。建议用户尽快更新到最新版本并采取相应的安全措施以防止潜在的风险。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 观察 | 求职体验:收到录用通知的公司通常不深究技术细节,而那些详细追问的公司往往没有后续进展
    观察 | 求职体验:收到录用通知的公司通常不深究技术细节,而那些详细追问的公司往往没有后续进展 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
author-avatar
皮蓬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有