oop - 继承可以完全被组合取代吗?

标签 oop design-patterns inheritance language-agnostic composition

这个问题是不是 像“继承与组合”这样的问题。

我完全理解继承与组合有何不同,我知道 Liskov substitution principle , diamond problem ,它们的优点和缺点以及这两个概念似乎都很简单。但是到处都有很多关于继承和组合的问题,我想,也许我误解了这个简单的想法。

让我们关注Go . Go 是一种来自 Google 的语言,每个人都很兴奋,它没有继承,没有类,但它有组合,这很酷。
对我来说,Go 中的作文给了你 正是 与其他语言(C++、Java、...)中的继承相同的功能 - 组件方法会自动公开并可作为后续结构的方法使用,如下所示:

package main

import (
    "fmt"
)

type Car struct{
    name string
}

func (c *Car) move() bool { 
    return true
} 

type MyCar struct{
    Car   
}

func main() {
    var c MyCar
    fmt.Print(c.move())
}

综上所述,组合优于继承,因为:
  • 更灵活(允许您在运行时更改组件,因此您可以影响“类”的工作方式。
  • 没有菱形继承(钻石问题)(但菱形继承(钻石问题)是可以解决的,所以这不是很强的优势)

  • 如果你考虑 Go 和它的接口(interface)(每个对象,具有由接口(interface)定义的方法,实现这个接口(interface)隐含)你有最终的解决方案吗?
    我们可以说带有一些语法糖的组合可以代替继承吗?


    这样的设计符合 Liskov 替换原则。我是否错过了某些东西或继承(从任何语言中知道)与 Go 中已知的组合(和接口(interface))相比没有优势?

    ===== 编辑1 =====

    为澄清起见,可以在 Go使用“标准”组合机制,如下所示(此示例的行为与前一个类似):
    package main
    
    import (
        "fmt"
    )
    
    type Car struct{
        name string
    }
    
    func (c *Car) move() bool { 
        return true
    } 
    
    type MyCar struct{
        car Car
    }
    
    func (c *MyCar) move() bool { 
        return c.car.move()
    } 
    
    func main() {
        var c MyCar
        fmt.Print(c.move())
    }
    

    但是,如果您像前面的示例一样使用它,则所有方法都隐含在“MyCar 类”中。

    最佳答案

    简短的回答

    真的没有那么黑白分明。简而言之,是的。任何可以通过继承解决的情况都可以通过组合来解决。简而言之,您的问题的答案是肯定的;继承可以用组合代替。

    为什么没那么简单

    何时使用继承
    这不是你是否可以换掉它们的问题。这取决于您正在编程的上下文,并且更多的是您是否应该将它们换掉的问题。以 Java 中的这个简单示例为例:

    public class Person
    {
        // Assume every person can speak.
        public void speak()
        {
        }
    }
    

    现在,假设我们还有另一个类,戴夫。戴夫是一个人。
    public class Dave extends Person
    {
         public void speak() { System.out.println("Hello!"); }
         public void killSomeone() {} // Dave is a violent Guy.
    }
    

    现在对 Dave 类更有意义吗?看起来像这样?
    public class Dave
    {
         private Person p;
         // Composition variant.
    
         public void speak() { p.speak(); }
         public void killSomeone() {} // Dave is a violent Guy.
    }
    

    这段代码暗示戴夫有一个人。它没有那么简单,也没有解释自己。此外,任何人能做的事,戴夫都能做,所以我们断言戴夫是一个“人”是有道理的。

    何时使用合成

    当我们只想公开部分类的接口(interface)时,我们使用组合。按照我们之前的示例,假设 Dave有一个 Guitar .吉他有一个更复杂的界面:
    public class Guitar
    {
         public Color color;
         // Guitar's color.
         public Tuning tuning;
         // Guitar's tuning.
    
         public void tuneGuitar()
         {}
    
         public void playChord()
         {}
    
         public void setColor()
         {}
    }
    

    现在,如果我们要继承这个类,结果会是什么?

    那么,Dave 类现在将具有属性 colortuning .是否 Dave有调音吗?我想不是!这就是继承没有意义的地方。我们不想暴露整个 Guitar接口(interface)连同Dave界面。我们只希望用户能够访问 Dave需要访问,所以在这种情况下,我们将使用一些组合:
    public class Dave extends Person
    {
         private Guitar guitar;
         // Hide the guitar object. Then limit what the user can do with it.
    
         public void changeGuitarColor(Color newColor)
         {
             // So this code makes a lot more sense than if we had used inheritance.
             guitar.setColor(newColor);
         }
    
    
         public void speak() { System.out.println("Hello!"); }
         public void killSomeone() {} // Dave is a violent Guy.
    }
    

    结论

    这真的不是什么可以替代另一个的情况。这是关于您在其中实现这些技术的情况。希望在示例结束时,您会看到继承适用于一个对象 IS A 的情况。对象,当一个对象时使用合成 HAS A目的。

    关于oop - 继承可以完全被组合取代吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15749316/

    相关文章:

    ruby - 通常从 Ruby 对象获取值?

    针对真实场景的 Java 体系结构类设计

    javascript - Typescript - 强制覆盖子类中的静态变量

    c++ - 防止 C++ 中的多重继承

    javascript - Controller 中的 $http 请求?

    inheritance - 应用程序架构中具体继承的主要竞争设计模式是什么?

    java - 许多类引用数据库连接类的最佳方式

    php - 这种 PHP 对象设计方法是好是坏? - 寻求批评

    java - 重写而不调用父方法,违反了里氏原则

    asp.net-mvc - 自定义 UserProfile 类 SimpleMembershipProvider