C# 中的对象可能具有实际上是不同类别组合的函数;一个典型的例子是教师例子:

在这个例子中,老师有一个人的特征(例如眼睛颜色)(尽管我有一些老师可能会破坏这个例子)和一个雇员的特征(例如薪水)
C# 不允许多重继承,因此我们寻找使用接口组合的想法。经常这样描述:
继承意味着如果 Cat 继承自 Animal 则 Cat “is-a” Animal
组合意味着如果 Cat 实现了 Noise,那么 Cat “has-a” Noise
为什么这种区别很重要?好吧,想象一下我们的猫。我们实际上可以让 Cat 继承自 Feline,而后者又继承自 Animal。有一天,我们决定要支持其他类型的 Animal,所以我们决定修改 Animal,但随后我们意识到我们将把这些更改推向所有其他子类型。我们的设计和层次结构突然变得相当复杂,如果我们一开始就没有做好,我们必须重新设计我们所有的子类。
规则是Prefer Composition over Inheritance,但请注意 Prefer 一词的使用 - 这很重要,因为这并不意味着我们应该只使用组合,而是我们的设计应该考虑什么时候继承有用,什么时候组合有用也。它还提醒我们,在基类中保留所有可能的方法是一个坏主意。
我喜欢你的形状示例。考虑:

我们的 ShapeSorter 可能有这样的方法:
public bool Sort(Shape shape)
{
foreach(Hole hole in Holes)
{
if(Hole.Type == HoleType.Circular && shape is Circle)
{
return true;
}
if(Hole.Type == HoleType.Square && shape is Square)
{
return true;
}
if(Hole.Type == HoleType.Triangular && shape is Triangle)
{
return true;
}
}
return false;
}
或者,我们可以做一些轻微的控制反转:
public bool Sort(Shape shape, Hole hole)
{
return hole.Accepts(shape); //We end up pushing the `if` code into Hole
}
或者它的一些变体。我们已经在编写大量代码,这些代码依赖于我们知道确切的 Shape 类型。想象一下,当您拥有其中之一时,维护起来会变得多么乏味:

所以,相反,我们自己思考——有没有一种更通用的方式可以通过将问题提炼到相关属性来描述我们的问题?
你称它为 IShape:
public interface IShape{
double Area {get;}
double Perimeter { get; } //Prefer Perimeter over circumference as more applicable to other shapes
int Sides { get; }
HoleType ShapeType { get; }
}
我们的 Sort 方法会变成:
public Hole Sort(IShape shape)
{
foreach(Hole hole in Holes)
{
if(hole.HoleType == shape.ShapeType && hole.Area >= shape.Area)
{
return hole;
}
}
return null;
}
这看起来更整洁,但实际上并不是任何无法通过 Shape 直接完成的事情。
真相是没有真相。最常见的方法将涉及使用继承和组合,因为现实世界中的许多事物都将是一种类型,并且还将具有由接口最好描述的其他属性。要避免的最重要的事情是在基类中保留所有可能的方法,并使用大量的 if 语句来确定派生类型可以做什么和不能做什么——这是很多难以维护的代码。此外,在基类中放置过多的功能可能会导致您的代码具体化——由于所有潜在的副作用,您以后不会想要修改内容。