2016-04-13 7 views
0

Ich habe diese Basis-Schnittstelle zu erklären, die das Verhalten eines Kartenspieler (Mensch und AI) beschreibt:Wie nicht näher bezeichnete Methode Argument in C#

interface ICardPlayer<T> 
    where T: Carta, new() 
{ 
    // some methods here 
    T Pop(UNSPECIFIED ARGUMENTS); 
} 

Pop-Funktionen ermöglicht es dem Cardplayer eine Karte von seinem Deck zu verwerfen , aber auf dieser Ebene weiß ich nicht, ob der Spieler ein menschlicher Spieler oder ein KI-Spieler ist. Wenn es ein menschlicher Spieler ist, ist die Methode T Pop(uint index);, aber wenn es ein AI-Spieler ist, wird die Methode T Pop() sein. In diesem Fall muss die Methode ohne Parameter sein, da die Pop-Funktion eines KI-Spielers die Methoden der KI aufrufen würde, um die korrekte Karte zu verwerfen. So werde ich auch diese beiden Schnittstellen:

interface IHumanCardPlayer<T> : ICardPlayer<T> 
    where T: Carta, new() 
{ 
    // some methods here 
    T Pop(uint index); 
} 

interface IAICardPlayer<T> 
    where T: Carta, new() 
{ 
    // some methods here 
    T Pop(); 
} 

Ich muss alle zwei Methoden nicht haben: wenn der Spieler ein menschlicher Spieler ist, hat er die Pop-Methode nennen es den Index der Karte geben die er verwerfen würde, und er kann die Methode nicht ohne Argumente aufrufen. Dasselbe gilt, wenn es sich um einen AI-Player handelt: Er muss die Pop-Methode aufrufen, ohne Argumente zu geben, und er kann die Methode Pop(index) nicht aufrufen.

Also, gibt es eine Möglichkeit, das Pop(UNSPECIFIED ARGUMENTS) in der ICardPlayer<T> Schnittstelle zu schreiben oder muss ich 2 verschiedene Pop-Methoden ohne Vererbung schreiben?

+1

Sie könnten dem Parameter einen Standardwert zuweisen und nach diesem Wert suchen. Zum Beispiel 'T Pop (uint index = -1);' – Wjdavis5

+0

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

+0

Oder machen Sie es NULL-fähig 'T Pop (uint? Index);' – Wjdavis5

Antwort

3

Das würde den Zweck einer Schnittstelle vereiteln, da es unmöglich wäre, die Methode aufzurufen (jeder mögliche Aufruf kann die falschen Argumente für die bestimmte Unterklasse haben).

Sie können das nicht tun.

+0

Ich fühle mich wie "kann nicht" ist falsch und irreführend. Sie können sicherlich erreichen, was das OP tun möchte. Es passt vielleicht nicht in das "richtige" Paradigma. Aber zu diesem Zeitpunkt ist es ziemlich eigensinnig. – Wjdavis5

3

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.

+0

vielen Dank, ich habe viel mehr über Schnittstellen verstanden :) – Clyky

Verwandte Themen