Skip to content

第 6 章:边界 — 一段代码和另一段代码之间,谁说了算

从同一个接口的 8 种写法开始

我们的订单系统需要接多种支付方式:微信支付、支付宝、信用卡。它们做的事情一样——收钱——但实现不同。

这个需求在每门语言里都会写。但你有没有想过:

为什么 Java 里定义一个接口叫 PaymentGateway,TypeScript 里叫 interface 但完全不需要 implements?为什么 Rust 的 trait 可以给别人的类型实现,而 Java 的 interface 不行?

接口不是 "定义一个方法签名" 那么简单。它是语言对 "你有多大权力约束别人" 的回答。


6.1 语法基础:8 种语言如何画线

Python

python
from abc import ABC, abstractmethod
from typing import Protocol

# 方式一:ABC(抽象基类)
class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, order: Order, amount: float) -> bool:
        ...

    @abstractmethod
    def refund(self, order: Order, amount: float) -> bool:
        ...

# 方式二:Protocol(结构化类型,Python 3.8+)
class PaymentGateway(Protocol):
    def charge(self, order: Order, amount: float) -> bool: ...
    def refund(self, order: Order, amount: float) -> bool: ...

class WechatPay:
    def charge(self, order: Order, amount: float) -> bool:
        print(f"微信支付: {amount}")
        return True
    def refund(self, order: Order, amount: float) -> bool:
        return True

# WechatPay 不需要显式声明 implements PaymentGateway
# 只要它有正确的方法签名,它就是 PaymentGateway(鸭子类型 + Protocol)

TypeScript

typescript
interface PaymentGateway {
    charge(order: Order, amount: number): boolean;
    refund(order: Order, amount: number): boolean;
}

// 方式一:类实现接口
class WechatPay implements PaymentGateway {
    charge(order: Order, amount: number): boolean {
        console.log(`微信支付: ${amount}`);
        return true;
    }
    refund(order: Order, amount: number): boolean {
        return true;
    }
}

// 方式二:纯对象满足接口(结构类型——不需要 implements)
const alipay: PaymentGateway = {
    charge(order, amount) { return true; },
    refund(order, amount) { return true; }
};

// 函数接受接口类型
function processPayment(gateway: PaymentGateway, order: Order): void {
    gateway.charge(order, order.amount);
}
// alipay 可以传进去——它满足 PaymentGateway 的形状,即使它没有 implements

Java

java
public interface PaymentGateway {
    boolean charge(Order order, double amount);
    boolean refund(Order order, double amount);

    // Java 8+:接口可以有默认方法
    default String getName() {
        return this.getClass().getSimpleName();
    }
}

// 必须显式 implements
public class WechatPay implements PaymentGateway {
    @Override
    public boolean charge(Order order, double amount) {
        System.out.println("微信支付: " + amount);
        return true;
    }

    @Override
    public boolean refund(Order order, double amount) {
        return true;
    }
}

// 函数接受接口类型
public void processPayment(PaymentGateway gateway, Order order) {
    gateway.charge(order, order.amount());
}

C++

cpp
// C++ 没有 interface 关键字。纯虚类 = 接口
class PaymentGateway {
public:
    virtual bool charge(const Order& order, double amount) = 0;
    virtual bool refund(const Order& order, double amount) = 0;
    virtual ~PaymentGateway() = default;  // 虚析构是必须的
};

// 实现
class WechatPay : public PaymentGateway {
public:
    bool charge(const Order& order, double amount) override {
        std::cout << "微信支付: " << amount << std::endl;
        return true;
    }
    bool refund(const Order& order, double amount) override {
        return true;
    }
};

// C++20:concept 提供了编译期接口
template<typename T>
concept PaymentGatewayConcept = requires(T t, const Order& o, double amt) {
    { t.charge(o, amt) } -> std::convertible_to<bool>;
    { t.refund(o, amt) } -> std::convertible_to<bool>;
};

void processPayment(PaymentGatewayConcept auto& gateway, const Order& order) {
    gateway.charge(order, order.amount);
}

