什么是面向对象设计中的组合?

76

我经常听到(并在这个网站上读到)很多关于“优先选择组合而不是继承”的话题。

但是什么是组合?我从人类:哺乳动物:动物的角度理解继承,但是我真的找不到组合的定义……有人可以帮我吗?

6个回答

76

组合指将简单类型组合成更复杂的类型。在您的示例中,组合可能是:

Animal:
    Skin animalSkin
    Organs animalOrgans


Mammal::Animal: 
    Hair/fur mammalFur
    warm-blooded-based_cirulation_system heartAndStuff

Person::Mammal: 
    string firstName
    string lastName

如果你想完全使用组合(并且摆脱所有继承),代码应该如下:

Animal:
    Skin animalSkin
    Organs animalOrgans

Mammal:
    private Animal _animalRef
    Hair/fur mammalFur
    warm-blooded-based_cirulation_system heartAndStuff

Person:
    private Mammal _mammalRef
    string firstName
    string lastName
这种方法的优势在于类型和不必符合它们之前父类的接口。这可能是一件好事,因为有时超类的更改会对子类产生严重影响。它们仍然可以通过私有实例访问这些类的属性和行为,如果想公开这些以前的超类行为,只需将其包装在公共方法中即可。 我在这里找到了一个带有良好示例的链接:http://www.artima.com/designtechniques/compoinh.html

8
那么我可以这样说吗:"组合是当我在类B内部创建一个类A对象(而不是将类B类A继承)"? - geekay
哦,好的,我明白了。w69rdy 后来回答了它。是的,我可以这么说。 - geekay
好的,现在这很有意义。对于其他试图理解这个问题的Python程序员,在考虑组合时不要陷入“Python中的所有内容都是对象”的想法中。相反,应该思考“当我创建我的类时,我将使用我创建的其他类作为实例变量”。 - als0052

49

组合(Composition)是指构成整体的各个部分。汽车由轮子、发动机和座位等部件组成。继承(Inheritance)是“是一个”("is a")关系。而组合则是“有一个”("has a")关系。


5
聚合是一种...关系。 - H H
2
聚合可以是简单的组合,或者如果它是一组类似的东西(例如汽车上的轮子),则可以将其视为集合。汽车可以有四个独立的轮子,每个轮子都有唯一的标识,或者它可以有一组轮子。这取决于使用情况。如果使用集合类,则该集合本身就是一个聚合。 - Cylon Cat

24

给一个类赋予行为有三种方法。你可以将该行为编写到类中;你可以从具有所需行为的类继承;或者,你可以将具有所需行为的类作为字段或成员变量合并到你的类中。后两种表示代码重用的形式,而最后一种——组合——通常更受欢迎。它实际上并没有为您的类提供所需的行为——您仍然需要在字段上调用方法——但它对您的类设计施加的约束较少,并且导致易于测试和调试的代码。继承有其地位,但应首选组合。


20
class Engine
{

}

class Automobile
{

}


class Car extends Automobile // car "is a" automobile //inheritance here
{ 
 Engine engine; // car "has a" engine //composition here

}

组合 - 对象的功能由不同类的集合组成。在实践中,这意味着持有对另一个类的指针,以将工作推迟到该类。

继承 - 对象的功能由它自己的功能加上父类的功能组成。

至于为什么组合比继承更受欢迎,请参考椭圆圆问题


5

组合的一个例子是在一个类的实例中包含另一个类,而不是从它继承。

这个页面有一篇很好的文章解释了为什么人们说“偏向于组合而非继承”,并且提供了一些例子来说明。


1
不是在另一个类(C1)中的类实例(C2),而是在另一个类的实例中的类实例。前者可能会被误解为在定义C1时实例化了C2,这种情况并不常见。 - Alois Mahdal

1

组合

简单地说,组合是使用实例变量作为对其他对象的引用。


为了说明继承和组合在代码重用方面的比较,考虑这个非常简单的例子:


1- 通过继承编写代码
    class Fruit {

    // Return int number of pieces of peel that
    // resulted from the peeling activity.
    public int peel() {

        System.out.println("Peeling is appealing.");
        return 1;
    }
}

class Apple extends Fruit {
}

class Example1 {

    public static void main(String[] args) {

        Apple apple = new Apple();
        int pieces = apple.peel();
    }
}

当您运行Example1应用程序时,它会打印出"Peeling is appealing.",因为Apple继承(重用)了Fruit的peel()实现。然而,如果在将来的某个时候,您希望将peel()的返回值更改为Peel类型,那么您将破坏Example1的代码。即使Example1直接使用Apple并且从未明确提到Fruit,您对Fruit的更改也会破坏Example1的代码。 更多信息请参考ref。
class Peel {

    private int peelCount;

    public Peel(int peelCount) {
        this.peelCount = peelCount;
    }

    public int getPeelCount() {

        return peelCount;
    }
    //...
}

class Fruit {

    // Return a Peel object that
    // results from the peeling activity.
    public Peel peel() {

        System.out.println("Peeling is appealing.");
        return new Peel(1);
    }
}

// Apple still compiles and works fine
class Apple extends Fruit {
}

// This old implementation of Example1
// is broken and won't compile.
class Example1 {

    public static void main(String[] args) {

        Apple apple = new Apple();
        int pieces = apple.peel();
    }
}

2- 通过组合编码 组合提供了一种替代方法,让Apple重用Fruitpeel()实现。与扩展Fruit不同,Apple可以持有对Fruit实例的引用,并定义自己的peel()方法,只需在Fruit上调用peel()即可。以下是代码:

class Fruit {

    // Return int number of pieces of peel that
    // resulted from the peeling activity.
    public int peel() {

        System.out.println("Peeling is appealing.");
        return 1;
    }
}

class Apple {

    private Fruit fruit = new Fruit();

    public int peel() {
        return fruit.peel();
    }
}

class Example2 {

    public static void main(String[] args) {

        Apple apple = new Apple();
        int pieces = apple.peel();
    }
}

获取更多信息ref


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接