为了账号安全,请及时绑定邮箱和手机立即绑定

面向对象编程与函数式编程:封装、状态变化与改进设计

面向对象编程(OOP)一直是软件开发的基石,促进了封装、模块化和代码重用。然而,正如Michael Feathers所说的,

"OO(面向对象编程)通过把移动的部分封装起来使代码更易理解,而 FP(函数式编程)通过减少移动的部分使代码更易理解,从而使代码更易理解。"

这种视角挑战了传统的面向对象编程思维方式,并鼓励开发者们探索函数式编程(FP)原则如何改善他们的设计决策。

但在我们进一步探讨这个范式转变之前,让我们先来看看迈克尔·费哲斯和他的贡献。

迈克尔·费泽斯是谁呀?

迈克尔·费泽斯是广受尊敬的人,在软件开发界以其在软件设计、代码重构和处理遗留代码方面的专长而著称。

他的书《与遗留代码有效工作》https://www.amazon.in/Working-Effectively-Legacy-Robert-Martin/dp/0131177052 已经成为处理复杂遗留代码库的开发人员的指南针。

Feathers对面向对象编程和函数式编程的见解强调了封装和不可变性之间的微妙平衡,提供了一种实用的方法来处理代码复杂度。

面对“活动部分”的挑战

在OOP里,核心思想是将数据和操作它的函数绑在一起。

然而,当对象的方法改变对象的内部状态时,就会出现一个重要问题。

这种突变带来了不可预测性,让调试变得更棘手。

例如,考虑一个典型的类中的方法,该方法用于修改内部状态。

    class Counter {
      constructor() {
        this.value = 0;
      }
      increment() {
        this.value++;
      }
    }

进入全屏|退出全屏

increment 方法会改写 this.value,这在多线程环境或是调试时发现状态变化不一致时可能会有点麻烦。

相比之下,一种更实用的方法会返回一个新的实例,而不是修改现有的实例。

下面是一个名为 `Counter` 的类,它主要用于计数功能。这个类有一个构造函数,可以接受一个默认值为 0 的参数。构造函数将传入的值赋给类的实例变量 `value`。此外,这个类中还定义了一个 `increment` 方法,该方法会返回一个新的 `Counter` 实例,其值为当前实例的 `value` 值加 1。
    class Counter {
      constructor(value = 0) {
        this.value = value;
      }
      increment() {
        return new Counter(this.value + 1);
      }
    }

全屏 退出全屏

在这里,increment() 不修改现有的对象,而是返回一个新实例,值已更新。

这样的设计选择提高了可预测性和线程安全。

性能方面的影响

采用函数式编程方法时,人们常见的问题是效率。

这常常意味着要复制原有的数据,结果可能会导致效率损失。

他举了一个在帧缓冲上画三角形的例子。

"> 你可以编写一个纯粹的 DrawTriangle() 函数,它将帧缓冲区作为参数,并返回一个新的帧缓冲区,其中已经绘制了三角形。不要那样做。"

在性能至关重要的情况下,直接处理内存最有效。

不过,现代多线程应用常常受益于不可变对象,因为这能确保更安全可靠的并行处理。

重新思考面向对象设计

羽 feather 建议我们分析我们的代码库以识别可变状态的依赖关系。一些实际的建议包括:

  • 跟踪外部状态: 记录每个函数可以访问和修改的状态。这种做法可以提高代码的清晰度和可维护性。
  • 偏好纯函数: 确保输出仅由输入决定而不修改共享状态。
  • 修改工具类方法: 将可以自我修改的方法改为返回新副本的方法,如果可行的话。
  • 强调const: 尽量使用const来防止意外修改并强制不可变性。

考虑下面的例子,我们将在下面的例子中使用这样的函数式原则来转换列表的转换过程。

    // 面向对象的方式
    class NamesList {
      constructor(names) {
        this.names = names;
      }
      toUpperCase() {
        this.names = this.names.map(name => name.toUpperCase());
      }
    }

    // 函数式的方式
    class NamesList {
      constructor(names) {
        this.names = names;
      }
      toUpperCase() {
        return new NamesList(this.names.map(name => name.toUpperCase()));
      }
    }

全屏模式 退出全屏

通过返回一个新的实例而不是修改现有实例,我们能保持代码的可读性并确保不可变性这一特性。

如图所示:
图片描述

最后的感想。

迈克尔·费泽斯的观点连接了面向对象编程与函数式编程的鸿沟,挑战开发者们思考状态管理与程序结构。

虽然面向对象编程封装了这些可变的部分,函数式编程则尽量减少这些部分,从而降低不可预测行为的可能性。

关键是不要放弃面向对象编程,而是在能带来实际好处的地方融入函数式编程。

"或许如果所有对象只是引用世界状态的只读版本,在一帧结束时复制更新的版本…… 等等,等等……"

这一认识凸显了软件设计的不断演变——采纳功能原则能够带来更易维护、更健壮和更可扩展的系统。


我一直在开发一个特别方便的工具LiveAPI

LiveAPI 可以帮助你只需几分钟即可自动生成所有后端 API 的文档。

借助LiveAPI,您可以快速创建交互式的API文档,让用户可以直接从浏览器中运行API。

图片说明
点击图片查看详细内容

如果你厌烦了手动编写API的文档,这个工具可能能让你的生活更轻松。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消