Rust

rust
trait PaymentGateway {
    fn charge(&mut self, order: &Order, amount: f64) -> bool;
    fn refund(&mut self, order: &Order, amount: f64) -> bool;

    // 默认实现
    fn name(&self) -> &str { "Unknown" }
}

// 实现
struct WechatPay;

impl PaymentGateway for WechatPay {
    fn charge(&mut self, order: &Order, amount: f64) -> bool {
        println!("微信支付: {}", amount);
        true
    }
    fn refund(&mut self, order: &Order, amount: f64) -> bool {
        true
    }
}

// 函数接受 trait 约束
fn process_payment(gateway: &mut impl PaymentGateway, order: &Order) {
    gateway.charge(order, order.amount);
}

// 或者用泛型写法
fn process_payment<T: PaymentGateway>(gateway: &mut T, order: &Order) {
    gateway.charge(order, order.amount);
}

Go

go
type PaymentGateway interface {
    Charge(order Order, amount float64) bool
    Refund(order Order, amount float64) bool
}

// 实现:不需要声明 implements!
type WechatPay struct{}

func (w WechatPay) Charge(order Order, amount float64) bool {
    fmt.Printf("微信支付: %f\n", amount)
    return true
}

func (w WechatPay) Refund(order Order, amount float64) bool {
    return true
}

// 函数接受接口
func ProcessPayment(gateway PaymentGateway, order Order) {
    gateway.Charge(order, order.Amount)
}

// WechatPay 可以直接传入——Go 的结构类型检查

Swift

swift
protocol PaymentGateway {
    func charge(order: Order, amount: Double) -> Bool
    func refund(order: Order, amount: Double) -> Bool

    // 协议扩展提供默认实现
    func name() -> String
}

extension PaymentGateway {
    func name() -> String { return String(describing: type(of: self)) }
}

// 实现
struct WechatPay: PaymentGateway {
    func charge(order: Order, amount: Double) -> Bool {
        print("微信支付: \(amount)")
        return true
    }
    func refund(order: Order, amount: Double) -> Bool {
        return true
    }
}

// 函数接受协议类型
func processPayment(gateway: any PaymentGateway, order: Order) {
    // 注意 any:表示 existential 类型,有运行时开销
    gateway.charge(order: order, amount: order.amount)
}

// 或者用泛型(编译期单态化,无运行时开销)
func processPayment<T: PaymentGateway>(gateway: T, order: Order) {
    gateway.charge(order: order, amount: order.amount)
}

Kotlin

kotlin
interface PaymentGateway {
    fun charge(order: Order, amount: Double): Boolean
    fun refund(order: Order, amount: Double): Boolean

    // 默认方法
    fun name(): String = this::class.simpleName ?: "Unknown"
}

// 实现
class WechatPay : PaymentGateway {
    override fun charge(order: Order, amount: Double): Boolean {
        println("微信支付: $amount")
        return true
    }
    override fun refund(order: Order, amount: Double): Boolean {
        return true
    }
}

// 函数接受接口类型
fun processPayment(gateway: PaymentGateway, order: Order) {
    gateway.charge(order, order.amount)
}

语法速查:抽象机制对比

能力PythonTypeScriptJavaC++RustGoSwiftKotlin
接口/抽象类型ABC / Protocolinterfaceinterface纯虚类 / concepttraitinterfaceprotocolinterface
类型系统鸭子类型 + 可选 Protocol结构类型名义类型名义 + concept(结构)名义 + trait结构类型名义 + protocol名义
需要显式声明实现?Protocol:否 / ABC:是继承:是 / concept:否
默认方法ABC 可无(type 可交叉)default 方法(Java 8+)虚函数 = 可trait 默认实现protocol extension接口方法 = 可
扩展函数无(用函数)无(用函数)无(用工具类)无(用自由函数)无(用自由函数)无(用函数)protocol extensionextension function
泛型约束T: ProtocolT extends IT extends Irequires / conceptT: Trait无泛型方法约束T: ProtocolwhereT: Interface

