我有这个基本界面,它描述了一个牌手(人和AI)的行为:
interface ICardPlayer<T>
where T: Carta, new()
{
// some methods here
T Pop(UNSPECIFIED ARGUMENTS);
}
Pop功能允许CardPlayer从他的套牌中丢弃一张牌,但在这个级别我不知道该玩家是人类玩家还是AI玩家。
如果它是人类玩家,则该方法将为T Pop(uint index);
,但如果它是AI玩家,则该方法将为T Pop()
。在这种情况下,该方法必须没有参数,因为AI播放器上的Pop功能会调用AI的方法来丢弃正确的卡。
所以我也会有这两个接口:
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();
}
我不必拥有所有两种方法:如果玩家是人类玩家,他必须调用Pop方法,为其提供他将丢弃的卡的索引,并且他不能不带参数调用方法。
同样如果它是一个AI玩家:他必须调用Pop方法而不给它任何参数,并且他不能调用方法Pop(index)
。
那么,有没有办法在Pop(UNSPECIFIED ARGUMENTS)
接口中编写ICardPlayer<T>
或者我是否必须编写2个不同的Pop方法而不使用继承?
答案 0 :(得分:3)
这会破坏接口的目的,因为调用该方法是不可能的(任何可能的调用可能都有特定子类的错误参数)。
你做不到。
答案 1 :(得分:3)
首先,您不使用继承。这不一定是好事或坏事。
其次,你不能,这是一件好事。
接口代表某种公共接口,它公开对一组通用功能的访问。通常,接口的任何实现都应该同样有效 - 它们应该是可互换的,并且使用这些接口的代码不应该关心您给出的具体实现。显然,在您的情况下情况并非如此 - 您希望根据您正在采用的特定实现来调用具有不同参数的接口。这与首先使用接口(和继承)的整个想法背道而驰。
然而,你只是不必要地把自己画在角落里。你可以说,界面太大了,你需要为同一个方法提供两组独立的参数。
相反,将人类行为和AI行为分开到不同的层面。 ICardPlayer
将始终获取int
参数。唯一的区别在于如何在不同的地方产生论证 - 在人类玩家的情况下,它是UI的产品,要求他选择卡片。对于AI播放器,它是由某种算法产生的。
因此,您将拥有一个代表行动的界面&#34;选择一张卡&#34;:
interface IPlayer
{
int PickCardToDiscard();
}
你离开了如何实施:
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
}
}
现在您的界面是一致的,并且您已将特定实施移动到它们所属的位置。当实例化 ICardPlayer
时,您总是知道您是否需要人类玩家或AI玩家。但那只是你所关心的唯一的地方。抽象的力量 - 精心设计的界面允许您将自己与具体事物隔离开来并专注于抽象(这是一个小得多的问题空间)。当游戏引擎想要选择一张卡时,它只需要调用
var cardToDiscard = deck.Pop(player.PickCardToDiscard());
它并不关心玩家是人还是人工智能,并且它为你提供了在其他实现中连线的机会 - 比如不同的人工智能策略,或者人类在网络上玩。
请记住,每一段代码都能更好地为自己付出代价 - 如果它没有益处,它就会积极地堕落。一般来说抽象也是如此 - 如果抽象不支付租金,修复或丢失它。在你的情况下,抽象显然是愚蠢的 - 即使你明确设计它的前两个案例它也不起作用。如果您有像#34;使用接口和#34;编写代码这样的任务,那么您可能会做的事情,并且您不知道如何设计实际为您的代码增加价值的接口。接口方面没有任何意义,或者为了抽象而抽象。 Mkae代码支付租金。
最后,有可选参数的情况很有意义。但关键是这些参数必须仍然是合同的一部分,并且所有实现必须同样有效。例如,您可能有这样的日志记录界面:
interface ILogger
{
void Log(string message, int? severity);
}
你可以指定严重性,或者你可以使用null
- 但选择并不取决于ILogger
的具体实现,它只取决于调用者 - 有时候,他想要指定严重性,有时他不会。