码迷,mamicode.com
首页 > 其他好文 > 详细

用代码来说明,为什么需要面向扩展的设计

时间:2020-11-13 12:30:53      阅读:7      评论:0      收藏:0      [点我收藏+]

标签:链接   common   空格   pdf   inf   扩展方法   val   规则   htm   

用代码来说明,为什么需要面向扩展的设计

技术图片

在基本的面向对象编程中,你只能直接调用一个类的方法,而这些方法是由这个类的作者定义的,这对于面向用户设计的类来说是没有问题的。此外,在 20 - 30 年前,在大型标准库和开源库被大量复用之前,大部分代码通常是跟自己的代码中的类来一起工作的 —— 也就是你自己的团队或公司维护的代码。然而,在现代代码世界中,我们经常会使用其他人编写的类。

业务逻辑通常大量使用包括字符串和集合等标准库功能、以及第三方库中的一些类,我们受到这些类提供的操作的限制。例如,当我们需要用破折号替换字符串中的空格时,我们会这样编写代码:

string.replace(‘ ‘, ‘-‘)

但是当我们需要将左边的字符串对齐到指定的长度时,我们可能没有现成的方法可用,在这些旧的语言(如 Objective-C、C++、Java 或 JS)中,你需要强制写成这种形式:

leftPad(string, ‘ ‘, length)

这个 leftPad 可能来自一个单独的库1,也可能来自第三方的工具函数集合(比如 Apache Commons),或者在你自己的项目中自行编写。总之,它的调用看起来和字符串类上的内置方法是非常不同的。

为什么会有这样的问题呢?我引用 Java 的作者之一 Guy Steele,他在 1998 年的《成长的语言》论文2中的一段话。

在大多数语言中,用户至少可以定义一些新语法来代表另外一段代码,然后可以很方便地调用这些代码,这种方式可以让新语法看起来像原生调用一样。通过这种方式,用户可以构建一个更大的语言来满足他的需求。
Guy Steele, Growing a Language

他是在批评 APL 缺乏这样的设施,但同样的批评也适用于现代环境下的旧的面向对象语言。你被困在一个类的操作词汇表上,而这个词汇表是原始库的设计者们所设想的,它不能由你来扩展。此外,它也没法被广泛使用的库的维护者随意地扩展,再次引用同一篇论文中的内容作为原因。

编程词汇的一部分适合所有程序员使用,但其他部分仅适合少数几个人。 程序员需要了解学习其所有词汇用法,这并不公平。

现代语言(如 C#、Scala、Rust、Kotlin 和 Swift)通过支持扩展方法解决了这个问题。你可以在不是你控制的类中添加特定领域的扩展方法,这样,你自己的函数可以用类似于内置方法来调用,而你的代码仍然可以像散文一样,流畅的按从左到右的顺序阅读。

string.padLeft(‘ ‘, length)

这个 padLeft 扩展可以在任何地方定义,它是一个很好的编程语言进化的故事。但是,它的意义还不止于此。

一旦一种编程语言支持扩展函数,它就改变了经典面向对象的 API 设计方法。这对于一个从 Java 这样的旧语言,切换到 Kotlin 这样的现代语言的程序员来说,是一个不小的启示,因为扩展函数通常只是作为方便的语法糖3呈现出来。我们还是先看一个带有一堆属性(或 getter 方法)的接口。


interface Obscure {
    val foo: Int
    val bar: Int
    val sum: Int
    val max: Int
    val min: Int
}

它和你在一个典型的商业应用程序中找到的接口或类并无大的区别 —— 有一堆属性和方法。

你能快速掌握这个接口代表了一个什么样的实体吗?它的状态空间是由哪些属性构成的?如果没有额外的文档,要弄清楚这一点并不容易。但是,让我们把这个接口重构成一个核心实体和方便的扩展函数。


interface NotObscure {
    val foo: Int
    val bar: Int
}

val NotObscure.sum: Int
val NotObscure.max: Int
val NotObscure.min: Int

现在,很明显,这个接口的核心功能是由两个整数属性 foo 和 bar 组成的,而其余的 sum、max 和 min 属性只是为了方便起见而提供的,并在这些核心属性的基础上进行计算。不需要再明确地写文档描述这种区别了 —— 从代码的结构中就可以直接看出。

这种面向扩展的设计在 Kotlin 标准库和第三方库中得到了广泛的应用。它是一种强大的设计技术,使用它会有非常好的效果。

这种设计方法有一个副作用。你可能会注意到,Kotlin 代码通常会使用通配符 import,比如 import com.examplease.*。这在 Kotlin 中很方便,因为在 Kotlin 中仅导入一个类是非常少见的。所有有用的、方便的、实用的函数通常都定义在同一个包中,但在类外作为扩展函数定义。

文中链接:

https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/ How one developer just broke Node, Babel and thousands of projects in 11 lines of JavaScript, Chris Williams, 2016
https://www.cs.virginia.edu/~evans/cs655/readings/steele.pdf Growing a Language, Guy Steele, 1998
https://kotlinlang.org/docs/reference/extensions.html Extensions in Kotlin Programming Language

英文原文:
https://medium.com/@elizarov/extension-oriented-design-13f4f27deaee

参考阅读:

  • Grab熔断器设计:如何应对突发打车峰值
  • Netflix云原生微服务设计分析
  • 整洁架构的正确之路
  • code review 的几条规则
  • 探寻繁杂定时任务的解决方案:分布式任务调度系统

本文作者 Roman Elizarov 系 JetBrains Kotlin Library 团队负责人,由高可用架构翻译,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建方式

技术图片

用代码来说明,为什么需要面向扩展的设计

标签:链接   common   空格   pdf   inf   扩展方法   val   规则   htm   

原文地址:https://blog.51cto.com/14977574/2546125

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!