继承与组成之间的区别


208

组成和继承相同吗?如果要实现合成模式,如何在Java中实现呢?


2
其他相关问题:有什么组成不能完成继承可以做到的吗?stackoverflow.com/questions/2238642/...
ewernli


1
这篇文章是对我很有用,因此帮助: thoughtworks.com/insights/blog/...
位于qmaster

Answers:


321

他们是绝对不同的。继承是一种“是”关系。组成是“有”

您可以通过将另一个类的实例作为类C的字段来进行合成,而不是扩展C。一个很好的例子是组合比继承要好得多java.util.Stack,目前已经扩展了java.util.Vector。现在,这被认为是一种错误。堆栈“ is-NOT-a”向量;您不应该被允许随意插入和删除元素。它应该是合成的。

不幸的是,现在纠正该设计错误为时已晚,因为现在更改继承层次结构会破坏与现有代码的兼容性。如果Stack使用合成而不是继承,则始终可以对其进行修改以使用其他数据结构,而不会违反API

我强烈推荐Josh Bloch的书Effective Java 2nd Edition

  • 项目16:赞成组成而不是继承
  • 第17条:继承的设计和文件,或者禁止继承

好的面向对象设计并不是要自由地扩展现有的类。您的第一个本能应该是撰写。


也可以看看:


4
有趣。为什么不创建一个使用组合的新类java.util.Stack2?
QED

5
我很感谢这个答案;但是,我觉得答案似乎偏离了轨道,而是更多地关注与语言(和特定程序包)有关的设计问题,而不是回答有关组合与继承的问题。我非常乐于回答有关SO的问题,并引用资源-在没有提供比单行汇总更深入的摘要的情况下,不要链接到外部资源。
汤玛斯(Thomas)

5
不好的例子,我不得不做额外的搜索来了解什么是Vector和Stack。
Sam Ramezanli

不错,但你对Java堆栈的例子是不是因为这个产业的一个很好的候选人是错误的决定,关于这篇文章中说,在选择继承组成一个样本:thoughtworks.com/insights/blog/...
位于qmaster

212

组成意味着HAS A
继承IS A

Example:汽车发动机,汽车汽车

在编程中,它表示为:

class Engine {} // The Engine class.

class Automobile {} // Automobile class which is parent to Car class.

class Car extends Automobile { // Car is an Automobile, so Car class extends Automobile class.
  private Engine engine; // Car has an Engine so, Car class has an instance of Engine class as its member.
}

7
我会把“汽车”改为“动力汽车”,因为在许多方面,汽车和汽车是等效的。
nanofarad

5
@hexafraction我同意Vehicle可能是一个更好的选择,但同时-codaddict-指出了要问的要点。
nckbrz 2014年

5
“引擎”:-/
奥马尔·塔里克

很好地解释了。+1。进一步阅读javarevisited.blogspot.in/2015/06/…–
roottraveller

@AndreyAkhmetov汽车公司可以拥有type类型字段Enum
Ojonugwa Jude Ochalifu '18

42

如何继承会很危险?

让我们举个例子

public class X{    
   public void do(){    
   }    
}    
Public Class Y extends X{
   public void work(){    
       do();    
   }
}

1)如上面代码中清楚所示,类Y与类X具有非常强的耦合。如果超类X发生任何更改,则Y可能会严重破坏。假设在将来的类X中使用以下签名实现方法工作

public int work(){
}

更改在X类上完成,但它将使Y类无法编译。因此,这种依赖性可能会上升到任何水平,并且非常危险。每次超类可能都无法完全了解其所有子类中的代码,并且子类可能一直都在注意超类中正在发生的事情。因此,我们需要避免这种强烈而不必要的耦合。

合成如何解决这个问题?

让我们通过修改相同的示例来看看

public class X{
    public void do(){
    }
}

Public Class Y{
    X x = new X();    
    public void work(){    
        x.do();
    }
}

在这里,我们在Y类中创建X类的引用,并通过创建X类的实例来调用X类的方法。现在所有这些强耦合都消失了。现在,超类和子类彼此高度独立。类可以自由地进行更改,这在继承情况下很危险。

2)组合的第二个非常好的优点在于它提供了调用方法的灵活性,例如:

class X implements R
{}
class Y implements R
{}

public class Test{    
    R r;    
}

在使用r引用的Test类中,我可以调用X类以及Y类的方法。继承从来就没有这种灵活性

3)另一个巨大的优势:单元测试

public class X {
    public void do(){
    }
}

Public Class Y {
    X x = new X();    
    public void work(){    
        x.do();    
    }    
}

