继承与聚合

2024-06-16 11:28:01 发布

您现在位置:Python中文网/ 问答频道 /正文

关于如何在面向对象系统中最好地扩展、增强和重用代码,有两种思想流派:

  1. 继承:通过创建子类来扩展类的功能。重写子类中的超类成员以提供新功能。使方法抽象/虚拟,以便在超类需要特定接口但对其实现不可知时,强制子类“填充空白”。

  2. 聚合:通过将其他类合并到一个新类中来创建新功能。为这个新类附加一个公共接口,以便与其他代码进行互操作。

它们的好处、成本和后果是什么?还有其他选择吗?

我看到这场辩论定期举行,但我认为没有人要求它 堆栈溢出(尽管有一些相关的讨论)。谷歌也出人意料地缺乏好的搜索结果。


Tags: 方法代码功能堆栈系统成员面向对象子类
3条回答

这种差异通常表示为“是a”和“有a”之间的差异。继承,即“is a”关系,在Liskov Substitution Principle中得到了很好的总结。聚合,即“has a”关系,只是-它表明聚合对象具有其中一个聚合对象。

进一步存在区别:C++中的私有继承表示“以关系”实现,也可以通过(非暴露)成员对象的聚合来建模。

GOF开始时,它们声明

Favor object composition over class inheritance.

这将进一步讨论here

这不是什么是最好的,而是什么时候用什么。

在“正常”情况下,一个简单的问题就足以找出我们是否需要继承或聚合。

  • 如果新类与原始类差不多。使用继承。新类现在是原始类的子类。
  • 如果新类必须具有原始类。使用聚合。新类现在有了作为成员的原始类。

但是,有一个很大的灰色区域。所以我们需要几个其他的技巧。

  • 如果我们使用了继承(或者我们计划使用它),但是我们只使用了接口的一部分,或者我们被迫重写许多功能来保持相关性的逻辑性。然后我们有一股难闻的气味,这表明我们必须使用聚合。
  • 如果我们已经使用了聚合(或者计划使用聚合),但发现我们需要复制几乎所有的功能。然后我们闻到一种指向遗传方向的气味。

缩短时间。如果没有使用接口的一部分,或者必须更改接口以避免出现不合逻辑的情况,我们应该使用聚合。我们只需要使用继承,如果我们需要几乎所有的功能而没有重大的更改。当有疑问时,使用聚合。

另一种可能性是,如果我们有一个类需要原始类的一部分功能,则将原始类拆分为一个根类和一个子类。并让新类从根类继承。但你应该小心,不要造成不合逻辑的分离。

让我们添加一个示例。我们有一个“狗”类的方法:“吃”、“走”、“叫”、“玩”。

class Dog
  Eat;
  Walk;
  Bark;
  Play;
end;

我们现在需要一个“猫”类,它需要“吃”、“走”、“咕噜”和“玩”。所以首先试着把它从狗身上伸出来。

class Cat is Dog
  Purr; 
end;

看,好吧,等一下。这只猫会叫(爱猫的人会因此杀了我)。一只吠叫的猫违反了宇宙的法则。所以我们需要重写Bark方法,这样它什么也做不了。

class Cat is Dog
  Purr; 
  Bark = null;
end;

好吧,这行,但闻起来很难闻。因此,让我们尝试聚合:

class Cat
  has Dog;
  Eat = Dog.Eat;
  Walk = Dog.Walk;
  Play = Dog.Play;
  Purr;
end;

好的,这很好。这只猫不再叫了,甚至不再沉默。但它还是有一只内在的狗想要离开。所以让我们试试第三种解决方案:

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

这要干净得多。没有内部狗。猫和狗在同一水平。我们甚至可以引进其他宠物来扩展这个模型。除非是一条鱼,或是什么不能走路的东西。在这种情况下,我们再次需要重构。但那是另一次。

相关问题 更多 >