mohuneko’s blog

かんばる駆け出しエンジニアのブログです

【Java】Template Methodパターン【デザインパターン】

駆け出しエンジニアがデザインパターンをもくもく勉強します

 こんな本で勉強しています🌟

目次

Template Methodパターンについて

Template Methodパターンとは

  • スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるデザインパターンです
  • スーパークラスでは、テンプレートとなる抽象メソッドを定義します
    • テンプレートとは、

      文書などのコンピュータデータを作成する上で雛形となるデータ

  • のことで、例えば、カリグラフィーなど文字を綺麗に書くためのテンプレート板などがあります
    • テンプレート板を使えば、どんなペンでなぞっても同じデザインの文字を描くことができます
  • オブジェクト指向言語では、同じような処理を1つのクラスとして纏めるように設計するので、Template Methodパターンに則っているということですね〜💡

サンプルプログラム

  • Template Methodパターンを使って、文字や文字列を5回繰り返して表示する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
AbstractDisplay (AbstractClass) displayメソッドのみ実装されている抽象クラス
CharDisplay (ConcreteClass) open、print、closeを実装しているクラス
文字表示をする
StringDisplay (ConcreteClass) open、print、closeを実装しているクラス
文字列表示をする

f:id:mohuNeko:20201227121550p:plain

AbstractDisplayクラス

  • 抽象クラスAbstractDisplayでは、 サブクラスに実装をまかせる抽象メソッド (open,print,close) を用意します
  • AbstractDisplayクラスでは、displayメソッドを実装します
    • まずopenして、5回printを繰り返して、最後にcloseします
  • displayメソッドがテンプレートメソッドになります
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();                            
    }
}

CharDisplayクラス

  • CharDisplayクラスは、AbstractDisplayのサブクラスです
  • コンストラクタで渡された文字chを、フィールドに保存します
  • スーパークラスの抽象メソッド (open,print,close) を、オーバーライドして実装します
    • printメソッドでは、フィールドに記憶しておいた文字を1個表示します
    • AbstractDisplayクラスのdisplayメソッドから繰り返して呼び出されます
  • スーパークラスで宣言したメソッドをサブクラスで実装する際には、実装したメソッドが呼ばれるタイミングを理解しておきましょう
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(">>");                
    }
}

StringDisplayクラス

  • StringDisplayクラスは、AbstractDisplayのサブクラスです
  • コンストラクタで渡された文字列stringと、バイト単位の幅widthを、フィールドに記憶します
  • スーパークラスの抽象メソッド (open,print,close) を、オーバーライドして実装します
    • open、closeメソッドでは、StringDisplayクラスのメソッドprintLineで線を引きます
    • printメソッドでは、フィールドに記憶しておいた文字列の前後に"|"をつけて表示します
  • printLineメソッドはprivateなので、このクラスの中だけで使われます
public class StringDisplay extends AbstractDisplay {    
    private String string;                              // 表示するべき文字列
    private int width;                                    // バイト単位で計算した文字列の幅

    public StringDisplay(String string) {               
        this.string = string;                           
        this.width = string.getBytes().length;          
    }

    public void open() {                               
        printLine();                                    
    }

    public void print() {                               
        System.out.println("|" + string + "|");    
    }

    public void close() {                            
        printLine();                                    
    }

    private void printLine() {                        
        System.out.print("+");                          
        for (int i = 0; i < width; i++) {               
            System.out.print("-");                     
        }

        System.out.println("+");                        
    }
}

Mainクラスで動作確認

  • CharDisplay、StringDisplayのインスタンスを1個作ります
  • 作成したインスタンス(d1,d2,d3)は、AbstractDisplayのサブクラスのインスタンスなので、 継承したdisplayメソッドを呼び出すことができます
  • 実際の動作は個々のクラスCharDisplayやStringDisplayで定まります
public class Main {
    public static void main(String[] args) {

        AbstractDisplay d1 = new CharDisplay('H');                           
        AbstractDisplay d2 = new StringDisplay("Hello, world.");    
        AbstractDisplay d3 = new StringDisplay("こんにちは。");     
        d1.display();                                              
        d2.display();                                              
        d3.display();                                               

    }
}

Template Methodパターンのメリット

LSP (The Liskov Substitution Principle) とは

  • クラス同士の継承関係が正しいか、を検証する原則で、リスコフの置換原則ともいわれます
  • Template Methodパターンに限らない、継承の一般的な原則です
  • 具体的には、スーパークラスとサブクラスを定義するとき、サブクラス別のサブクラスに置き換えても、スーパークラスがは正常に動作しないといけないというルールです
  • 継承関係でバグが発生しないように実装するときに気をつけるべきは、以下の点です
    • サブクラスでオーバーライドされた結果、動作を意図しないものに変えない
      • 例:privateやfinalにしてアクセスを制限する
    • スーパークラスの意図から外れてしまう結果になるようなオーバーライドはしない
    • スーパークラスサブクラスの種類を判定して処理をするような設計にしない

今日のポイント

  • Template Methodパターンは、スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるデザインパターンです
  • 同じような流れの処理を共通化したい時に、Template Methodパターンを適応することができます
  • スーパークラス(抽象クラス)では、 サブクラスに実装をまかせる抽象メソッド を用意します
    • サブクラスで、スーパークラスの抽象メソッドを、オーバーライドして実装します
    • サブクラスで実装したメソッドをスーパークラステンプレートメソッドで繰り返し呼び出します
  • スーパークラス型の変数に 、サブクラスのインスタンスを代入することで、サブクラスの種類を特定しなくても動くコードになります
  • よって、リスコフの置換原則に則った設計ができているということになります

 本日もお疲れ様です😊