+4  A: 

Have a GameStrategy class that implements a Win method. The win method takes a list of Hands, and returns either a Hand -- if there is a winner -- or null if the game was a tie. I think the winning strategy is not really a property of the hand, but of the game. Incorporate the determination of a winner of a pair of hands into the GameStrategy class.

EDIT: potential strategy

public enum RPSEnum { Rock, Paper, Scissors }

private RPSEnum FirtRPS = RPSEnum.Rock;
private RPSEnum LastRPS = RPSEnum.Scissors;

public Hand Win( Hand firstPlayer, Hand secondPlayer )
{
    if (firstPlayer.Value == FirstRPS
        && secondPlayer.Value == LastRPS)
    {
       return firstPlayer;
    }
    else if (secondPlayer.Value == FirstRPS
             && firstPlayer.Value == LastRPS)
       return secondPlayer;
    }
    else
    {
       int compare = (int)firstPlayer.Value - (int)secondPlayer.Value;
       if (compare > 0)
       {
          return firstPlayer;
       }
       else if (compare < 0)
       {
          return secondPlayer;
       }
       else
       {
          return null;
       }       
    }
}

To add a new hand value, just add the value to RPSEnum in the proper sequence. If it is the new "lowest" hand, update FirstRPS. If it is the new "highest" hand, update LastRPS. You shouldn't need to change the actual algorithm at all.

NOTE: this is more complex than it needs to be for just three values, but the requirement was additional values be able to be added without updating much code.

tvanfosson
So how would you implement the strategy? Would you have a strategy hierarchy, with one realization for each possible match of hands? This hierarchy would grow too fast as hands are added.
wilhelmtell
Would you hold something like a matrix or a switch in the strategy instead? That is a reasonable solution. I originally wondered if I could somehow end up saying something like "result = hand1.fight(hand2)", to make client code look nice.
wilhelmtell
I would assume that the hands are ordered and have the value of the enum provide the ordering. You'd just compare values of the enum. The only special case you'd need to check is that the first hand beats the last hand -- all the other ones could be done by casting to int and using subtraction.
tvanfosson
I originally noted that it would be best to have the least possible entities that must change when the chart changes, but I accept people's point that this might not be worth the efforts.
wilhelmtell
And the need for updating FirstRPS and LastRPS can be eliminated by using RPSEnum.values()[0] and such
abyx
+2  A: 

If they have sufficient conceptual similarity, you may not want to knock yourself out reducing the coupling.

"Coupling" is really just a metric of how much code breakage there will be if the internal implementation of one thing is changed. If the internal implementation of these things is inherently sensitive to the others, then it is; reducing coupling is fine, but the software, first of all, should reflect reality.

Charlie Martin
+1  A: 

I don't think the different hands are different types: they are separate instances of the same type. That type has attributes like name, possibly picture, etc.

You initialize the game by loading, from data, the list of hand names and a matrix giving which hand beats each hand. Perhaps the data would be loaded into a Game class with a Compare method.

David Norman
A: 

In this case, the important thing is that if you compare two objects, you get back a result.

So basically, you need to decouple this comparison in such a way that you can not only add a new type of object into the mix, but also add comparison rules into the mix that can handle this object.

Now, I also commented on your question saying that "Sometimes, questions can be too generic", and the problem here is that no matter how much I tell you how to do what you ask about, it won't help you one bit about the real problem.

Unless you are building a rock-paper-scissors-X game.

Personally I would do the following, with my own IoC container.

ServiceContainer.Global.RegisterFactory<IHandType>()
    .FromDelegate(() => RandomRockScissorPaper());
ServiceContainer.Global.RegisterFactory<IHandComparison, DefaultHandComparison>();

(or rather, I would configure up the above in the app.config file or similar so that it could be changed after the project has been built).

Then, if a user/customer/end-point needs to override, to add another type, I would augment the registrations as follows (remember that the above are in app.config, so I would replace those with the ones below):

ServiceContainer.Global.RegisterFactory<IHandType>()
    .FromDelegate(() => RandomRockScissorPaper())
    .ForPolicy("original");
