哪些语言能更优雅地实现装饰器模式? - frankel


在这篇文章中,我想描述如何向已经存在代码中添加新行为,所有主流语言都提供这样的功能, Java 是唯一在这方面没有提供任何内容的语言。解释型语言允许扩展外部 API,而编译型语言则不允许——Kotlin 是个例外。
 
JavaScript
可以轻松地将属性(状态或行为)添加到原来的对象中:

Object.defineProperty(String.prototype, "toTitleCase", {
    value: function toTitleCase() {
        return this.replace(/\w\S*/g, function(word) {
            return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
        });
    }
});

console.debug(
"OncE upOn a tImE in thE WEst".toTitleCase());

 
Ruby
在 Ruby 生态系统中,向现有类添加方法或属性是非常标准的。我发现了两种向 Ruby 中现有类型添加方法的机制:
  1. 使用class_eval:在 mod 的上下文中评估字符串或块,除了在给定块时,常量/类变量查找不受影响。这可用于向类添加方法
  2. 只需在现有类上实现该方法。

这是第二种方法的代码:
class String
  def to_camel_case()
    return self.gsub(/\w\S*/) {|word| word.capitalize()}
  end
end

puts "OncE upOn a tImE in thE WEst".to_camel_case()

 
Python
Python 允许您向现有类型添加函数 - 有限制。让我们尝试使用str内置类型:

import re

def to_title_case(string):
    return re.sub(
        r'\w\S*',
        lambda word: word.group(0).capitalize(),
        string)

setattr(str, 'to_title_case', to_title_case)

print("OncE upOn a tImE in thE WEst".to_title_case())

不幸的是,上面的代码在执行过程中失败了:因为str是内置类型,我们不能动态添加行为。我们可以更新代码来应对这个限制:

import re

def to_title_case(string):
    return re.sub(
        r'\w\S*',
        lambda word: word.group(0).capitalize(),
        string)

class String(str):
    pass

setattr(String, 'to_title_case', to_title_case)

print(String("OncE upOn a tImE in thE WEst").to_title_case())

现在可以扩展了String,因为它是我们创建的一个类。当然,它违背了最初的目的:我们必须首先扩展str。因此,它适用于第三方库。
使用解释型语言,向类型添加行为相当容易。然而,Python 已经达到了极限,因为内置类型是用 C 实现的。
 
Java
Java 是一种在 JVM 上运行的静态和强类型编译语言。它的静态特性使得无法向类型添加行为。
解决方法是使用static方法。如果您已经成为 Java 开发人员很长时间了,我相信您可能在职业生涯的早期就见过自定义StringUtils和DateUtils类。这些类看起来像这样:
public class StringUtils {

    public static String toCamelCase(String string) {
        // The implementation is not relevant
    }

   
// Other string transformations here
}

我希望到现在为止,使用 Apache Commons 和 Guava 已经取代了所有这些类:
System.out.println(WordUtils.capitalize("OncE upOn a tImE in thE WEst"));

在这两种情况下,静态方法的使用都会阻止流畅的 API 使用,从而损害开发人员的体验。但是其他 JVM 语言确实提供了令人兴奋的替代方案。
 
Scala
与 Java 一样,Scala 是一种在 JVM 上运行的编译型、静态和强类型语言。它最初的设计目的是在面向对象编程和函数式编程之间架起桥梁。Scala 提供了许多强大的功能。其中,隐式类允许向现有类添加行为和状态。以下是如何将toCamelCase()函数添加到String:
import Utils.StringExtensions

object Utils {
  implicit class StringExtensions(thiz: String) {
    def toCamelCase() = "\\w\\S*".r.replaceAllIn(
      thiz,
      { it => it.group(0).toLowerCase().capitalize }
    )
  }
}

println(
"OncE upOn a tImE in thE WEst".toCamelCase())

Scala 3使用更合适的语法保持相同的功能:implicit
extension(thiz: String)
  def toCamelCase() = "\\w\\S*".r.replaceAllIn(
    thiz,
    { it => it.group(0).toLowerCase().capitalize }
  )

请注意,编译后的字节码在这两种情况下都与 Java 的静态方法方法有些相似。然而,API 的使用是流畅的,因为您可以一个接一个地链接方法调用。
 
kotlin
与 Java 和 Scala 一样,Kotlin 是一种在 JVM 上运行的编译型、静态和强类型语言。其他几种语言,包括 Scala,启发了它的设计。
我的观点是 Scala 比 Kotlin 更强大,但权衡是额外的认知负担。相反,Kotlin 有一种轻量级的方法,更实用。这是Kotlin 版本

fun String.toCamelCase() = "\\w\\S*"
    .toRegex()
    .replace(this) {
        it.groups[0]
            ?.value
            ?.lowercase()
            ?.replaceFirstChar { char -> char.titlecase(Locale.getDefault()) }
            ?: this
    }

println(
"OncE upOn a tImE in thE WEst".toCamelCase())

 
Rust
最后但并非最不重要的在我们的列表中,Rust 是一种编译语言,静态和强类型。它最初旨在生成本地二进制文件。然而,通过相关配置,它还允许生成Wasm。
有趣的是,虽然是静态类型,但 Rust 还允许扩展第三方 API,如下面的代码所示:

trait StringExt {                                                  
    fn to_camel_case(&self) -> String;
}

impl StringExt for str {                                           
    fn to_camel_case(&self) -> String {
        let re = Regex::new("\\w\\S*").unwrap();
        re.captures_iter(self)
            .map(|capture| {
                let word = capture.get(0).unwrap().as_str();
                let first = &word[0..1].to_uppercase();
                let rest = &word[1..].to_lowercase();
                first.to_owned() + rest
            })
            .collect::<Vec<String>>()
            .join(
" ")
    }
}

println!(
"{}", "OncE upOn a tImE in thE WEst".to_camel_case());

Trait 实现有一个限制:我们的代码必须至少声明 trait 或结构之一。您不能为现有结构实现现有特征。