代码大全继承派生覆盖子程序,但没有任何操作
派生后覆盖了某个子程序,但在其中没做任何操作,这种情况也值得怀疑这通常表明基类的设计中有错误。
举例来说,假设你有一个Cat(猫)类,它有一个Scratch()(抓)成员函数,可是最终你发现有些猫的爪尖儿没了,不能抓了。你可能想从Cat类派生一个叫 scratchiesscat(不能抓的猫)的类,然后覆盖scratch()方法让它什么都不做。但这种做法有这么几个问题。 它修改了 cat 类的接口所表达的语义,因此破坏了Cat 类所代表的抽象(即接口契约) 当你从它进一步派生出其他派生类时,采用这一做法会迅速失控。如果你又发现有只猫没有尾巴该怎么办?或者有只猫不捉老鼠呢?再或者有只猫不喝牛奶?最终你会派生出一堆类似ScratchlessTaillessMicelessMi1klessCat(不能抓、没尾巴、不捉老鼠、不喝牛奶的猫)这样的派生类来。 采用这种做法一段时间后,代码会逐渐变得混乱而难以维护,因为基类的接口和行为几乎无法让人理解其派生类的行为。 修正这一问题的位置不是在派生类,而是在最初的Cat类中。应该创建一个Claw(爪子)类并让Cat类包含它。问题的根源在于做了所有猫都能抓的假设,因此应该从源头上解决问题,而不是到发现问题的地方修补。
这段描述提到的问题和解决策略涉及到面向对象设计中的几个重要概念,包括里氏替换原则(LSP)、接口的一致性、以及如何正确地使用继承。以下是对这个问题的分析和建议: ### 问题分析 1. **接口一致性**: - 如果 `Cat` 类有一个 `Scratch()` 方法,那么所有 `Cat` 的实例都应该能够执行抓的动作。如果某个 `Cat` 的子类不能抓,这违反了接口的一致性。 2. **继承滥用**: - 通过创建一个不能抓的 `Cat` 子类并覆盖 `Scratch()` 方法使其无效,这种做法滥用了继承。继承应该用来表示 "是一个"(is-a)关系,而不是 "像是一个"(acts-like-a)关系。 3. **过度细分**: - 如果继续这种设计方式,可能会导致大量的子类,每个子类都缺少一些 `Cat` 类的特性,这会导致代码的复杂性和难以维护。 ### 解决策略 1. **重新设计基类**: - 考虑在 `Cat` 类中使用组合,而不是继承。例如,创建一个 `Claw` 类,`Cat` 类包含一个或多个 `Claw` 对象。这样,`Cat` 类的行为就依赖于 `Claw` 对象的状态。 2. **使用条件逻辑**: - 在 `Cat` 类的 `Scratch()` 方法中,添加逻辑来判断是否可以执行抓的动作,例如检查 `Claw` 对象是否存在。 3. **接口契约**: - 确保 `Cat` 类的接口契约清晰明确,所有继承自 `Cat` 的子类都应该遵守这些契约。 4. **使用状态模式**: - 如果 `Cat` 的行为会根据其状态改变,可以使用状态模式来管理这些行为,而不是通过创建大量的子类。 5. **避免过度继承**: - 避免创建过多的子类来表示不同的行为或状态。应该寻找更通用的设计模式或原则来简化设计。 6. **使用多态**: - 如果 `Cat` 类的不同子类需要有不同的行为,可以使用多态性来实现,而不是通过覆盖方法使其无效。 7. **代码重构**: - 如果现有的继承结构已经变得复杂,考虑重构代码,将继承结构替换为更合理的设计模式。 8. **文档和沟通**: - 在团队中确保设计决策有充分的文档记录和沟通,以便所有开发人员理解设计意图和约束。 通过这些策略,可以避免继承层次结构的滥用,并设计出更灵活、更可维护的系统。重要的是要理解继承应该谨慎使用,并且在设计时考虑到所有潜在的变化和扩展。
在Go语言中,我们可以通过组合而非继承来解决这个问题。下面是一个简单的示例,演示了如何使用组合来实现这个设计: ```go package main import ( "fmt" ) // Claw 表示猫的爪子,包含是否可以抓的属性 type Claw struct { canScratch bool } // NewClaw 创建一个新的爪子实例,默认是可以抓的 func NewClaw(canScratch bool) *Claw { return &Claw{canScratch: canScratch} } // Cat 表示猫,包含猫的名字和一个爪子对象 type Cat struct { name string claw *Claw } // NewCat 创建一个新的猫实例 func NewCat(name string, claw *Claw) *Cat { return &Cat{name: name, claw: claw} } // Scratch 模拟猫抓的行为,根据爪子的状态决定是否能抓 func (c *Cat) Scratch() { if c. claw.canScratch { fmt.Printf("%s scratches the post.\n", c.name) } else { fmt.Printf("%s tries to scratch but cannot.\n", c.name) } } func main() { // 创建一个正常的爪子和猫 normalClaw := NewClaw(true) normalCat := NewCat("Whiskers", normalClaw) normalCat.Scratch() // 创建一个不能抓的爪子 scratchlessClaw := NewClaw(false) // 创建一个不能抓的猫 scratchlessCat := NewCat("Paws", scratchlessClaw) scratchlessCat.Scratch() } ``` 在这个示例中: - 我们定义了一个 `Claw` 结构体来表示猫的爪子,其中包含一个 `canScratch` 字段,用以表示爪子是否可以抓。 - `Cat` 结构体通过组合包含一个 `Claw` 对象,而不是通过继承来共享行为。 - `Cat` 类的 `Scratch` 方法根据 `Claw` 对象的 `canScratch` 属性来决定猫是否可以抓。 - 在 `main` 函数中,我们创建了两个 `Cat` 实例,一个具有正常的爪子,另一个具有不能抓的爪子,然后调用它们的 `Scratch` 方法来演示不同行为。 这种设计避免了通过继承创建大量具有不同行为的子类的问题,同时也保持了代码的清晰和可维护性。如果将来需要添加更多的行为或属性,我们可以简单地修改 `Claw` 类或其他相关类,而不需要创建大量的新的 `Cat` 子类。
本文来自博客园,作者:易先讯,转载请注明原文链接:https://www.cnblogs.com/gongxianjin/p/18336031