Zuerst verwenden Sie keine Vererbung. Das ist nicht unbedingt gut oder schlecht.
Zweitens können Sie nicht, und es ist eine gute Sache.
Schnittstellen stellen eine Art öffentliche Schnittstelle dar, die den Zugriff auf eine gemeinsame Funktionalität ermöglicht. Im Allgemeinen sollte jede Implementierung der Schnittstelle gleichermaßen gültig sein - sie sollte austauschbar sein, und der Code, der diese Schnittstellen verwendet, sollte sich nicht darum kümmern, welche spezifische Implementierung Sie erhalten. Offensichtlich trifft dies in Ihrem Fall nicht zu - Sie möchten die Schnittstelle mit verschiedenen Argumenten aufrufen, basierend auf der Implementierung, die Sie vornehmen. Das widerspricht der Idee, Schnittstellen (und Vererbung) zu verwenden.
Allerdings haben Sie sich einfach unnötig in die Ecke gemalt. Sie haben die Schnittstelle sozusagen zu groß gemacht, wie Sie sehen, wenn Sie zwei separate Sätze von Argumenten für dieselbe Methode benötigen.
Stattdessen trennen Sie das menschliche Verhalten und AI-Verhalten auf einer anderen Ebene. ICardPlayer
wird immer nehmen Sie die int
Argument. Der einzige Unterschied ist, wie das Argument an einem anderen Ort erzeugt wird - im Falle eines menschlichen Spielers ist es das Produkt der Benutzeroberfläche, das ihn nach einer Karte fragt. Im Falle eines AI-Players wird es von einem Algorithmus erzeugt.
So haben Sie eine Schnittstelle, die die Aktion „Kommissionierung eine Karte“ steht für:
interface IPlayer
{
int PickCardToDiscard();
}
Und Sie verlassen die, wie man die Umsetzung:
public class HumanPlayer: IPlayer
{
private readonly IGui gui;
public HumanPlayer(IGui gui)
{
this.gui = gui;
}
public int PickCardToDiscard()
{
return gui.AskForCardSelection("Pick a card to discard.");
}
}
public class StupidPlayer: IPlayer
{
public int PickCardToDiscard()
{
return 42; // Feeling lucky
}
}
Jetzt ist Ihre Schnittstellen konsistent sind, und Sie haben die spezifischen Implementierungen an den Ort verschoben, an den sie gehören. Wenn die ICardPlayer
instanziiert wird, können Sie immer wissen, ob Sie einen menschlichen Spieler oder einen KI-Spieler wollen. Aber das ist die nur Stelle, wo Sie sich interessieren. Da ist die Kraft der Abstraktion - gut gestaltete Interfaces erlauben es, sich von den Besonderheiten zu isolieren und sich auf das Abstrakte zu konzentrieren (was ein viel kleinerer Problembereich ist). Wenn die Spiel-Engine eine Karte holen will, hat alles, um es zu tun zu nennen, ist
var cardToDiscard = deck.Pop(player.PickCardToDiscard());
Es kümmert sich nicht darum, ob der Spieler ein Mensch ist oder eine KI, und es gibt Ihnen Möglichkeiten Draht in anderen Implementierungen als gut - wie verschiedene KI-Strategien oder ein Mensch, der über das Netzwerk spielt.
Denken Sie daran, jedes Stück Code besser für sich selbst zahlen - wenn es nicht vorteilhaft ist, ist es aktiv detriminal. Das Gleiche gilt für Abstraktionen im Allgemeinen - wenn die Abstraktion keine Miete zahlt, sie repariert oder verliert. In Ihrem Fall ist die Abstraktion eindeutig albern - sie funktioniert nicht einmal für die ersten beiden Fälle, für die Sie sie explizit entworfen haben. Es ist die Art von Dingen, die Sie tun könnten, wenn Sie eine Aufgabe wie "Code über Schnittstellen schreiben" haben, und Sie haben keine Ahnung, wie Sie Interfaces entwerfen, die Ihren Code wirklich wertschätzen. Es gibt keinen Sinn in Interfaces für die Schnittstelle, oder in Abstraktionen zur Abstraktion. Mkae Code Miete Miete.
Schließlich gibt es Fälle, in denen optionale Parameter sinnvoll sind. Aber der entscheidende Punkt ist, dass diese Parameter immer noch Teil des Vertrags sein müssen, und alle Implementierungen müssen gleichermaßen gültig sein. Zum Beispiel könnten Sie eine Logging-Schnittstelle wie diese:
interface ILogger
{
void Log(string message, int? severity);
}
Sie Schwere angeben können, oder Sie können null
verwenden - aber die Wahl hängt nicht von der konkreten Umsetzung von ILogger
, es hängt nur von dem Anrufer - Manchmal möchte er einen Schweregrad angeben und manchmal nicht.
Sie könnten dem Parameter einen Standardwert zuweisen und nach diesem Wert suchen. Zum Beispiel 'T Pop (uint index = -1);' – Wjdavis5
Der springende Punkt von Interfaces ist, Details der Implementierung zu verbergen und sicherzustellen, dass jede Instanz dieselbe API hat. Warum definieren Sie nicht die Schnittstelle mit 'T Pop (uint index)' und ignorieren einfach das Argument in der AI. – Toxantron
Oder machen Sie es NULL-fähig 'T Pop (uint? Index);' – Wjdavis5