在上面的示例中,如果x实例的状态未知,则可以使用一些测试数据轻松模拟它,并且可以轻松测试所有方法。这在继承中根本不可能,因为您在很大程度上依赖超类来获取实例的状态并执行任何方法。

4)我们应该避免继承的另一个很好的理由是Java不支持多重继承。

让我们举个例子来理解这一点:

Public class Transaction {
    Banking b;
    public static void main(String a[])    
    {    
        b = new Deposit();    
        if(b.deposit()){    
            b = new Credit();
            c.credit();    
        }
    }
}

很高兴知道 :

  1. 组合在运行时很容易实现,而继承在编译时提供其功能

  2. 组合也称为HAS-A关系,继承也称为IS-A关系

因此,出于上述各种原因,养成始终偏向于继承而不是继承的习惯。


3
同意,但是给定了使用组合的解决方案。...我们仍然需要做个保姆,例如,现在超类X将方法名称从do更改为do...。然后,子类Y也将需要维护(需要进行更改)以及这仍然是紧密耦合?我们如何摆脱它?
stickedoverflow

19

@Michael Rodrigues给出的答案是不正确的(我很抱歉;我无法直接发表评论),并且可能导致混乱。

接口实现是继承的一种形式 ……实现接口时,不仅继承了所有常量,而且还承诺对象是接口指定的类型。仍然是“ ”关系。如果一辆车实现了可输入资料,汽车“ 是,一个可输入资料,并且可以在你的代码中使用,无论你将使用可输入资料

组合从根本上不同于继承。使用组合时,(如其他答案所述)您将在两个对象之间建立“ has-a ”关系,而不是使用继承时所建立的“ is-a ”关系

因此,从其他问题的汽车示例中,如果我想说一辆汽车“ 有-一个 ”油箱,我将使用以下组成:

public class Car {

private GasTank myCarsGasTank;

}

希望这可以消除任何误解。


17

继承带来了IS-A关系。组成带出HAS-A关系。策略模式说明,在有一系列算法定义特定行为的情况下,应使用组合。
一个典型的例子是鸭类,它实现了飞行行为。

public interface Flyable{
 public void fly();
}

public class Duck {
 Flyable fly;

 public Duck(){
  fly = new BackwardFlying();
 }
}

因此,我们可以有多个实现飞行的类,例如:

public class BackwardFlying implements Flyable{
  public void fly(){
    Systemout.println("Flies backward ");
  }
}
public class FastFlying implements Flyable{
  public void fly(){
    Systemout.println("Flies 100 miles/sec");
  }
}

如果是继承,我们将有两类不同的鸟,它们一遍又一遍地实现fly功能。因此继承和组成完全不同。


7

合成就像听起来一样-您可以通过插入零件来创建对象。

根据以下前提,错误地编辑此答案的其余部分。
这是通过接口完成的。
例如,使用Car上面的示例,

Car implements iDrivable, iUsesFuel, iProtectsOccupants
Motorbike implements iDrivable, iUsesFuel, iShortcutThroughTraffic
House implements iProtectsOccupants
Generator implements iUsesFuel

因此,通过一些标准的理论组件,您可以构建您的对象。然后,您的工作就是填写House保护住户的方式以及Car保护住户的方式。

继承就像相反。您从一个完整的(或半完整的)对象开始,然后替换或覆盖要更改的各个位。

例如,MotorVehicle可能附带一种Fuelable方法和Drive一种方法。您可以按原样保留Fuel方法,因为填充摩托车和汽车的Drive方法相同,但是可以覆盖该方法,因为Motorcycle的驾驶方式与的差别很大Car

通过继承,某些类已经完全实现,而另一些类则具有被强制重写的方法。有了成分,您什么也不会得到。(但是,如果碰巧有一些问题,可以通过调用其他类中的方法来实现接口)。

组合看起来更灵活,因为如果您拥有iUsesFuel之类的方法,那么您可以在其他地方(另一个类,另一个项目)拥有一个方法,该方法只担心处理可以加油的物体,而不管它是否是汽车,接口要求类实现它们的接口实际上具有该接口的所有方法。例如,

iFuelable Interface:
   void AddSomeFuel()
   void UseSomeFuel()
   int  percentageFull()

那么你可以在其他地方有一种方法

