Für mich wurde der Punkt zu Beginn erst klar, als Sie aufhörten, sie als Dinge zu betrachten, die das Schreiben Ihres Codes einfacher / schneller machen - dies ist nicht ihr Zweck. Sie haben eine Reihe von Verwendungszwecken:
(Dies wird die Pizza-Analogie verlieren, da es nicht sehr einfach ist, eine Verwendung davon zu visualisieren.)
Angenommen, Sie machen ein einfaches Spiel auf dem Bildschirm und es werden Kreaturen angezeigt, mit denen Sie interagieren.
A: Sie können die Wartung Ihres Codes in Zukunft vereinfachen, indem sie eine lose Kopplung zwischen Ihrem Front-End und Ihrer Back-End-Implementierung einführen.
Sie könnten dies zunächst schreiben, da es nur Trolle geben wird:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
Vorderes Ende:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
Zwei Wochen später entscheidet das Marketing, dass Sie auch Orks brauchen, da diese auf Twitter darüber lesen. Sie müssten also Folgendes tun:
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
Vorderes Ende:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
Und Sie können sehen, wie dies anfängt, chaotisch zu werden. Sie können hier eine Schnittstelle verwenden, damit Ihr Front-End einmal geschrieben und (hier das wichtige Bit) getestet wird. Anschließend können Sie nach Bedarf weitere Back-End-Elemente einstecken:
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
Frontend ist dann:
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
Das Frontend kümmert sich jetzt nur noch um die Schnittstelle ICreature - es geht nicht um die interne Implementierung eines Trolls oder eines Orks, sondern nur um die Tatsache, dass sie ICreature implementieren.
Ein wichtiger Punkt, den Sie unter diesem Gesichtspunkt beachten sollten, ist, dass Sie auch leicht eine abstrakte Kreaturenklasse hätten verwenden können, und aus dieser Perspektive hat dies den gleichen Effekt.
Und Sie könnten die Kreation in eine Fabrik extrahieren:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
Und unser Frontend würde dann werden:
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
Das Frontend muss jetzt nicht einmal mehr auf die Bibliothek verweisen, in der Troll und Orc implementiert sind (vorausgesetzt, die Fabrik befindet sich in einer separaten Bibliothek) - es muss überhaupt nichts über sie wissen.
B: Angenommen, Sie haben Funktionen, die nur einige Kreaturen in Ihrer ansonsten homogenen Datenstruktur haben , z
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
Frontend könnte dann sein:
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C: Verwendung für die Abhängigkeitsinjektion
Die meisten Abhängigkeitsinjektions-Frameworks sind einfacher zu bearbeiten, wenn eine sehr lockere Kopplung zwischen dem Front-End-Code und der Back-End-Implementierung besteht. Wenn wir unser Fabrikbeispiel oben nehmen und unsere Fabrik eine Schnittstelle implementieren lassen:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
Unser Front-End könnte dies dann (z. B. einen MVC-API-Controller) über den Konstruktor injizieren lassen (normalerweise):
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Mit unserem DI-Framework (z. B. Ninject oder Autofac) können wir sie so einrichten, dass zur Laufzeit eine Instanz von CreatureFactory erstellt wird, wenn eine ICreatureFactory in einem Konstruktor benötigt wird - dies macht unseren Code schön und einfach.
Dies bedeutet auch, dass wir beim Schreiben eines Komponententests für unseren Controller eine verspottete ICreatureFactory bereitstellen können (z. B. wenn die konkrete Implementierung einen DB-Zugriff erfordert, möchten wir nicht, dass unsere Komponententests davon abhängen) und den Code in unserem Controller einfach testen können .
D: Es gibt andere Verwendungszwecke, z. B. haben Sie zwei Projekte A und B, die aus "alten" Gründen nicht gut strukturiert sind, und A hat einen Verweis auf B.
In B finden Sie dann Funktionen, die eine Methode bereits in A aufrufen müssen. Sie können dies nicht mit konkreten Implementierungen tun, da Sie einen Zirkelverweis erhalten.
Sie können eine Schnittstelle in B deklarieren lassen, die die Klasse in A dann implementiert. Ihrer Methode in B kann eine Instanz einer Klasse übergeben werden, die die Schnittstelle problemlos implementiert, obwohl das konkrete Objekt vom Typ in A ist.