6.2 深度对照一:名义类型 vs 结构类型

这是接口世界最根本的分界线。

名义类型(Nominal Typing)

类型是通过名字来识别的。 你必须显式声明 class WechatPay implements PaymentGateway,否则 WechatPay 就不是 PaymentGateway

Java、Kotlin、Swift、Rust 走这条路。

java
// Java
class WechatPay implements PaymentGateway { ... }
// 没有 "implements PaymentGateway" 这一行,
// 即使有完全一样的方法,也不能当 PaymentGateway 用

优点:意图明确。你看到 implements 就知道 "这个类被设计成这个接口的实现"。

缺点:你不能让别人的类型满足你的接口。如果第三方库的 ThirdPartyPaychargerefund 方法,但它没 implements PaymentGateway,你就不能把它当 PaymentGateway 用——除非你写一个适配器。

结构类型(Structural Typing)

类型是通过形状来识别的。 只要一个类型有正确的方法签名,它就是那个接口。不需要显式声明。

TypeScript、Go 走这条路。Python 的 Protocol 是可选的结构类型。

typescript
// TypeScript
interface PaymentGateway {
    charge(order: Order, amount: number): boolean;
    refund(order: Order, amount: number): boolean;
}

// 这个对象没有 implements PaymentGateway,但形状匹配
const wechatPay = {
    charge(order: Order, amount: number) { return true; },
    refund(order: Order, amount: number) { return true; }
};

function pay(g: PaymentGateway) { ... }
pay(wechatPay);  // 有效!
go
// Go:同样的结构类型
type PaymentGateway interface {
    Charge(Order, float64) bool
    Refund(Order, float64) bool
}

type WechatPay struct{}
func (w WechatPay) Charge(o Order, a float64) bool { return true }
func (w WechatPay) Refund(o Order, a float64) bool { return true }
// WechatPay 没有声明实现 PaymentGateway——但它就是

优点:极度灵活。你可以让你不拥有的类型满足你的接口。这也是为什么 Go 的 io.Reader 只有一个方法——任何有 Read 方法的类型都是 Reader。

缺点:意图不明确。你无法从类型声明上直接看出 "这个 struct 被设计用来做什么"。IDE 的 "查找所有实现" 功能在结构类型系统里更难实现。

Rust 的特别之处:名义 + 孤儿规则

Rust 是名义类型(必须写 impl Trait for Type),但它允许你为别人的类型实现你的 trait(或者为你自己的类型实现别人的 trait)。

rust
// 为第三方类型实现你自己的 trait(合法)
impl MyPaymentExt for ThirdPartyPay { ... }

// 为你自己的类型实现第三方的 trait(合法)
impl ThirdPartyTrait for MyWechatPay { ... }

// 但你不能为第三方的类型实现第三方的 trait!
// impl ForeignTrait for ForeignType { ... }  // 编译错误

这叫 孤儿规则(Orphan Rule)。它的作用是防止两个 crate 分别为同一个 ForeignType 实现同一个 ForeignTrait,导致冲突。这是一个精妙的制度设计——在灵活性和安全性之间找平衡。


6.3 深度对照二:扩展——在不拥有类型的情况下加能力

在 Java 里,你不能给 String 加一个方法。在 Kotlin 里,你可以。

扩展函数(Kotlin)

kotlin
// 给 String 加方法
fun String.isOrderId(): Boolean = this.matches(Regex("^[A-Z]\\d+$"))

// 使用
"A1001".isOrderId()  // true

协议扩展(Swift)

swift
extension String {
    var isOrderId: Bool {
        return self.range(of: "^[A-Z]\\d+$", options: .regularExpression) != nil
    }
}

Rust 的 trait + blanket implementation

rust
trait IsOrderId {
    fn is_order_id(&self) -> bool;
}

impl IsOrderId for String {
    fn is_order_id(&self) -> bool {
        // ...
    }
}

