デザインパターン「template method」「Strategy」

最近デザインパターンをまた勉強していたのでその考察点を記載します。

ソースは基本的に結城浩『増補改訂版Java言語で学ぶデザインパターン入門』(デザパタ本)から引用しています。

継承を用いる「Template Method」

Template Methodパターンは、abstractクラスにな抽象メソッドを記載し、それを継承したサブクラスに実装を強制するパターンです。 すると、似たような動きをするメソッド群を一つのabstract classの下に集結させることができます。

public abstract class AbstractDisplay { 
    public abstract void open();        
    public abstract void print();       
    public abstract void close();       
    public final void display() {       
        open();                             
        for (int i = 0; i < 5; i++) {       
            print();                    
        }
        close();                            
    }
}

継承したサブクラスは以下のようになります。

public class CharDisplay extends AbstractDisplay {  
    private char ch;                                
    public CharDisplay(char ch) {                   
        this.ch = ch;                               
    }
    public void open() {                           
        System.out.print("<<");                     
    }
    public void print() {                           
        System.out.print(ch);                       
    }
    public void close() {                           
        System.out.println(">>");                  
    }
}

これにより何が嬉しくなるかというと、Displayクラス群を他の場所で使いたい場合、それが具体的にどのサブクラスなのかを気にしなくてよくなります。

public class Main {
    public static void main(String[] args) {
        AbstractDisplay d1 = new CharDisplay('H');                  
        AbstractDisplay d2 = new StringDisplay("Hello, world.");    
        d1.display();                                               
        d2.display();                                               
    }
}

Mainクラスで AbstractDisplayクラスに代入する必然性は、これだけでは見えづらいように見えますが…。

委譲を用いる「Strategy」

Template Methodの関連パターンとして紹介されているのが、Strategyパターンです。extendsではなくimplementsが使われています。

デザパタ本では、じゃんけんを例に手の出し方を決めるStrategyインタフェースを記載しています。

public interface Strategy {
    public abstract((別にここはabstractでなくてもいい気がする)) Hand nextHand();
    public abstract void study(boolean win); // 勝敗に応じて手を変えることを想定
}

出す手を決めたり、勝敗数を保持したりするPlayerクラスは以下になります。

public class Player {
    private String name;
    private Strategy strategy;
    private int wincount;
    private int losecount;
    private int gamecount;
    public Player(String name, Strategy strategy) {        
        this.name = name;
        this.strategy = strategy;
    }
    public Hand nextHand() {                                
        return strategy.nextHand();
    }
    public void win() {                 
        strategy.study(true);
        wincount++;
        gamecount++;
    }
    public void lose() {                
        strategy.study(false);
        losecount++;
        gamecount++;
    }
    public void even() {               
        gamecount++;
    }
    public String toString() {
        return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";
    }
}

Strategyインタフェースを実装したクラスは省略します。

「継承より委譲」の理解とデフォルトメソッド

やっていることが類似している継承と委譲ですが、調べてみると「多くの場面で、継承より委譲を用いるべき」という主張(定説?)がありました。 が、これが元を辿ろうとするとはっきりした記述をネット上で見つけられず、一旦保留とします。

僕が言えそうなのは疎結合にできるならするべきだし、委譲ならそれが実現しやすかったよねということです。

しかし、Java8ではデフォルトメソッドが登場したことにより、ますます両者の区別が難しくなったように見えます。

d.hatena.ne.jp

導入の背景について調べてみましたがこれしか出てきませんでした。

Java SE 8のラムダ式の基礎──なぜ必要なのか? 従来記法のリファクタリングを通して、その本質を理解する - page3 - builder by ZDNet Japan

これってJava側の都合では?って印象を受けました。まあしかし深いところについては分からず……。