こんにちは.はまじです.


今回からデザインパターンシリーズということで,各回1つずつ(2つもあるかも)デザインパターンをご紹介していきます.



Chain of Responsibility パターン


Chain of Responsibility パターンは,いわゆる「責任のたらい回し」です.

自分自身の手に負えないものは別のやつに任せよう,という感じです.

普通やってしまいそうな,大元のクラスのメソッドのなかで if文を山のように書くというクソみたいな方法が,このパターンを適応するとすごいシンプルになります.


最近ポーカーの役判定プログラムを書いていましたが,複合役のないポーカーではまさにうってつけのパターンだと言えます.


試しにパターン適応前のコードの例をお見せします.




public int getMatchedPokerHand(Player player) {
    Hand hand = player.getHand();
    if (hand.isRoyalStraightFlash()) {
        return ROYAL_STRAIGHT_FLASH;
    }
    if (hand.isStraightFlash()) {
        return STRAIGHT_FLASH;
    }
    if(hand.isFourCards()) {
        return FOUR_CARDS;
    }
    // continues...
}

普通のポーカーなら役の数が少ないのでまだましですが,これが100個とかになってくると発狂してきます.


麻雀のような複合役のあるものには適応しづらいですが,判定方法が複雑かつ順序が決まっているものに対して効果抜群の効力を発揮出来ます.




設計




こんなかんじです.



役クラスを抽象クラスにしておき,それを継承した子クラスで判定方法実装するという形になっています.



コード


とりあえず重要なところだけを抜粋してお見せします.

PlayerクラスとかHandクラスとか,中身は妄想してください.

あと,AbstractPokerHand#matches の引数がPlayer型なのは,可読性向上のためにわざとやってます.




AbstractPokerHand.java


public abstract class AbstractPokerHand {
    private AbstractPokerHand nextPokerHand;
    private String pokerHandName;

    public AbstractPokerHand(String pokerHandName) {
        this.pokerHandName = pokerHandName;
    }
    public AbstractPokerHand setNext(AbstractPokerHand next) {
        this.nextPokerHand = next;
        return next;
    }
    public final AbstractPokerHand getMatchedPokerHand(Player player) {
        if ( this.matches(player) ) {
            return this;
        } else if (nextPokerHand != null) {
            return nextPokerHand.getMatchedPokerHand(player);
        } else {
            return null;
        }
    }
    protected abstract boolean matches(Player player);

    @Override
    pubilc String toString() {
        return "Your hand is " + this.pokerHandName;
    }
}


キモなのが,matchesメソッドを抽象メソッドにすることです.

各子クラス(具体的な役のクラス)で,与えられた手と自分自身の役が一致しているかを判定するアルゴリズムを実装します.

RoyalStraightFlash.java


public class RoyalStraightFlash extends AbstractPokerHand {
    public RoyalStraightFlash() {
        super("Royal Straight Flash");
    }

    protected boolean matches(Player player) {
        Hand hand  = player.getHand();
        return hand.isFlash() && 
            hand.containsCardNumberOf(10) &&
            hand.containsCardNumberOf(11) &&
            hand.containsCardNumberOf(12) &&
            hand.containsCardNumberOf(13) &&
            hand.containsCardNumberOf(1);
    }
}



TwoPairs.java


public class TwoPairs extends AbstractPokerHand {
    public TwoPairs() {
        super("Two Pairs");
    }

    protected boolean matches(Player player) {
        Hand hand = player.getHand();
        return hand.getNumberOfPairs() == 2;
    }
}



Poker.java


public class Poker {
    public static AbstractPokerHand getHandRule() {
        AbstractPokerHand royalStraightFlash = new RoyalStraightFlash();
        AbstractPokerHand straightFlash = new StraightFlash();
        AbstractPokerHand fourCards = new FourCards();
        // continues…
        AbstractPokerHand onePair = new OnePair();
        AbstractPokerHand noPairs = new NoPairs();
        royalStraightFlash.setNext(straightFlash).setNext(fourCards)
            .setNext(continues…).setNext(onePair).setNext(noPairs);
        return royalStraightFlash;
    }

    public static void main(String[] args) {
        Player player = new Player();
        AbstractPokerHand handRule = getHandRule();
        player.setHand( Hand.createStraightFlashHand() );
        System.out.println( handRules.getMatchedPokerHand(player) );
        // => Your hand is Straight Flash
    }
}


どうでしょうか?

if文の数も2回で済み,ネストもほとんどなく実装できます.

開発現場でガード節っぽい if文が山のように出てきたときにこのパターンを適応できないか考えてみましょう!!