// blanket impl:为所有满足条件的类型实现
impl<T: Display> IsOrderId for T {
    fn is_order_id(&self) -> bool {
        // 默认实现
    }
}

Java / C++ / Go / Python:用自由函数

java
// Java:你只能定义工具方法
public class OrderUtils {
    public static boolean isOrderId(String s) { ... }
}
// 不能写成 "A1001".isOrderId()
cpp
// C++:也是自由函数
bool is_order_id(const std::string& s) { ... }
// 用的时候: is_order_id("A1001"); ——没有方法调用语法
go
// Go:自由函数
func IsOrderID(s string) bool { ... }
python
# Python:自由函数——但你也可以猴子补丁(不推荐)
def is_order_id(s: str) -> bool: ...

扩展函数的存在与否,深刻影响了你如何组织代码。在有扩展的语言里,你可以把辅助方法 "附着" 到类型上,让 IDE 的自动补全帮你发现它们。在没有扩展的语言里,你被迫创建 XxxUtils 类——而这些类往往变成什么都往里扔的垃圾桶。


6.4 深度对照三:泛型——谁能在边界上写约束

泛型是抽象边界的放大器。没有泛型,你只能为具体类型写接口。有泛型,你可以为 "任何满足条件 X 的类型" 写接口。

泛型的三种气质

Java 风格:擦除

java
// Java:泛型在编译后擦除(type erasure)
public <T extends PaymentGateway> void process(T gateway, Order order) {
    gateway.charge(order, order.amount());
    // 运行时不知道 T 是什么——不能写 new T()
}

类型在编译后被擦除成 Object(或上界)。这保持了 JVM 的向后兼容,但限制了运行时反射泛型信息的能力。

C++ 风格:模板 = 编译期代码生成

cpp
// C++:每个不同的 T 产生一份独立的机器码
template<typename T>
void process(T&& gateway, const Order& order) {
    gateway.charge(order, order.amount);
}
// 调用 process(wechatPay, order) 时,编译器为 WechatPay 生成一份专门的 process

零运行时开销,但编译速度慢,二进制体积大。

Rust/Swift/Kotlin 风格:单态化(和 C++ 类似但不完全相同)

rust
// Rust:编译期单态化,但带 trait 约束
fn process<T: PaymentGateway>(gateway: &mut T, order: &Order) {
    gateway.charge(order, order.amount);
}

和 C++ 模板类似——每个类型生成一份代码。但 Rust 的 trait 约束让编译器可以提前检查(而不是像 C++ 模板在实例化时才报错)。

Go 风格:刚加入的泛型(1.18+)

go
// Go 1.18+:轻度泛型
func Process[T PaymentGateway](gateway T, order Order) {
    gateway.Charge(order, order.Amount)
}

Go 的泛型刻意做得很克制——没有模板元编程,没有 constexpr 泛型参数,没有 specialization。和 Go 一贯的哲学一致:泛型解决 80% 的问题就够了,剩余的 20% 用手写代码。


6.5 边界探索:跨语言接口陷阱

陷阱一:Java → Go:把 interface 当成同一种东西

java
// Java:接口很大——可能有 10 个方法
interface OrderRepository {
    Order findById(String id);
    List<Order> findByStatus(String status);
    List<Order> findByDateRange(LocalDate from, LocalDate to);
    void save(Order order);
    void delete(String id);
    // ...
}
go
// Go:惯用的接口很小——通常 1-2 个方法
type OrderReader interface {
    FindByID(id string) (Order, error)
}

type OrderWriter interface {
    Save(order Order) error
}

Go 社区的强烈约定是 "接口要小——最好只有一个方法"。这不是语言强制,但如果你从 Java 带过来大接口的习惯,Go 的代码评审会不断地让你拆。

陷阱二:TypeScript → Java:习惯了结构类型

