# 组合与委托:输出组件增强
学习目标:掌握“组合优于继承”的工程实践,通过包装现有输出组件附加日志行为,理解显式委托与语言级委托支持。
前置知识与范围:接口/Trait 与结构体组合。本篇通过为内存输出接收器增加日志前缀,演示装饰与行为委托。
# 问题与边界
设计一个可替换的消息输出组件体系:
- 统一定义写入契约:
write_message(msg: &str)。 - 基础组件
MemorySink:内部维护列表,每次写入将原始文本追加到内存集合中。 - 增强组件
LoggingSink:- 内部持有任意满足写入契约的
inner组件(组合关系)。 - 每次写入时,将消息统一添加前缀
"[LOG] ",然后委托给内部的inner执行实际存储。
- 内部持有任意满足写入契约的
- 验证契约:通过
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++ 通过引用或智能指针组合接口基类,虚析构函数保证内存安全。
# 迁移误区与适用限制
- 滥用类继承替代组合:为了获得日志能力强行让
LoggingSink extends MemorySink,会导致类层次深耦合,无法将日志能力同时复用于后续的FileSink或NetworkSink。 - 委托泄露内部状态:装饰类在对外暴露时应严格遵守接口契约,不应泄露被装饰者的私有内部实现。
# 运行与验证
语言版本基线见示例基线。 所有程序运行后控制台输出均为:
纯文本stored: [LOG] hello
内存接收器最终验证记录完整无误。
# 相关阅读
- 将报告写入不同目标:行为抽象与标准库协议复用
- 继承与重写:类层次与方法覆盖