ServiceContainer.Global.RegisterFactory<IHandType>()
    .FromDelegate((IHandType original) => RandomRockScissorPaperBlubb(original))
    .WithParameters(
        new Parameter<IHandType>("original").WithPolicy("original"))
    .ForPolicy("new")
    .AsDefaultPolicy();

Here I add a new way to resolve IHandType, by not only keeping the original way, but adding a new one as well. This new one will be given the result of calling the old one, and then would have to internally decide if a random chance should return the fourth type, or the original type (one of the three original ones).

I would then also override the original comparison rule:

ServiceContainer.Global.RegisterFactory<IHandComparison, DefaultHandComparison>()
    .ForPolicy("original");
ServiceContainer.Global.RegisterFactory<IHandComparison, NewHandComparison>()
    .ForPolicy("new")
    .AsDefaultPolicy()
    .WithParameters(
        new Parameter<IHandType>("original"));

Here's how it would be used:

IHandType hand1 = ServiceContainer.Global.Resolve<IHandType>();
IHandType hand2 = ServiceContainer.Global.Resolve<IHandType>();
IHandComparison comparison = ServiceContainer.Global.Resolve<IHandComparison>();
if (comparison.Compare(hand1, hand2) < 0)
    Console.Out.WriteLine("hand 1 wins");
else if (comparison.Compare(hand1, hand2) > 0)
    Console.Out.WriteLine("hand 1 wins");
else
    Console.Out.WriteLine("equal");

Here's how to implement:

public interface IHandComparison
{
   Int32 Compare(IHandType hand1, IHandType hand2);
}
public class DefaultHandComparison : IHandComparison
{
    public Int32 Compare(IHandType hand1, IHandType hand2)
    {
        ... normal rules here
    }
}
public class NewHandComparison : IHandComparison
{
    private IHandComparison _Original;
    public NewHandComparison(IHandComparison original)
    {
        _Original = original;
    }
    public Int32 Compare(IHandType hand1, IHandType hand2)
    {
        if hand1 is blubb or hand2 is blubb then ...
        else
            return _Original.Compare(hand1, hand2);
    }
}

After writing all this I realize that the app.config configuration of mine won't be able to handle delegates, so a factory object would be needed, but the same still applies.

You need to both be able to resolve the way to obtain new hands, as well as to resolve the rules for what is the winning hands.

Lasse V. Karlsen
A: 

Why decouple? These elements are all intrinsically linked to each other, adding a new hand won't change that.

Just have a base Hand class that is extended by Rock, Paper and Scissors. Give the base class a .beatenby attribute that takes one of the other classes of type Hand. If you wind up with a situation where a hand can be beaten by more than one other hand, simply make the .beatenby attribute take an array instead.

Soviut
A: 

Several other answers have provided very flexible and very decoupled solutions... and they're way more complex than they need to be. The short answer to decoupling is double dispatch, which very few languages natively support. The GoF Visitor Pattern exists to simulate double dispatch in languages that support single dispatch (any OO language supports this, even C with function pointers).

Here's a slightly bigger example in the same spirit. Suppose you're monitoring HTTP traffic, and you're trying to classify patterns based a combination of the request method and the response code. Instead of looking at this series:

rock/paper
paper/scissors
paper/paper
...

...you're looking at this series:

GET/200
GET/304
POST/401
POST/200
...

If the system had HttpRequest and HttpResponse objects, the simplest way to dispatch would be to use a single function that routes to all of the possible choices:

HttpRequest req;
HttpResponse resp;
switch ((req.method, resp.code)) {
    case (GET, 200): return handleGET_OK(req, resp);
    case (GET, 304); return handleGET_NotModified(req, resp);
    case (POST, 404): return handlePOST_NotFound(req, resp);
    ...
    default: print "Unhandled combination"; break;
}

With this, your underlying objects are not impacted by adding new types. However, your classification method is still very simple and thus maintainable for other developers. If you wanted (or if your language makes it easy), you can turn that switch statement into some sort of a function map and register handlers different combinations of request methods and response codes on startup.

Tom