How can one describe a rock-paper-scissors relatio

2019-02-12 07:46发布

问题:

Let's say I have the following structure:

abstract class Hand {}

class Rock extends Hand {}
class Paper extends Hand {}
class Scissors extends Hand {}

The goal is to make a function (or a method) Hand::compareHands(Hand $hand1, Hand $hand2), which would return the winning hand in a rock-paper-scissors match.

That would be very easy with a bunch of ifs, but the point is to have a more robust structure, that's relying on polymorphism rather than on procedural code.

P.S. this is done in actual production code, if someone is asking. This isn't some sort of challenge or homework. (It's not really rock-paper-scissors, but you get the point).

回答1:

The sole nature of your hand is that it is beating one single other one.

Then you want to not repeat the code while having one concrete type per hand-form, so you need to parametrize. Depending on the level of freedom you can allow, this can be as simple as a protected member:

abstract class Hand {

    protected $beats;

    final public function beats(Hand $opponent) {

        return $opponent instanceof $this->beats;
    }
}

class Rock extends Hand {

    protected beats = 'Scissors';
}

class Paper extends Hand {

    protected beats = 'Rock';
}

class Scissors extends Hand {

    protected beats = 'Paper';
}

I think this is the standard template method pattern here, in a very simple form.

Compare this with Lusitanian's answer who should get the credits for the actual code, I just have re-sorted it a little bit. But only a very little.

Additionally I need to give credits to @Leigh for the far better function and parameter naming. This should reduce the need of comments.

The second alternative that Lusistanian suggests can be represented with the strategy pattern. It is also somewhat straight forward:

class EvaluateHands
{
    private $rules;

    public function __construct(array $rules)
    {
        $this->rules = $rules;
    }

    public function compareHands(Hand $hand1, Hand $hand2)
    {
        return $this->rules[get_class($hand1)] === get_class($hand2) ? $hand1 : $hand2;
    }
}

new EvaluateHands(
    array(
        'Rock' => 'Scissors',
        'Paper' => 'Rock',
        'Scissor' => 'Paper'
    )
);

The comparison between two hands has been fully encapsulated into the EvaluateHands type which is even configureable (if the rules of the game change), while the hands would stay the same:

abstract class Hand {}

class Rock extends Hand {}

class Paper extends Hand {}

class Scissors extends Hand {}

Credits for this code go to gordon (next to Lusistanian).



回答2:

How about this?

class Scissors extends Hand implements Beats<Paper> {}

where Beats<> is a generic interface whose signature looks like:

interface Beats<Hand> {}


回答3:

From PHP chat

OOP-style

<?php
interface Hand {
    function beats(Hand $hand);
}

class Rock implements Hand {
    public function beats(Hand $hand) {
        return $hand instanceof Scissors;
    }
}
class Paper implements Hand {
    public function beats(Hand $hand) {
        return $hand instanceof Rock;
    }
}

class Scissors implements Hand {
    public function beats(Hand $hand) {
        return $hand instanceof Paper;
    }
}

Simple function

<?php
const PAPER = 1;
const ROCK = 2;
const SCISSORS = 3;

function whichHandWon($hand1, $hand2) {
    $winners = [PAPER => ROCK, ROCK => SCISSORS, SCISSORS => PAPER];
    return intval($winners[$hand1] !== $hand2) + 1;
}


标签: php oop