user5892486
user5892486

Reputation:

How to declare unspecified method's argument in C#

I have this base interface, which describes the behaviours of a card player (human and AI):

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

Pop functions allows the CardPlayer to discard a card from his deck, but at this level I don't know if the player is an human player or an AI player. If it's an human player, the method will be T Pop(uint index); but if it is an AI player the method will be T Pop(). In this case the method has to be without parameters because the Pop function on an AI player would call the AI's methods to discard the correct card. So I will have also these two interfaces:

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();
}

I don't have to have all 2 methods: if the player is an human player, he has to call the Pop method giving it the index of the card which he would discard, and he can't call the method without arguments. The same is if it is an AI player: he has to call the Pop method without giving it any arguments, and he can't call the method Pop(index).

So, is there a way to write that Pop(UNSPECIFIED ARGUMENTS) in the ICardPlayer<T> interface or do I have to write 2 distinct Pop methods without using inheritance?

Upvotes: 0

Views: 182

Answers (2)

Luaan
Luaan

Reputation: 63772

First, you're not using inheritance. That isn't necessarily good or bad.

Second, you can't, and it's a good thing.

Interfaces represent some kind of public interface that exposes access to a common set of functionality. In general, any implementation of the interface should be equally valid - they should be interchangeable, and the code that uses those interfaces shouldn't care about which specific implemetation you're being given. Obviously, this is not true in your case - you want to invoke the interface with different arguments based on what specific implementation you're taking. That goes against the whole idea of using interfaces (and inheritance) in the first place.

However, you just painted yourself in the corner needlessly. You made the interface too big, so to speak, as evidenced by your need to have two separate sets of arguments to the same method.

Instead, separate the human behaviour and AI behaviour on a different level. ICardPlayer will always take the int argument. The only difference is how the argument is produced in a different place - in case of a human player, it is the product of the UI that asks him for a card to pick. In the case of an AI player, it's produced by some algorithm.

So you'll have an interface that represents the action "picking a card":

interface IPlayer
{
  int PickCardToDiscard();
}

And you leave the how to the implementation:

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
  }
}

Now your interfaces are consistent, and you've moved the specific implementations to the place they belong. When instantiating the ICardPlayer, you always know whether you want a human player or an AI player. But that's the only place where you care. There's the power of the abstraction - well designed interfaces allow you to isolate yourself from the specifics and focus on the abstract (which is a much smaller problem space). When the game engine wants to pick a card, all it has to do is call

var cardToDiscard = deck.Pop(player.PickCardToDiscard());

It doesn't care whether the player is a human or an AI, and it gives you opportunities to wire in other implementations as well - like different AI strategies, or a human playing over the network.

Remember, every piece of code better pay for itself - if it's not beneficial, it's actively detriminal. The same goes for abstractions in general - if the abstraction doesn't pay rent, fix it or lose it. In your case, the abstraction is clearly silly - it doesn't work even for the very first two cases you explicitly designed it for. It's the kind of thing you might do if you have an assignment like "Write code using interfaces", and you have no idea how to design interfaces that actually add value to your code. There's no point in interfaces for interface sake, or in abstractions for abstraction sake. Mkae code pay rent.

Finally, there's cases where having optional parameters makes sense. But the key point is that those parameters must still be part of the contract, and all implementations must be equally valid. For example, you might have a logging interface like this:

interface ILogger
{
  void Log(string message, int? severity);
}

You can specify severity, or you can use null - but the choice doesn't depend on the specific implementation of ILogger, it only depends on the caller - sometimes, he wants to specify a severity, and sometimes he doesn't.

Upvotes: 3

SLaks
SLaks

Reputation: 887767

That would defeat the purpose of an interface, since it would be impossible to call the method (any possible call may have the wrong arguments for the particular subclass).

You can't do that.

Upvotes: 3

Related Questions