• Fukans
    2025-06-09 来自北京
    备注:代码中的 "implements" 可以表示继承,也可以表示“隐式实现” // 1. 通知器接口定义 interface Notifier { Send(message: string) -> error } // 2. 具体通知器实现 class EmailNotifier implements Notifier { Send(message: string) -> error { } } class SMSNotifier implements Notifier { Send(message: string) -> error { } } class PushNotifier implements Notifier { Send(message: string) -> error { } } // 3. 通知服务 class NotificationService { // 组合关系:持有Notifier接口实例 notifier: Notifier // 构造时注入具体实现 constructor(notifier: Notifier) { this.notifier = notifier } // 统一发送入口 Send(message: string) -> error { return notifier.Send(message) } // 切换通知器 SetNotifier(newNotifier: Notifier) { this.notifier = newNotifier } }
    展开

    作者回复: 👍。你这伪代码格式不错,至少我看懂了😁

    
    
  • Amosヾ
    2025-06-09 来自广东
    思考题: // 通知器接口 - 所有通知类型必须实现此接口 type Notifier interface { Send(message string) error } // 邮件通知器 type EmailNotifier struct{} func (e *EmailNotifier) Send(message string) error { // 实现邮件发送逻辑 fmt.Println("Sending email:", message) return nil } // 短信通知器 type SMSNotifier struct{} func (s *SMSNotifier) Send(message string) error { // 实现短信发送逻辑 fmt.Println("Sending SMS:", message) return nil } // 推送通知器 type PushNotifier struct{} func (p *PushNotifier) Send(message string) error { // 实现推送发送逻辑 fmt.Println("Sending push:", message) return nil } type NotificationService struct { notifier Notifier // 依赖抽象接口而非具体实现 } // 创建通知服务(依赖注入) func NewNotificationService(notifier Notifier) *NotificationService { return &NotificationService{notifier: notifier} } // 统一发送接口 func (ns *NotificationService) SendNotification(message string) error { return ns.notifier.Send(message) }
    展开

    作者回复: 👍

    
    
  • Amosヾ
    2025-06-09 来自广东
    关于文中锁阐述的“组合带来的优势”有以下疑问: 1.松耦合:修改一个对象的内部实现通常不会影响到使用它的其他对象(只要接口不变),java的继承应该也不会影响吧?①子类重载后,完全不影响;②子类未重载,那么java和go都会影响的吧? 2.高灵活性:这里感觉和多继承差不多啊,像C++、Python都是支持多继承的 3.更好的封装性:C++、Java应该都具备这个特性, 子类能访问父类的protected的属性,go中也可以 4.避免继承层次问题:组合的扁平可以举个例子说明吗?感觉起来和继承差不多

    作者回复: 你的问题非常好,显然是经过了认真的思考。 关于问题1: 这里有个关键点。继承下,子类和父类之间是“白盒复用”,子类可能依赖父类的内部实现细节。如果父类的内部实现变了(即使方法签名没变),有时确实会意外地破坏子类的行为,这就是所谓的“脆弱基类问题”。组合呢,可以理解是“黑盒复用”。外层对象只关心内层对象暴露的接口(或公开方法)。只要内层对象的接口不变,它内部怎么改,外层对象通常不受影响。Go 里面通过接口或者直接嵌入结构体,都是这种思路。子>类重载确实能隔离一些影响,但如果父类某个你没重载但间接依赖的方法行为变了,还是可能出问题。 关于问题2:组合确实能达到类似多继承的效果——从多个源头获取功能。但C++和Python的多继承也带来了复杂性,比如著名的“菱形继承问题”(diamond problem)和方法解析顺序的困扰。Go 通过接口和结构体嵌入,提供了一种更简洁、争议更少的方式来组合行为,避免了多继承的那些经典难 。你可以把多个小行为(通过小接口或小结构体实现)“拼装”到一个大结构体里,灵活性很高,但结构更清晰。 关于问题3:protected 确实是继承体系里为了扩展性而对封装做的一点“让步”,子类能看到父类的一些内部东西。但组合强调的是,被组合的对象本身是完全封装的,它只暴露它想暴露的公共接口。外层对象不能(也不应该)直接触碰内层对象的内部状态,除非内层对象明确提供了方法。Go 的包级私有(小写字母开头)和公开(大写字母开头)提供了封装,但当一个结构体 A 嵌入另一个结构体 B 时,A 默认也只能访问 B 的公开成员,除非 B 主动暴露更多。组合通常能带来更强的封装边界。 关于问题4:想象一下经典 GUI 库的继承:Button is a Control, Control is a Component, Component is an Object... 这就形成了一个很深的继承树。如果你想创建一个带特殊边框的按钮,可能得在继承树的某个地方插入一个 BorderedControl,或者让 Button 再去继承一个 BorderFeature(如果支持多继承)。而用组合呢,你的 MySpecialButton 可以是这样的:它有一个 StandardButtonBehavior (负责点击等),有一个 FancyBorderRenderer (负责画边框),可能还有一个 TooltipProvider。这些部件都是相对独立的,MySpecialButton 把它们“组合”起来。结构上不是 A -> B -> C 的纵向关系,更像是 X = {partA, partB, partC} 的横向“拼装”关系,这就是“扁平化”。并且修改或替换某个部件(比如换个边框样式)通常不影响其他部件,也更容易添加新的、正交的功能。 Go 推崇组合,但并不是说继承一无是处,而是在大型、复杂项目中,过度使用继承容易导致设计僵化、耦合过紧。组合提供了另一种更灵活、更松耦合的思路来构建和复用代码。

    
    