AI Library · 知识库

# 组合与委托:输出组件增强

学习目标:掌握“组合优于继承”的工程实践,通过包装现有输出组件附加日志行为,理解显式委托与语言级委托支持。

前置知识与范围:接口/Trait 与结构体组合。本篇通过为内存输出接收器增加日志前缀,演示装饰与行为委托。

# 问题与边界

设计一个可替换的消息输出组件体系:

  1. 统一定义写入契约:write_message(msg: &str)。
  2. 基础组件 MemorySink:内部维护列表,每次写入将原始文本追加到内存集合中。
  3. 增强组件 LoggingSink:
    • 内部持有任意满足写入契约的 inner 组件(组合关系)。
    • 每次写入时,将消息统一添加前缀 "[LOG] ",然后委托给内部的 inner 执行实际存储。
  4. 验证契约:通过 LoggingSink 写入 "hello" 后,底层的 MemorySink 中精确记录为 "[LOG] hello"。

# 关键差异

语言 组合与委托机制 原生委托关键字支持 接口与多态形态
Rust 包装持有 inner: T 并为包装体实现同名 Trait 无原生关键字,显式转发调用 静态泛型单态化或 Box<dyn Trait>
Go 结构体内嵌字段(Embedding)实现自动方法提升 内嵌接口或结构体自动提升导出方法 隐式接口(鸭子类型)
Java 装饰器模式(Decorator Pattern),显式实现接口 无关键字,手动显式转发 显式 implements
JavaScript 类组合持有引用 手动转发或 ES6 Proxy 动态鸭子类型
TypeScript 接口与类包装 手动转发或 Proxy 静态结构化类型约束
Kotlin 原生类委托语法:class LoggingSink(inner: Sink) : Sink by inner 原生支持 by 关键字 编译器自动生成委托转发桩代码
Python 持有实例并转发或覆盖 __getattr__ 动态转发协议 动态协议
C++ 模板包装或持有抽象基类指针 手动调用转发 虚函数或静态概念(Concepts)

# 各语言实现

# Rust

Rusttrait Sink {
    fn write_msg(&mut self, msg: &str);
}

#[derive(Default)]
struct MemorySink {
    records: Vec<String>,
}

impl Sink for MemorySink {
    fn write_msg(&mut self, msg: &str) {
        self.records.push(msg.to_string());
    }
}

// 组合持有任意泛型 Sink
struct LoggingSink<S: Sink> {
    inner: S,
}

impl<S: Sink> LoggingSink<S> {
    fn new(inner: S) -> Self {
        LoggingSink { inner }
    }
}

impl<S: Sink> Sink for LoggingSink<S> {
    fn write_msg(&mut self, msg: &str) {
        let logged = format!("[LOG] {msg}");
        self.inner.write_msg(&logged); // 委托内部组件
    }
}

fn main() {
    let mut sink = LoggingSink::new(MemorySink::default());
    sink.write_msg("hello");

    assert_eq!(sink.inner.records, vec!["[LOG] hello"]);

    println!("stored: {}", sink.inner.records[0]);
}

Rust 的静态分派组合零运行时开销,内部与外部完全解耦。

# Go

Gopackage main

import "fmt"

type Sink interface {
	WriteMsg(msg string)
}

type MemorySink struct {
	Records []string
}

func (m *MemorySink) WriteMsg(msg string) {
	m.Records = append(m.Records, msg)
}

type LoggingSink struct {
	inner Sink // 组合接口
}

func (l *LoggingSink) WriteMsg(msg string) {
	l.inner.WriteMsg("[LOG] " + msg) // 增加日志并委托
}

func main() {
	mem := &MemorySink{}
	logger := &LoggingSink{inner: mem}
	logger.WriteMsg("hello")

	if len(mem.Records) != 1 || mem.Records[0] != "[LOG] hello" {
		panic("mismatch")
	}

	fmt.Printf("stored: %s\n", mem.Records[0])
}

Go 的接口满足是隐式的,不需要在 MemorySink 声明 implements Sink。

# Java

Javaimport java.util.ArrayList;
import java.util.List;

public class Main {
    public interface Sink {
        void writeMsg(String msg);
    }

    public static class MemorySink implements Sink {
        public final List<String> records = new ArrayList<>();
        @Override
        public void writeMsg(String msg) {
            records.add(msg);
        }
    }

    public static class LoggingSink implements Sink {
        private final Sink inner;
        public LoggingSink(Sink inner) {
            this.inner = inner;
        }

        @Override
        public void writeMsg(String msg) {
            inner.writeMsg("[LOG] " + msg);
        }
    }

    public static void main(String[] args) {
        MemorySink mem = new MemorySink();
        Sink logger = new LoggingSink(mem);
        logger.writeMsg("hello");

        assert mem.records.size() == 1;
        assert "[LOG] hello".equals(mem.records.get(0));

        System.out.println("stored: " + mem.records.get(0));
    }
}

