哪些語言能更優雅地實現裝飾器模式? - frankel

banq發表於2021-11-16

在這篇文章中,我想描述如何向已經存在程式碼中新增新行為,所有主流語言都提供這樣的功能, 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 或結構之一。您不能為現有結構實現現有特徵。

相關文章