继承与构成的区别

gmh*_*mhk 187 java oop inheritance composition

组成和继承是一样的吗?如果我想实现组合模式,我该如何在Java中实现?

pol*_*nts 292

它们完全不同.继承是一种"是一种"关系.作文是"有一个".

您可以通过将另一个类的实例作为类C的字段来进行组合,而不是扩展C.一个很好的例子,其中组合将比继承更好java.util.Stack,目前正在扩展java.util.Vector.现在这被认为是一个大错.堆栈"is-not-a"向量; 你不应该被允许任意插入和删除元素.它应该是组成而不是.

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

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

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

良好的面向对象设计不是关于自由扩展现有类.你的第一直觉应该是构图.


也可以看看:

  • 我很欣赏这个答案; 然而,我感觉好像答案偏离轨道并且更多地考虑到围绕语言设计(和特定包装)的问题,而不是回答关于构图与继承的问题.我非常喜欢回答关于SO的问题,引用资源 - 没有提供更深入的摘要而不是单行求和,而不是链接到外部资源. (4认同)
  • 不好的例子,我不得不做额外的搜索来理解Vector和Stack是什么. (4认同)
  • 有趣.为什么不创建一个使用组合的新类java.util.Stack2? (3认同)

cod*_*ict 196

组合意味着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.
}
Run Code Online (Sandbox Code Playgroud)

  • 我将"汽车"改为"动力汽车",因为许多汽车和汽车都是相同的. (7认同)
  • @hexafraction我同意车辆可能是一个更好的选择,但同时--codaddict-已经说明了这一点,对于被问到的问题. (5认同)
  • "引擎"`: - /` (4认同)
  • 这个例子真的很有帮助。 (2认同)
  • 这是最好的答案! (2认同)

小智 39

继承如何危险?

让我们举个例子

public class X{    
   public void do(){    
   }    
}    
Public Class Y extends X{
   public void work(){    
       do();    
   }
}
Run Code Online (Sandbox Code Playgroud)

1)如上面的代码所示,Y类与X类有很强的耦合.如果超类X有任何变化,Y可能会突然发生.假设在将来的类X中实现具有以下签名的方法工作

public int work(){
}
Run Code Online (Sandbox Code Playgroud)

更改在X类中完成,但它将使Y类无法编译.因此,这种依赖可以达到任何级别,这可能非常危险.每当超类可能无法完全看到其所有子类中的代码时,子类可能会一直注意到超类中发生的事情.所以我们需要避免这种强烈和不必要的耦合.

作文如何解决这个问题?

让我们看看修改相同的例子

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

Public Class Y{
    X x = new X();    
    public void work(){    
        x.do();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

public class Test{    
    R r;    
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Public Class Y {
    X x = new X();    
    public void work(){    
        x.do();    
    }    
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,如果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();    
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

很高兴知道 :

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

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

因此,出于上述各种原因,习惯于总是更喜欢构图而不是继承.

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

小智 19

@Michael Rodrigues给出的答案是不正确的(我道歉;我无法直接评论),并可能导致一些混乱.

接口实现是一种继承形式 ......当您实现接口时,您不仅要继承所有常量,而且要将对象提交为接口指定的类型; 它仍然是一种" 是一种 "关系.如果汽车实现Fillable,那么汽车" 是一个 " 可填充的,并且可以在您使用Fillable的任何地方使用.

组成与继承根本不同.当您使用合成时,您(在其他答案中注释)在两个对象之间建立" has-a "关系,而不是在使用继承时创建的" is-a "关系.

所以,从其他问题的汽车示例中,如果我想说一辆汽车" 有一个 "油箱,我会使用组成,如下:

public class Car {

private GasTank myCarsGasTank;

}
Run Code Online (Sandbox Code Playgroud)

希望这可以解决任何误解.


fri*_*ley 17

继承带来了IS-A关系.构图带来了HAS-A关系.策略模式解释组合应该用于存在定义特定行为的算法族的情况.
经典的例子是鸭子类,它实现了飞行行为.

public interface Flyable{
 public void fly();
}

public class Duck {
 Flyable fly;

 public Duck(){
  fly = new BackwardFlying();
 }
}
Run Code Online (Sandbox Code Playgroud)

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

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");
  }
}
Run Code Online (Sandbox Code Playgroud)

如果是继承,我们会有两种不同类型的鸟类一遍又一遍地实现飞行功能.所以继承和构成完全不同.


Mic*_*ues 7

组合就像听起来一样 - 通过插入部件来创建对象.

编辑其余答案错误地基于以下前提.
这是通过Interfaces完成的.
例如,使用Car上面的例子,

Car implements iDrivable, iUsesFuel, iProtectsOccupants
Motorbike implements iDrivable, iUsesFuel, iShortcutThroughTraffic
House implements iProtectsOccupants
Generator implements iUsesFuel
Run Code Online (Sandbox Code Playgroud)

因此,使用一些标准的理论组件,您可以构建您的对象.然后,您的工作就是填写如何House保护其居住者,以及如何Car保护其居住者.

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

例如,MotorVehicle可以带有Fuelable方法和Drive方法.您可以保留Fuel方法,因为填充摩托车和汽车是一样的,但是您可以覆盖该Drive方法,因为摩托车驾驶方式与a非常不同Car.

通过继承,一些类已经完全实现,而其他类则有被强制覆盖的方法.随着作文没有给你任何东西.(但是你可以通过调用其他类中的方法来实现接口,如果碰巧有东西存在的话).

组合被认为更灵活,因为如果你有一个像iUsesFuel这样的方法,你可以在其他地方(另一个类,另一个项目)有一个方法,只需要担心处理可以加油的物体,无论它是否是汽车,接口要求说他们实现该接口的类实际上具有该接口所有的方法.例如,

iFuelable Interface:
   void AddSomeFuel()
   void UseSomeFuel()
   int  percentageFull()
Run Code Online (Sandbox Code Playgroud)

那么你可以在其他地方找到一个方法

private void FillHerUp(iFuelable : objectToFill) {

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

        objectToFill.AddSomeFuel();
   }
Run Code Online (Sandbox Code Playgroud)

奇怪的例子,但它表明这个方法并不关心它填满了什么,因为对象实现了iUsesFuel,它可以被填充.故事结局.

如果您使用的继承,而不是,你需要不同的FillHerUp方法来处理MotorVehiclesBarbecues,除非你有一些怪异,而"ObjectThatUsesFuel"的基地,以继承对象.


Rav*_*abu 6

组成和继承是一样的吗?

它们不一样.

组合:它使一组对象必须以与单个对象实例相同的方式处理.复合的意图是将对象"组合"成树结构以表示部分整体层次结构

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

如果我想实现组合模式,我该如何在Java中实现?

维基百科文章足以在java中实现复合模式.

在此输入图像描述

主要参与者:

组件:

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

:

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

复合:

  1. 表示复合Component(具有子节点的组件)
  2. 实现操纵子项的方法
  3. 实现所有Component方法,通常通过将它们委托给其子代

用于理解Composite模式的代码示例:

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());
    }

}
Run Code Online (Sandbox Code Playgroud)

输出:

Car name:Innova
Car parts:DiselEngine Trunk Body
Car price:37000.0
Run Code Online (Sandbox Code Playgroud)

说明:

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

有关构成和继承的优缺点,请参阅下面的问题.

喜欢构成而不是继承?