Java 经典的装饰器模式,通过相同的接口实现透明包装与替换。

# JavaScript

JavaScriptconst assert = {
  equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn, errType) => {
    try { fn(); throw new Error("expected throw"); } catch (e) {
      if (e.message === "expected throw") throw e;
      if (errType && !(e instanceof errType)) throw e;
    }
  }
};

class MemorySink {
  records = [];
  writeMsg(msg) {
    this.records.push(msg);
  }
}

class LoggingSink {
  constructor(inner) {
    this.inner = inner;
  }
  writeMsg(msg) {
    this.inner.writeMsg(`[LOG] ${msg}`);
  }
}

const mem = new MemorySink();
const logger = new LoggingSink(mem);
logger.writeMsg("hello");

assert.equal(mem.records.length, 1);
assert.equal(mem.records[0], "[LOG] hello");

console.log(`stored: ${mem.records[0]}`);

JavaScript 利用对象引用的传递轻松组装管道。

# TypeScript

TypeScriptconst assert = {
  equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn: () => void, errType?: unknown) => {
    try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
      if (e instanceof Error && e.message === "expected throw") throw e;
      if (typeof errType === "function" && !(e instanceof errType)) throw e;
    }
  }
};

interface Sink {
  writeMsg(msg: string): void;
}

class MemorySink implements Sink {
  readonly records: string[] = [];
  writeMsg(msg: string): void {
    this.records.push(msg);
  }
}

class LoggingSink implements Sink {
  constructor(private readonly inner: Sink) {}
  writeMsg(msg: string): void {
    this.inner.writeMsg(`[LOG] ${msg}`);
  }
}

const mem = new MemorySink();
const logger: Sink = new LoggingSink(mem);
logger.writeMsg("hello");

assert.equal(mem.records.length, 1);
assert.equal(mem.records[0], "[LOG] hello");

console.log(`stored: ${mem.records[0]}`);

TypeScript 确保被包装的组件与包装者满足统一的 Sink 契约约束。

# Kotlin

Kotlininterface Sink {
    fun writeMsg(msg: String)
}

class MemorySink : Sink {
    val records = mutableListOf<String>()
    override fun writeMsg(msg: String) {
        records.add(msg)
    }
}

// Kotlin 原生支持接口委托语法 `by inner`
class LoggingSink(private val inner: Sink) : Sink {
    override fun writeMsg(msg: String) {
        inner.writeMsg("[LOG] $msg")
    }
}

fun main() {
    val mem = MemorySink()
    val logger = LoggingSink(mem)
    logger.writeMsg("hello")

    check(mem.records.size == 1)
    check(mem.records[0] == "[LOG] hello")

    println("stored: ${mem.records[0]}")
}

Kotlin 的委托机制(Delegation)由语言原生内置,避免了手写大量的机械转发代码。

# Python

Pythonfrom typing import List, Protocol

class Sink(Protocol):
    def write_msg(self, msg: str) -> None: ...

class MemorySink:
    def __init__(self) -> None:
        self.records: List[str] = []

    def write_msg(self, msg: str) -> None:
        self.records.append(msg)

class LoggingSink:
    def __init__(self, inner: Sink) -> None:
        self.inner = inner

    def write_msg(self, msg: str) -> None:
        self.inner.write_msg(f"[LOG] {msg}")

if __name__ == "__main__":
    mem = MemorySink()
    logger = LoggingSink(mem)
    logger.write_msg("hello")

    assert mem.records == ["[LOG] hello"]

    print(f"stored: {mem.records[0]}")

Python 使用 typing.Protocol 实现静态鸭子类型下的结构化检查。

# C++

C++#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <cassert>

struct Sink {
    virtual ~Sink() = default;
    virtual void write_msg(const std::string& msg) = 0;
};

class MemorySink : public Sink {
public:
    std::vector<std::string> records;
    void write_msg(const std::string& msg) override {
        records.push_back(msg);
    }
};

class LoggingSink : public Sink {
private:
    Sink& inner_;

public:
    explicit LoggingSink(Sink& inner) : inner_(inner) {}
    void write_msg(const std::string& msg) override {
        inner_.write_msg("[LOG] " + msg);
    }
};

int main() {
    MemorySink mem;
    LoggingSink logger(mem);
    logger.write_msg("hello");

    assert(mem.records.size() == 1);
    assert(mem.records[0] == "[LOG] hello");

    std::cout << "stored: " << mem.records[0] << "\n";
}

C++ 通过引用或智能指针组合接口基类,虚析构函数保证内存安全。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本stored: [LOG] hello

内存接收器最终验证记录完整无误。

# 相关阅读