Ich denke, die prägnanteste Formulierung ist die folgende:
Gemeinsame Eigenschaften => abstrakte Klasse.
Gemeinsame Funktionalität => Schnittstelle.
Und um es weniger lapidar zu formulieren...
Beispiel für eine abstrakte Klasse:
public abstract class BaseAnimal
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
}
public class Dog : BaseAnimal
{
public Dog() : base(4) { }
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
}
Da Tiere eine gemeinsame Eigenschaft haben - in diesem Fall die Anzahl der Beine - ist es sinnvoll, eine abstrakte Klasse zu erstellen, die diese gemeinsame Eigenschaft enthält. Dies ermöglicht es uns auch, gemeinsamen Code zu schreiben, der mit dieser Eigenschaft arbeitet. Zum Beispiel:
public static int CountAllLegs(List<BaseAnimal> animals)
{
int legCount = 0;
foreach (BaseAnimal animal in animals)
{
legCount += animal.NumberOfLegs;
}
return legCount;
}
Beispiel für eine Schnittstelle:
public interface IMakeSound
{
void MakeSound();
}
public class Car : IMakeSound
{
public void MakeSound() => Console.WriteLine("Vroom!");
}
public class Vuvuzela : IMakeSound
{
public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");
}
Beachten Sie hier, dass Vuvuzelas und Autos völlig unterschiedliche Dinge sind, aber eine gemeinsame Funktion haben: ein Geräusch zu machen. Daher ist eine Schnittstelle hier sinnvoll. Außerdem ermöglicht es Programmierern, Dinge, die Geräusche machen, unter einer gemeinsamen Schnittstelle zusammenzufassen -- IMakeSound
in diesem Fall. Mit diesem Entwurf könnten Sie den folgenden Code schreiben:
List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());
foreach (IMakeSound soundMaker in soundMakers)
{
soundMaker.MakeSound();
}
Können Sie sagen, was dabei herauskommen würde?
Und schließlich können Sie beides kombinieren.
Kombiniertes Beispiel:
public interface IMakeSound
{
void MakeSound();
}
public abstract class BaseAnimal : IMakeSound
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
public abstract void MakeSound();
}
public class Cat : BaseAnimal
{
public Cat() : base(4) { }
public override void MakeSound() => Console.WriteLine("Meow!");
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
public override void MakeSound() => Console.WriteLine("Hello, world!");
}
Hier benötigen wir alle BaseAnimal
s ein Geräusch machen, aber wir kennen seine Umsetzung noch nicht. In einem solchen Fall können wir die Schnittstellenimplementierung abstrahieren und ihre Implementierung an ihre Unterklassen delegieren.
Ein letzter Punkt: Erinnern Sie sich daran, dass wir im Beispiel der abstrakten Klasse die gemeinsamen Eigenschaften verschiedener Objekte bearbeiten konnten und im Beispiel der Schnittstelle die gemeinsame Funktionalität verschiedener Objekte aufrufen konnten? In diesem letzten Beispiel konnten wir beides tun.