typescript
// TypeScript:只要形状匹配就行
interface Named { name: string; }
class Person { name: string = ""; age: number = 0; }
const n: Named = new Person();  // OK——结构类型
java
// Java:必须显式声明
interface Named { String name(); }
class Person { public String name() { return ""; } }
// Named n = new Person();  // 编译错误!Person 没有 implements Named

从 TypeScript 到 Java,你会失去 "任何有正确方法的类型就能满足接口" 的灵活性。需要大量适配器代码。

陷阱三:Rust → Go:习惯了 trait bound,到 Go 失去约束

rust
fn process<T: PaymentGateway + Debug>(gateway: &T) { ... }

切到 Go 后,你只能用 interface 做动态分发(有运行时开销),或者用泛型接口(Go 1.18+的泛型不支持方法上的类型参数)。很多在 Rust 里用 trait bound 表达的约束,在 Go 里你只能说 "这是 interface{}"——也就是放弃了类型检查。

陷阱四:Kotlin → Java:从扩展函数回到工具类

Kotlin 的扩展函数让你可以把逻辑自然地附着在类型上。切回 Java 后,你必须创建 XxxUtils 类来放置这些逻辑。如果你习惯了用扩展函数组织代码,回到 Java 会感觉代码在 "散落"。


6.6 洞察:真正让系统腐化的,从来不是烂代码,而是烂边界

如果你只用一门语言设计接口,你不会发现:

接口不是定义 "能做什么" 的合同。接口是定义 "什么是不能碰的" 的隔离带。

在一门语言里,接口只是方法签名的集合。但在 8 门语言的对照中,你会发现接口在替你做三件非常不一样的事:

  1. 隔离变更(Java、Kotlin、Swift):接口让你可以换掉实现而不影响调用方。这是经典面向对象的核心承诺。

  2. 表达约束(Rust、C++ concepts):接口不只是 "你能调用什么",而是 "你被允许做什么"。Rust 的 Send + Sync trait 告诉你这个类型能不能跨线程传递。这不是功能,是安全合约。

  3. 降低耦合(Go):Go 的接口如此之小(通常 1-2 个方法),以至于依赖关系变得极度细粒度。一个函数只需要声明它真正用的那一两个方法,而不是一个庞大的接口。

这三种用途的差异,导致了完全不同的系统演化路径:

Java 团队倾向定义大接口("一个 Repository 什么都能做"),然后通过实现类来处理差异。Go 团队倾向定义小接口("我要什么就声明什么"),然后让 struct 自然地满足多个小接口。Rust 团队在接口里写入安全约束("这个类型能跨线程传吗"),让编译期保证整个系统的边界安全。

如果你从 Java 到 Go 只会说 "Go 的接口更简单",你错过了重点。重点是:Go 的接口体系让依赖方向变得更细粒度——函数只依赖它真正用的东西。这让系统的耦合表面积大幅缩小。

这是一件只有同时看过 Java 的大接口文化和 Go 的小接口文化之后,才能体会的事。


本章小结

  • 名义 vs 结构:接口世界最根本的分界线。 名义类型(Java/Kotlin/Swift/Rust)要求显式声明,给意图和可追踪性。结构类型(TypeScript/Go)给灵活性和解耦。Python 两边都有得选。

  • 扩展函数改变了代码组织的引力。 在 Kotlin 和 Swift 里,辅助逻辑自然地附着在类型上。在 Java 和 Go 里,它们变成了 XxxUtils——而 XxxUtils 是低内聚的重灾区。

  • 泛型不只是代码复用——它是边界上的类型约束。 Rust 的 trait bound、C++ 的 concept、Java 的 <T extends>、Go 的轻量泛型——它们决定你在边界上能说多清楚、能被检查多少。

  • 接口大小不是审美问题。 Java 的大接口和 Go 的小接口反映了两种完全不同的依赖管理哲学。前者方便实现的集中管理,后者降低耦合的表面积。


如果你只用一门语言,你不会学到这个:

好的接口不是把你会的都写进去。好的接口是把你不会的、不该的、不敢的,全部排除在外。而你能排除多少,取决于你的语言给了你多少约束工具。