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

开发 后端
在基本的面向对象编程中,你只能直接调用一个类的方法,而这些方法是由这个类的作者定义的,这对于面向用户设计的类来说是没有问题的。

 [[328143]]

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

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

  1. string.replace(' ''-'

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

  1. leftPad(string, ' ', length) 

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

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

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

Guy Steele, Growing a Language

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

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

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

  1. string.padLeft(' ', length) 

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

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

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

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

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

  1. interface NotObscure { 
  2.     val foo: Int 
  3.     val bar: Int 
  4.  
  5. val NotObscure.sumInt 
  6. val NotObscure.maxInt 
  7. val NotObscure.minInt 

现在,很明显,这个接口的核心功能是由两个整数属性 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

本文转载自微信公众号「 高可用架构」,可以通过以下二维码关注。转载本文请联系 高可用架构公众号。

 

 

责任编辑:武晓燕 来源: 高可用架构
相关推荐

2021-11-12 08:00:00

抽象开发代码

2015-08-03 10:40:45

动效设计优势

2020-03-25 20:01:16

数据中心综合布线技术

2015-10-19 17:57:33

容器OpenStack微服务

2020-07-10 15:18:12

微服务设计模型

2020-02-04 14:41:37

微服务设计DDD

2022-05-07 15:40:30

加密货币乌克兰战争

2011-02-16 09:42:04

DevOps

2023-10-30 11:13:55

2022-09-22 14:49:37

智能建筑物联网

2009-09-07 16:45:54

.NET代码设计

2017-02-14 13:16:50

4G大数据M2M

2017-10-31 11:06:38

NAS存储大数据

2015-04-16 15:42:21

关系型数据库NoSQL

2022-06-28 14:54:26

加密货币数组货币安全

2022-06-07 10:09:20

5GRAN 架构移动通信

2014-07-02 09:56:33

2020-11-30 10:02:27

云计算IT运营工具

2020-04-29 15:30:22

CSP网页前端

2009-06-15 14:59:31

Java代码Java
点赞
收藏

51CTO技术栈公众号