private void FillHerUp(iFuelable : objectToFill) {

   Do while (objectToFill.percentageFull() <= 100)  {

        objectToFill.AddSomeFuel();
   }

奇怪的示例,但是它表明此方法并不关心它填充了什么,因为该对象实现了iUsesFuel,它可以被填充。故事结局。

如果改用继承,则将需要其他FillHerUp方法来处理MotorVehiclesBarbecues,除非您有一些非常奇怪的“ ObjectThatUsesFuel”基础对象要从中继承。


Java约定规定类和接口名称是用而ThisCase不是编写的camelCase。因此,最好命名您的接口IDrivable等。如果您将所有接口正确地重新组合到一个程序包中,则可能不需要“ I”。
ThePyroEagle 2015年

6

组成和继承相同吗?

他们不一样。

组合:它使必须以与单个对象实例相同的方式对待一组对象。组合的目的是将对象“组成”为树状结构,以表示整体层次结构

继承:类从其所有超类(无论直接还是间接)继承字段和方法。子类可以覆盖其继承的方法,也可以隐藏其继承的字段或方法。

如果要实现合成模式,如何在Java中实现呢?

维基百科的文章足以在Java中实现复合模式。

在此处输入图片说明

主要参与者:

组成部分

  1. 是所有组件(包括复合组件)的抽象
  2. 声明合成中对象的接口

  1. 表示合成中的叶子对象
  2. 实现所有Component方法

复合材料

  1. 表示一个复合组件(具有子组件的组件)
  2. 实施操纵儿童的方法
  3. 实现所有Component方法,通常是将它们委派给其子级

理解复合模式的代码示例:

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

interface Part{
    public double getPrice();
    public String getName();
}
class Engine implements Part{
    String name;
    double price;
    public Engine(String name,double price){
        this.name = name;
        this.price = price;
    }
    public double getPrice(){
        return price;
    }
    public String getName(){
        return name;
    }
}
class Trunk implements Part{
    String name;
    double price;
    public Trunk(String name,double price){
        this.name = name;
        this.price = price;
    }
    public double getPrice(){
        return price;
    }
    public String getName(){
        return name;
    }
}
class Body implements Part{
    String name;
    double price;
    public Body(String name,double price){
        this.name = name;
        this.price = price;
    }
    public double getPrice(){
        return price;
    }
    public String getName(){
        return name;
    }
}
class Car implements Part{
    List<Part> parts;
    String name;

    public Car(String name){
        this.name = name;
        parts = new ArrayList<Part>();
    }
    public void addPart(Part part){
        parts.add(part);
    }
    public String getName(){
        return name;
    }
    public String getPartNames(){
        StringBuilder sb = new StringBuilder();
        for ( Part part: parts){
            sb.append(part.getName()).append(" ");
        }
        return sb.toString();
    }
    public double getPrice(){
        double price = 0;
        for ( Part part: parts){
            price += part.getPrice();
        }
        return price;
    }   
}

public class CompositeDemo{
    public static void main(String args[]){
        Part engine = new Engine("DiselEngine",15000);
        Part trunk = new Trunk("Trunk",10000);
        Part body = new Body("Body",12000);

        Car car = new Car("Innova");
        car.addPart(engine);
        car.addPart(trunk);
        car.addPart(body);

        double price = car.getPrice();

        System.out.println("Car name:"+car.getName());
        System.out.println("Car parts:"+car.getPartNames());
        System.out.println("Car price:"+car.getPrice());
    }

}

输出:

Car name:Innova
Car parts:DiselEngine Trunk Body
Car price:37000.0

说明:

  1. 部分是一片叶子
  2. 汽车包含许多零件
  3. 汽车的不同部分已添加到汽车中
  4. 汽车价格=(各部分的价格)之和

有关组成和继承的优缺点,请参见以下问题。

比继承更偏爱组成?


实施汽车零件也有意义吗?如果你把它作为组成将它不会好
bhalkian

4

再举一个例子,考虑汽车类别,这将是一种很好的组合用法,汽车将“具有”发动机,变速箱,轮胎,座椅等。它不会扩展任何这些类别。


4

组合是由不同部分组成的部分,并且与这些部分有很强的关系。如果主要部分死亡,其他部分也将死亡,那么他们将无法拥有自己的生活。人体是一个粗糙的例子。掏出心脏,其他所有部分都消失了。

继承是您仅需获取已有的东西并使用它的地方。没有牢固的关系。一个人可以继承父亲的财产,但没有财产就可以继承。

我不了解Java,因此无法提供示例,但可以对概念进行解释。


3

两个类之间的继承(其中一个类扩展另一个类)建立“ IS A ”关系。

组成的另一端包含一个在您的类中建立“ 具有 ”关系的另一个类的实例。Java中的组合非常有用,因为它在技术上有利于多重继承。


3

用简单的单词聚合意味着有关系..

合成是聚合的特例。以更具体的方式,受限制的聚合称为合成。当一个对象包含另一个对象时,如果所包含的对象在没有容器对象的情况下无法存在,则称为合成。 示例:一个班级包含学生。没有班级就不可能存在学生。班级与学生之间存在组成。

为什么要使用汇总

代码可重用性

使用汇总时

当没有关系时,通过聚合也可以最好地实现代码重用

遗产

继承是亲子关系继承是一种关系

Java中的继承是一种机制,其中一个对象获取父对象的所有属性和行为。

在Java 1代码可重用性中使用继承。2在子类和方法覆盖中添加额外功能(因此可以实现运行时多态)。


1

尽管“继承”和“组合”都提供了代码可重用性,但是Java中“组合”和“继承”之间的主要区别在于,“组合”允许在不扩展代码的情况下重用代码,但是对于继承,您必须扩展类以实现对代码或功能的任何重用。来自此事实的另一个区别是,通过使用Composition,您甚至可以为最终类重用代码,这是不可扩展的,但是在这种情况下,继承不能重用代码。同样,通过使用Composition,您可以重用许多类中的代码,因为它们被声明为成员变量,但是通过Inheritance,您只能重用一个类的代码,因为在Java中您只能扩展一个类,因为Java中不支持多重继承。 。您可以在C ++中执行此操作,因为在那里一类可以扩展一个以上的类。顺便说一句,你应该永远更喜欢Java中的继承而不是继承,它不仅是我本人,甚至约书亚·布洛赫(Joshua Bloch)都在他的书中建议过


1
为什么要“ 对继承进行合成 ”?它们是不同的概念,并且用于不同的目的。坦白说,我什至不知道你怎么能从一个转到另一个。
克鲁夫

1

我认为这个示例清楚地解释了继承组合之间的区别。

例如,使用继承和组合解决了问题。作者注意到事实; 在继承中,超类的更改可能会导致继承它的派生类出现问题。

使用UML进行继承或组合时,您还可以在此处看到表示形式的差异。

http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should-you-choose-.html


1
不鼓励仅链接的答案,因为它们过时。请至少在您的答案中包含链接中最重要的相关信息。
斯科特·索默

1

继承与组成。

继承和组合都用于重用和扩展类行为。

继承主要用于家庭算法编程模型中,例如IS-A。关系类型表示类似的对象。例。

  1. 喷粉器是汽车
  2. Safari是一辆车

这些属于汽车家族。

组合表示HAS-A关系类型。它显示对象的功能,例如Duster具有5档,Safari具有4档等。每当我们需要扩展现有类的功能时,便使用组合。例如,我们需要在Duster对象中添加一个齿轮,然后再创建一个齿轮对象并将其组合到除尘器对象中。

除非/除非所有派生类都需要这些功能,否则我们不应该在基类中进行更改。对于这种情况,我们应该使用Composition。

B级派生的A级

C类派生的A类

由D类派生的A类。

当我们在A类中添加任何功能时,即使C和D类不需要这些功能,所有子类都可以使用它。对于这种情况,我们需要为这些功能创建一个单独的类并将其组合为所需的类(这是B类)。

下面是示例:

          // This is a base class
                 public abstract class Car
                    {
                       //Define prototype
                       public abstract void color();
                       public void Gear() {
                           Console.WriteLine("Car has a four Gear");
                       }
                    }


           // Here is the use of inheritence
           // This Desire class have four gears.
          //  But we need to add one more gear that is Neutral gear.

          public class Desire : Car
                   {
                       Neutral obj = null;
                       public Desire()
                       {
     // Here we are incorporating neutral gear(It is the use of composition). 
     // Now this class would have five gear. 

                           obj = new Neutral();
                           obj.NeutralGear();
                       }

                       public override void color()
                       {
                           Console.WriteLine("This is a white color car");
                       }

                   }


             // This Safari class have four gears and it is not required the neutral
             //  gear and hence we don't need to compose here.

                   public class Safari :Car{
                       public Safari()
                       { }

                       public override void color()
                       {
                           Console.WriteLine("This is a red color car");
                       }


                   }

   // This class represents the neutral gear and it would be used as a composition.

   public class Neutral {
                      public void NeutralGear() {
                           Console.WriteLine("This is a Neutral Gear");
                       }
                   }

0

组合意味着为与该特定类有关系的类创建对象。假设学生与帐户有关系;

继承是,这是具有扩展功能的上一类。这意味着该新类是具有某些扩展功能的旧类。假设学生是学生,但所有学生都是人。因此,与学生和人有关系。这就是继承。



0

继承意味着重用一个类的全部功能。在这里,我的类必须使用超类的所有方法,并且在继承的情况下,我的类将与超类进行细致的耦合,并且两个类中的代码都将重复。

但是当我们使用写作与另一个班级交谈时,我们可以克服所有这些问题。合成是在我们要讨论的班级中声明另一个班级的属性。以及使用该属性可以从该类中获得什么功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.