mohuneko’s blog

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

【Java】Visitorパターン【デザインパターン】

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

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

目次

Visitorパターンについて

Visitorパターンとは

  • Visitorパターンは、データ構造と処理を分離するパターンです
  • データ構造の中にアクセスする"訪問者クラス"を用意し、訪問者クラスに処理を任せます
  • 新しい処理を追加したいときは新しい訪問者を作り、データ構造の方は、訪問者をアクセスさせたら良いことになります

サンプルプログラム

  • Visitorパターンを使って、ディレクトリ、ファイルの一覧を表示する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Visitor 訪問者を表す抽象クラス
Element Visitorクラスのインスタンス(訪問者)を受け入れるデータ構造を表すインターフェース
ListVisitor (ConcreteVisitor) Visitorクラスのサブクラスで、ファイル、ディレクトリの一覧を表示するクラス
Entry FileとDirectoryのスーパークラスとなる抽象クラス
File (ConcreteElement) ファイルを表すクラス
Directory ( ConcreteElement / ObjectStructure ) ディレクトリを表すクラス

f:id:mohuNeko:20201229233214p:plain

Visitorクラス

  • 訪問者を表す抽象クラスです
  • 訪問者は訪問先のデータ構造である、FileとDirectoryに依存しています
  • 2つのvisitメソッドは、引数の型によって、メソッドの識別を行います(オーバーロード
public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}

Elementインターフェース

  • 訪問者を受け入れるインターフェースです
public interface Element {
    public abstract void accept(Visitor v);
}

Entryクラス

  • Elementインターフェースを実装します
  • acceptメソッドを実際に実装するのはEntryのサブクラス(File、Directory)クラスです
import java.util.Iterator;

public abstract class Entry implements Element {
    public abstract String getName();       // 名前を得る
    public abstract int getSize();      // サイズを得る
    public Entry add(Entry entry) throws FileTreatmentException {   // エントリを追加する
        throw new FileTreatmentException();
    }
    public Iterator iterator() throws FileTreatmentException {    // Iteratorの生成
        throw new FileTreatmentException();
    }
    public String toString() {       // 文字列表現
        return getName() + " (" + getSize() + ")";
    }
}

Fileクラス

  • acceptメソッドを実装します
  • v.visit(this);で Visitorのvisitメソッドを呼び出します
public class File extends Entry {
    private String name;
    private int size;
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    public String getName() {
        return name;
    }
    public int getSize() {
        return size;
    }
    public void accept(Visitor v) {
        v.visit(this);
    }
}

Directoryクラス

  • v.visit(this);で Visitorのvisitメソッドを呼び出します
import java.util.Iterator;
import java.util.ArrayList;

public class Directory extends Entry {
    private String name;                    // ディレクトリの名前
    private ArrayList dir = new ArrayList();      // ディレクトリエントリの集合
    public Directory(String name) {         // コンストラクタ
        this.name = name;
    }
    public String getName() {         
        return name;
    }
    public int getSize() {         
        int size = 0;
        Iterator it = dir.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            size += entry.getSize();
        }
        return size;
    }
    public Entry add(Entry entry) {         // エントリの追加
        dir.add(entry);
        return this;
    }
    public Iterator iterator() {      // Iteratorの生成
        return dir.iterator();
    }
    public void accept(Visitor v) {         // 訪問者の受け入れ
        v.visit(this);
    }
}

ListVisitorクラス

  • Visitorのサブクラスで、データ構造の中を歩き、一覧を表示するクラスです
  • acceptメソッドはvisitメソッドを呼び、visitメソッドはacceptメソッドを呼びます
  • visitメソッドとacceptメソッドが互いに相手を呼び出しているということです
import java.util.Iterator;

public class ListVisitor extends Visitor {
    private String currentdir = "";        // 現在のディレクトリ名

    public void visit(File file) {       // Fileクラスのインスタンスに行う処理
        System.out.println(currentdir + "/" + file);
    }
    public void visit(Directory directory) {   // Directoryクラスのインスタンスに行う処理
        System.out.println(currentdir + "/" + directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
}

FileTreatmentException クラス

public class FileTreatmentException extends RuntimeException {
    public FileTreatmentException() {
    }
    public FileTreatmentException(String msg) {
        super(msg);
    }
}

Mainクラスで動作確認

  • Directoryの中身を表示するのに、"表示を行う訪問者"である、ListVisitorクラスのインスタンスを使います
  • ディレクトリの表示も、データ構造内の各要素に対して行う処理であるので、Visitor側で実装しています
public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("Making root entries...");
            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));
            rootdir.accept(new ListVisitor());              

            System.out.println("");
            System.out.println("Making user entries...");
            Directory kiki = new Directory("kiki");
            Directory nausicaa = new Directory("nausicaa");
            Directory chihiro = new Directory("chihiro");
            usrdir.add(kiki);
            usrdir.add(nausicaa);
            usrdir.add(chihiro);
            kiki.add(new File("diary.html", 100));
            kiki.add(new File("Composite.java", 200));
            nausicaa.add(new File("memo.tex", 300));
            chihiro.add(new File("game.doc", 400));
            chihiro.add(new File("junk.mail", 500));
            rootdir.accept(new ListVisitor());              
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

Visitor/Elementの処理のまとめ

  • Directory・Fileクラスのインスタンスに対しては、acceptメソッドが呼ばれます
  • acceptメソッドはインスタンスごとに一回しか呼ばれません
  • ListVisitorメソッドのインスタンスに対して、visit(Directory)・visit(File)が呼ばれます
  • visit(Directory)やvisit(File)を処理しているのは、1つのListVisitorインスタンスです
    • ListVisitorにvisitの処理が集中しています
  • Visitor パターンでは、受入者 ( ConcreteAcceptor / ConcreteElement ) のacceptメソッドを呼び出すと、 訪問者 (ConcreteVisitor) のvisitメソッド が呼び出され、実行する処理が決定します。このような "2重呼び出し"を ダブルディスパッチ (2 重振り分け) と呼びます

Visitorパターンのメリット

  • Visitorパターンの目的は、データ構造と処理を分離することです
  • データ構造は、要素を集合としてまとめたり、要素間を繋いだりしてくれます
    • そのデータ構造を保持しておくことと、その構造を基礎とした処理を書くことは別のものです。
  • 訪問者役 (ListVisitor) は、受け入れ役 (File、Directory) とは独立して開発することができます
    • つまり、Visitorパターンは、受け入れ役のクラスの部品としての独立性を高めていることになります
    • 処理の内容をFileクラスやDirectoryクラスのメソッドとして実装してしまうと、新しい処理を追加して機能拡張するたびにFileクラスやDirectoryクラスを修正する必要があります

今日のポイント

  • Visitorパターンは、データ構造と処理を分離するパターンです
    • データ構造の中にアクセスする訪問者クラスを用意し、訪問者クラスに処理を任せます
  • Visitorパターンの目的は、データ構造と処理を分離することです
  • 訪問者役 (ListVisitor) は、受け入れ役 (File、Directory) とは独立して開発することができ、受け入れ役のクラスの部品としての独立性を高めていることになります
    • 部品としての独立性を高いということは、The Open-Closed Principle の原則に則っています
      • "ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張のために開いていて、修正のために閉じていなければならない"
      • つまり、変更が発生した場合は、"既存のコードには修正を加えずに、新しくコードを追加するだけで対応しましょう"

 本日もお疲れ様です😊  

 

 

【Java】Decoratorパターン【デザインパターン】

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

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

目次

Decoratorパターンについて

Decoratorパターンとは

  • Decoratorパターンは、オブジェクトに対してデコレーション (飾り付け) を行うパターンです
  • スポンジに苺、チョコ、生クリーム。。とデコレーションするように、オブジェクトも機能を一つ一つ被せてデコレーションしていくイメージです

サンプルプログラム

  • Decoratorパターンを使って、文字列の周りに飾り枠をつけて表示する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Display 文字列表示用の抽象クラス
StringDisplay 1行だけの文字列表示用のクラス
Border 飾り枠を表す抽象クラス
SideBorder 左右に飾り枠をつけるクラス
FullBorder 上下左右に飾り枠をつけるクラス

f:id:mohuNeko:20201229222052p:plain

Displayクラス

  • 複数行の文字列を表示するための抽象クラスです
  • showメソッドでは、getRowsメソッドで行数を取得し、getRowTextメソッドで表示する文字列を取得し、forループを使って全行を表示します
  • showはgetRows、getRowTextメソッドの抽象メソッドを使ったTemplate Methodパターンになっています
public abstract class Display {
    public abstract int getColumns();               // 横の文字数を得る
    public abstract int getRows();                  // 縦の行数を得る
    public abstract String getRowText(int row);     // row番目の文字列を得る
    public void show() {                            // 全部表示する
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}

StringDisplayクラス

  • StringDisplayクラスは、一行の文字列を表示するクラスです
  • StringDisplayクラスはDisplayクラスのサブクラスなので、Displayクラスで宣言している抽象メソッドを実装します
  • stringフィールドに表示する文字列を保持します
public class StringDisplay extends Display {
    private String string;      // 表示文字列
    public StringDisplay(String string) {      // 引数で表示文字列を指定
        this.string = string;
    }
    public int getColumns() {    // 文字数
        return string.getBytes().length;
    }
    public int getRows() {   // 行数は1
        return 1;
    }
    public String getRowText(int row) {   // rowが0のときのみstringを返す
        if (row == 0) {
            return string;
        } else {
            return null;
        }
    }
}

Borderクラス

  • 飾り枠を表すクラスです
  • Displayクラスを継承しているので、飾り枠は中身と同じメソッドを持つことになります
    • インターフェース的には、飾り枠と中身を同一視できることになります
  • "中身"はSrtingDisplayクラスなのか、Borderクラスのサブクラスのインスタンス(飾り枠)かは分かりません
    • Borderクラスのサブクラスのインスタンスも、さらにdisplayフィールドを持ちます
public abstract class Border extends Display {
    protected Display display;    // この飾り枠がくるんでいる"中身"を指す
    protected Border(Display display) { // インスタンス生成時に"中身"を引数で指定
        this.display = display;
    }
}

SideBorderクラス

  • Borderクラスのサブクラスの一つで、具体的な飾り枠です
  • SideBorderクラスはスーパークラスで宣言したメソッドを全て実装しているので、抽象クラスではありません
public class SideBorder extends Border {

    private char borderChar;    // 飾りとなる文字

    public SideBorder(Display display, char ch) {   // コンストラクタでDisplayと飾り文字を指定
        super(display);
        this.borderChar = ch;
    }
    public int getColumns() {  // 中身の両側+飾り文字分を加えた
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {     // 行数は中身と同じ
        return display.getRows();
    }
    public String getRowText(int row) {  // 中身の指定行の両側+飾り文字
        return borderChar + display.getRowText(row) + borderChar;
    }
}

FullBorderクラス

  • Borderクラスのサブクラスの一つで、具体的な飾り枠です
public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }
    public int getColumns() {  // 中身の両側+左右の飾り文字分を加えた
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {    //中身の行数+上下の飾り文字分を加えた
        return 1 + display.getRows() + 1;
    }
    public String getRowText(int row) {   // 指定した行の内容
        if (row == 0) {    // 上端の枠
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {     // 下端の枠
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {      // それ以外
            return "|" + display.getRowText(row - 1) + "|";
        }
    }
    private String makeLine(char ch, int count) {   // 文字chをcount個連続させた文字列を作る
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

Mainクラスで動作確認

public class Main {
    public static void main(String[] args) {
        Display b1 = new StringDisplay("Hello, world."); //飾りなし
        Display b2 = new SideBorder(b1, '#'); //#の文字で左右に飾り付け
        Display b3 = new FullBorder(b2); //全体の飾り枠をつけた
        b1.show();
        b2.show();
        b3.show();
        Display b4 =  //何重にも重ねて枠をつける
                    new SideBorder(
                        new FullBorder(
                            new FullBorder(
                                new SideBorder(
                                    new FullBorder(
                                        new StringDisplay("こんにちは。")
                                    ),
                                    '*'
                                )
                            )
                        ),
                        '/'
                    );
        b4.show();
    }
}

Decoratorパターンのメリット

Decoratorパターンでは、飾り枠(Border)も中身(StringDisplay)も共通のインターフェースを持っています。 インターフェースは共通ですが、包めば包むほど機能が追加されていきます。 その際に、包まれる方を修正する必要はありません。 包まれるものを変更することなく、機能の追加を行うことができます。

今日のポイント

  • Decoratorパターンは、オブジェクトに対してデコレーション (飾り付け) を行うパターンです

 本日もお疲れ様です😊  

 

 

【Java】Compositeパターン【デザインパターン】

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

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

目次

Compositeパターンについて

Compositeパターンとは

  • 容器と中身を同一視し、再帰的な構造を作るパターンです
  • Compositeは、混合物や複合物という意味です。
    • ディレクトリとファイルをまとめてディレクトリエントリとして扱うように、容器と中身を同じ種類のものとして扱うと便利な場合があります
    • 例えば、容器の中に中身を入れても、容器を入れることもできます。このようにして、再帰的な構造を作ることができます

サンプルプログラム

  • Compositeパターンを使って、ファイルとディレクトリを模式的に表現する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Entry(Component) Fileと Directoryを同一視するクラス
File(Leaf ファイルを表すクラス
Directory(Composite) ディレクトリを表すクラス
FileTreatmentException ファイルにEntryを追加しようとした時に起きる例外クラス

f:id:mohuNeko:20201229201649p:plain

Entryクラス

  • 抽象クラスで、ディレクトリエントリを表現します
    • サブクラスで、FileクラスとDiirectoryクラスが作られます
  • printListは引数なしとありの2つのメソッドがあり、メソッドをオーバーロード(多重継承)しています
  • printList()はpublic、printList(String)はprotectedとして、Entryクラスのサブクラスでのみ使われるようにしています
public abstract class Entry {
    public abstract String getName();                               // 名前を得る
    public abstract int getSize();                                  // サイズを得る
    public Entry add(Entry entry) throws FileTreatmentException {   // エントリを追加する
        throw new FileTreatmentException();
    }
    public void printList() {                                       // 一覧を表示する
        printList("");
    }
    protected abstract void printList(String prefix);               // prefixを前につけて一覧を表示する
    public String toString() {                                      // 文字列表現
        return getName() + " (" + getSize() + ")";
    }
}

Fileクラス

  • Entryクラスで定義していた、抽象クラス(getName,getSize,printList(String))を全て実装します
public class File extends Entry {
    private String name;
    private int size;
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    public String getName() {
        return name;
    }
    public int getSize() {
        return size;
    }
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
    }
}

Directoryクラス

  • getSizeメソッドの変数sizeにentryのサイズを加えていますが、このentryはFileのインスタンスでも、Directoryクラスのインスタンスでも、良いような使用になっています
    • これが、容器と中身を同じものとみなす、ということになります
    • entryはEntryのサブクラスのインスタンスなので、getSizeを呼ぶことができます
  • Compositeパターンの再帰的構造が、getSizeメソッドの再帰的呼び出しに対応しています (printListも同様)
import java.util.Iterator;
import java.util.ArrayList;

public class Directory extends Entry {
    private String name;   // ディレクトリの名前
    private ArrayList directory = new ArrayList();    // ディレクトリエントリの集合
    public Directory(String name) {   // コンストラクタ
        this.name = name;
    }
    public String getName() {  // 名前を得る
        return name;
    }
    public int getSize() {  // サイズを得る
        int size = 0;
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            size += entry.getSize();
        }
        return size;
    }
    public Entry add(Entry entry) {  // エントリの追加
        directory.add(entry);
        return this;
    }
    protected void printList(String prefix) {   // エントリの一覧
        System.out.println(prefix + "/" + this);
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry)it.next();
            entry.printList(prefix + "/" + name);
        }
    }
}

FileTreatmentExceptionクラス

  • ファイルにaddメソッドを呼んでしまったときに投げられる例外です
public class FileTreatmentException extends RuntimeException {  // RuntimeExceptionで正しいか?
    public FileTreatmentException() {
    }
    public FileTreatmentException(String msg) {
        super(msg);
    }
}

Mainクラスで動作確認

public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("Making root entries...");
            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));
            rootdir.printList();

            System.out.println("");
            System.out.println("Making user entries...");
            Directory kiki = new Directory("kiki");
            Directory nausicaa = new Directory("nausicaa");
            Directory chihiro = new Directory("chihiro");
            usrdir.add(kiki);
            usrdir.add(nausicaa);
            usrdir.add(chihiro);
            kiki.add(new File("diary.html", 100));
            kiki.add(new File("Composite.java", 200));
            nausicaa.add(new File("memo.tex", 300));
            chihiro.add(new File("game.doc", 400));
            chihiro.add(new File("junk.mail", 500));
            rootdir.printList();
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

Compositeパターンのメリット

  • 全てのオブジェクト(File・Directory)は、共通の抽象クラス(Entry)を持っているので、クライアントから見て、中身(File・Directory)を意識する必要がなく、一様に扱うことができます
  • 新たなクラスを追加した場合でも、基底クラス (Entry) のインターフェースが変わらなければ、 Clientの処理には影響しません

今日のポイント

  • Compositeパターンとは、容器と中身を同一視し、再帰的な構造を作るパターンです
    • つまり複数個のものを集めて、あたかも一つのものであるかのように取り扱います
  • 全ての Leaf、Composite オブジェクトは、共通の抽象クラスを持っているので、 Client から見て、中身を意識する必要がなく、一様に扱うことができます

 本日もお疲れ様です😊  

 

 

【Java】Strategyパターン【デザインパターン】

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

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

目次

Strategyパターンについて

Strategyパターンとは

  • 問題を解くためのアルゴリズムをごっそり交換できるパターンです
  • アルゴリズム (方法・戦略・方策) を簡単に変更し、同じ問題を別の方法で解くことができます

サンプルプログラム

  • Strategyパターンを使って、じゃんけんする例を取り上げます。"勝ったら次も同じ手を出す戦略"と、"一回前の手から次の手を確率的に計算する戦略"があります
  • 各クラスの役割は以下のようになっています
名前 役割
Hand 手を表すクラス
Strategy 戦略を表すインターフェース
WinningStrategy (ConcreteStrategy) 勝ったら次も同じ手を出す戦略を表すクラス
ProbStrategy (ConcreteStrategy) 一回前の手から次の手を確率的に計算する戦略を表すクラス
Player (Context) じゃんけんを行うプレイヤー

f:id:mohuNeko:20201229151617p:plain

Handクラス

  • グー・チョキ・パーを0・1・2のintで表現します
  • Handクラスではインスタンスは3つしか作られません (Singletonパターンの一種)
  • (this.handvalue + 1) % 3 == h.handvalueでは、じゃんけんに勝ったかどうかを判断しています
    • thisの値に1を加えたものがhの手の値(thisがグーならhがチョキ、thisがチョキならhはパー)になっていたら、trueです
    • %3で剰余を取っているのは、thisが2(パー)の時に、1を加えて0 (グー)になるようにするためです
public class Hand {
    public static final int HANDVALUE_GUU = 0;  
    public static final int HANDVALUE_CHO = 1;  
    public static final int HANDVALUE_PAA = 2;  
    public static final Hand[] hand = {         // じゃんけんの手を表す3つのインスタンス
        new Hand(HANDVALUE_GUU),
        new Hand(HANDVALUE_CHO),
        new Hand(HANDVALUE_PAA),
    };
    private static final String[] name = {      // じゃんけんの手の文字列表現
        "グー", "チョキ", "パー",
    };
    private int handvalue;                      // じゃんけんの手の値
    private Hand(int handvalue) {
        this.handvalue = handvalue;
    }
    public static Hand getHand(int handvalue) { // 値からインスタンスを得る
        return hand[handvalue];
    }
    public boolean isStrongerThan(Hand h) {     // thisがhより強いときtrue
        return fight(h) == 1;
    }
    public boolean isWeakerThan(Hand h) {       // thisがhより弱いときtrue
        return fight(h) == -1;
    }
    private int fight(Hand h) {                 // 引き分けは0, thisの勝ちなら1, hの勝ちなら-1
        if (this == h) {
            return 0;
        } else if ((this.handvalue + 1) % 3 == h.handvalue) {
            return 1;
        } else {
            return -1;
        }
    }
    public String toString() {                 
        return name[handvalue];
    }
}

Strategyインターフェース

  • じゃんけんの戦略のための抽象メソッドを集めたインターフェースです
    • nextHandは次にだす手を得るメソッドで、これが呼ばれたら、Strategyインターフェース実装クラスが次の一手を決めます
  • studyは"前回の手が勝ったかどうか"を判定するメソッドで、勝ったらstudy(true)、負けたらstudy(false)として呼び出します
  • こうすることで、Strategy実装クラスが自分の内部状態を変更し、次回以降のnextHandメソッドの戻り値を決定する材料にします
public interface Strategy {
    public abstract Hand nextHand();
    public abstract void study(boolean win);
}

WinningStrategyクラス

  • Strategyインターフェース実装クラスの一つです
    • nextHandとstudyメソッドを実装します
  • ここでの戦略は"前回勝ったら、次回も同じ手を出す"、"前回負けたら、次の手はランダムに出す"という方針です
import java.util.Random;

public class WinningStrategy implements Strategy {
    private Random random;
    private boolean won = false; //前回の結果を保持する
    private Hand prevHand; //前回の手を保持する

    public WinningStrategy(int seed) {
        random = new Random(seed); //乱数を発生させる
    }
    public Hand nextHand() {
        if (!won) { //wonがtrueの場合にfalse、wonがfalseの場合にtrue
            prevHand = Hand.getHand(random.nextInt(3)); //負けたらランダム
        }
        return prevHand; //勝ったら同じ手
    }
    public void study(boolean win) {
        won = win;
    }
}

ProbStrategyクラス

  • Strategyインターフェース実装クラスの一つです
  • 次の手は乱数で決定しますが、過去の勝ち負けの履歴を使って確率を変更します
  • historyフィールドで過去の勝敗を反映した表を表します(history[前回の手][今回の手])
    • history[0][0]の場合、グー、グーと出した時の過去の勝ち数です
    • 前回グーの場合、history[0][0],history[0][1],history[0][2]の値の和を計算し、0からその数までの乱数を計算して、それを元に次の手を決めます
  • studyメソッドは、nextHandメソッドで返した手の勝敗をもとにhistoryフィールドの内容を変更します
import java.util.Random;

public class ProbStrategy implements Strategy {
    private Random random;
    private int prevHandValue = 0;
    private int currentHandValue = 0;
    private int[][] history = {
        { 1, 1, 1, },
        { 1, 1, 1, },
        { 1, 1, 1, },
    };
    public ProbStrategy(int seed) {
        random = new Random(seed);
    }
    public Hand nextHand() {
        int bet = random.nextInt(getSum(currentHandValue));
        int handvalue = 0;
        if (bet < history[currentHandValue][0]) {
            handvalue = 0;
        } else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
            handvalue = 1;
        } else {
            handvalue = 2;
        }
        prevHandValue = currentHandValue;
        currentHandValue = handvalue;
        return Hand.getHand(handvalue);
    }
    private int getSum(int hv) {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += history[hv][i];
        }
        return sum;
    }
    public void study(boolean win) {
        if (win) {
            history[prevHandValue][currentHandValue]++;
        } else {
            history[prevHandValue][(currentHandValue + 1) % 3]++;
            history[prevHandValue][(currentHandValue + 2) % 3]++;
        }
    }
}

Playerクラス

  • じゃんけんを行う人を表現したクラスです
  • 名前と戦略を与えられてインスタンスを作成します
  • nextHandメソッドは次の手を得ますが、実際に決定するのは戦略です
    • strategy.nextHandの戻り値が、そのままPlayer のnextHandメソッドの戻り値になります
    • つまり、nextHandメソッドは処理をStrategyに委譲していることになります
  • 勝敗結果を保持して次の戦略に活かすために、strategyフィールドを通じてstudyメソッドを呼びます
    • studyメソッドを使って戦略の内部状態を変化させます
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" + "]";
    }
}

Mainクラスで動作確認

  • 名前と戦略を渡して10000回じゃんけんを行います
public class Main {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: java Main randomseed1 randomseed2");
            System.out.println("Example: java Main 314 15");
            System.exit(0);
        }
        int seed1 = Integer.parseInt(args[0]);
        int seed2 = Integer.parseInt(args[1]);
        Player player1 = new Player("Taro", new WinningStrategy(seed1));
        Player player2 = new Player("Hana", new ProbStrategy(seed2));
        for (int i = 0; i < 10000; i++) {
            Hand nextHand1 = player1.nextHand();
            Hand nextHand2 = player2.nextHand();
            if (nextHand1.isStrongerThan(nextHand2)) {
                System.out.println("Winner:" + player1);
                player1.win();
                player2.lose();
            } else if (nextHand2.isStrongerThan(nextHand1)) {
                System.out.println("Winner:" + player2);
                player1.lose();
                player2.win();
            } else {
                System.out.println("Even...");
                player1.even();
                player2.even();
            }
        }
        System.out.println("Total result:");
        System.out.println(player1.toString());
        System.out.println(player2.toString());
    }
}

コンパイルして実行します

  • javac -encoding EUC-JP Main.java
  • java Main 314 15 //乱数を引数に
Even...
Winner:[Hana:1 games, 0 win, 0 lose]
Winner:[Taro:2 games, 0 win, 1 lose]
Even...
Winner:[Hana:4 games, 1 win, 1 lose]
Winner:[Taro:5 games, 1 win, 2 lose]
Even...
Even...
Winner:[Taro:8 games, 2 win, 2 lose]
Winner:[Taro:9 games, 3 win, 2 lose]
Winner:[Taro:10 games, 4 win, 2 lose]
Even...

//中略

Winner:[Hana:9996 games, 3489 win, 3167 lose]
Even...
Even...
Even...
Total result:
[Taro:10000 games, 3167 win, 3490 lose]
[Hana:10000 games, 3490 win, 3167 lose]

Strategyパターンのメリット

  • Strategyパターンでは、アルゴリズムの部分をほかの部分と意識的に分離します
  • アルゴリズムとのインターフェースの部分だけを規定し、委譲によってアルゴリズムを利用します
  • こうすることで、アルゴリズムを改良してもっと高速にしたい場合、Strategyインターフェースを変更せず、アルゴリズムをだけを追加、修正すればいいのです
    • 委譲というゆるやかな結びつきを使っているのため、アルゴリズムを用意に切り替えることができます。
    • ゲームプログラム等では、ユーザーの選択に合わせて難易度を切り替えるなど
  • また、プログラム実行中にConcreteStrategyを変更することもできます
    • メモリの少ない環境や、多い環境で戦略を変更するなど

今日のポイント

  • Strategyパターンは、問題を解くためのアルゴリズムをごっそり交換できるパターンです
    • アルゴリズム (方法・戦略・方策) を簡単に変更し、同じ問題を別の方法で解くことができます
  • Strategyパターンでは、アルゴリズムの部分を他と意識的に分離します
  • アルゴリズムとのインターフェースの部分だけを規定し、委譲によってアルゴリズムを利用します
  • こうすることで、アルゴリズムを改良したい場合、Strategyインターフェースを変更せず、アルゴリズムをだけを追加、修正すればいいことになります

 本日もお疲れ様です😊  

 

 

【Java】Observer パターン【デザインパターン】

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

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

目次

Observerパターンについて

Observerパターンとは

  • Observerパターンは、観察対象の状態が変化すると、観察者に対して通知が行われる方式です。
  • 状態変化に応じた処理を記述するときに有効です

サンプルプログラム

  • Observerパターンを使って、数を沢山生成するオブジェクトを観察者が観察して、その値を表示する例を取り上げます
  • 表示の方法は、観察者によって変わります
  • 各クラスの役割は以下のようになっています
名前 役割
Observer 観察者を表すインターフェース
NumberGenerator(Subject) 数を生成するオブジェクトを表す抽象クラス
RandomNumberGenerator(ConcreteSubject) ランダムに数を生成するクラス
DigitObserver(ConcreteObserver) 数字で数を表す行うクラスで、Observerインターフェースを実装する
GraphObserver(ConcreteObserver) 簡易グラフで数を表す行うクラスで、Observerインターフェースを実装

f:id:mohuNeko:20210101205015p:plain

Observerインターフェース

  • updateメソッドを呼びだすのは、数を生成するNumberGeneratorです
  • 自分の内容が変更されたことをObserberに伝えます
public interface Observer {
    public abstract void update(NumberGenerator generator);
}

NumberGeneratorクラス

  • 数を生成する抽象クラスです
    • 実際の数の生成と、取得はサブクラスが実装します
import java.util.ArrayList;
import java.util.Iterator;

public abstract class NumberGenerator {
    private ArrayList observers = new ArrayList();        // Observerたちを保持
    public void addObserver(Observer observer) {    // Observerを追加
        observers.add(observer);
    }
    public void deleteObserver(Observer observer) { // Observerを削除
        observers.remove(observer);
    }
    public void notifyObservers() {               // Observerへ通知
        Iterator it = observers.iterator();
        while (it.hasNext()) {
            Observer o = (Observer)it.next();
            o.update(this);
        }
    }
    public abstract int getNumber();                // 数を取得する
    public abstract void execute();                 // 数を生成する
}

RandomNumberGeneratorクラス

  • 乱数を生成します
import java.util.Random;

public class RandomNumberGenerator extends NumberGenerator {
    private Random random = new Random();   // 乱数生成機
    private int number;                     // 現在の数
    public int getNumber() {                // 数を取得する
        return number;
    }
    public void execute() {
        for (int i = 0; i < 20; i++) {
            number = random.nextInt(50);
            notifyObservers();
        }
    }
}

DigitObserverクラス

* 観察した値を数字で表示するためのものです

public class DigitObserver implements Observer {
    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

GraphObserverクラス

  • 観察した値を*****のように簡易グラフで表示します
public class GraphObserver implements Observer {
    public void update(NumberGenerator generator) {
        System.out.print("GraphObserver:");
        int count = generator.getNumber();
        for (int i = 0; i < count; i++) {
            System.out.print("*");
        }
        System.out.println("");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
    }
}

Mainクラスで動作確認

public class Main {
    public static void main(String[] args) {
        NumberGenerator generator = new RandomNumberGenerator();
        Observer observer1 = new DigitObserver();
        Observer observer2 = new GraphObserver();
        generator.addObserver(observer1); //観察者登録
        generator.addObserver(observer2); 
        generator.execute(); //数を生成
    }
}

Observerパターンのメリット

  • 状態を持っているRandomNumberGeneratorクラスと、状態変化を通知してもらうDigitObserverクラス、GraphObserverクラスが登場します
    • その2つの役目を、ObserverインターフェースとNumberGeneratorクラスが繋ぎます
    • RandomNumberGeneratorクラスは、自分が現在監視しているの(通知する相手)が、DigitObserver/GraphObserverインスタンスなのかを知りません。
    • しかし、observersフィールドに格納されているインスタンスが、Observerインターフェースを継承していることは知っており、updateメソッドを呼び出せることが保証されています。
    • 一方、DigitObserverクラス、GraphObserverクラスは、自分を観察しているのがRandomNumberGeneratorインスタンスなのか、他のXXXNumberGeneratorインスタンスなのかを知らず、NumberGeneratorのサブクラスのインスタンスであり、getNumberメソッドを持っていることは知っています。
  • 具象クラスの交換可能性を高くするポイントは以下の2点です
    • 抽象クラスやインターフェースを使って、具象クラスから抽象メソッドを引きはがす
    • 引数でインスタンスを渡すときや、フィールドでインスタンスを保持するときは、抽象クラスやインターフェースの型にしておく
  • MVCモデルの中の、ModelとViewの関係は、 ObserverパターンのSubjectとObserverの関係に相当します
    • Modelは、"表示形式に依存しない内部モデルを操作する"役割で、Viewは "Modelをどのように見せるか" 管理します。一般的に、1つのModelに対して、複数のViewが対応します。

今日のポイント

  • Observerパターンは、観察対象の状態が変化すると、観察者に対して通知が行われる方式です
  • 状態を持っているクラス(ConcreteSubject)と、状態変化を通知してもらうクラスConcreteObserver)が登場します
  • お互いに通知する相手は知らないので、独立性を高めることができます。
  • 具象クラスの交換可能性を高くするには、以下が重要です
    • 抽象クラスやインターフェースを使って、具象クラスから抽象メソッドを引きはがす
    • 引数でインスタンスを渡すときや、フィールドでインスタンスを保持するときは、抽象クラスやインターフェースの型にしておく
  • MVCモデルの中の、ModelとViewの関係は、 ObserverパターンのSubjectとObserverの関係に相当します

 本日もお疲れ様です😊  

 

 

【Java】Bridgeパターン【デザインパターン】

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

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

目次

Bridgeパターンについて

Bridgeパターンとは

  • 機能のクラス階層と、実装のクラス階層を、橋渡しするパターンです
  • 機能のクラス階層と実装のクラス階層が階層構造の中に混在しないように、2つの独立したクラス階層に分離して、それぞれの階層の橋渡しをします

機能のクラス階層

  • クラスに新しい機能を追加したい場合は、クラス階層の中から、目的に近いクラスを探し、そのサブクラスを作成します
    • スーパークラスでは基本的な機能を持っている
    • サブクラスでは新しい機能を追加する
  • 他の機能を追加する場合は、さらに新しいサブクラスを用意して、階層構造にします

実装のクラス階層

  • 抽象メソッドでインターフェースを規定し、サブクラスで実装します
  • 役割分担をすることで部品として交換可能性が高いクラスを作ることができます
  • 新しいメソッドを追加するためではなく、役割分担のために、クラス階層を使います
    • スーパークラスは抽象メソッドでインターフェースを規定する
    • サブクラスは具象メソッドでインターフェースを実装する

サンプルプログラム

  • Bridgeパターンを使って、抽象的な何かを表じする例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Display (Abstraction) <機能のクラス階層> 表示するクラス
CountDisplay (RefinedAbstration) <機能のクラス階層> 指定回数だけ表示する機能を追加したクラス
DisplayImpl (Implementor) <実装のクラス階層> 表示するクラス
StringDisplayImpl (ConcreteImplementor) <実装のクラス階層> 文字列を使って表示するクラス

f:id:mohuNeko:20201229131321p:plain

<機能のクラス階層> Displayクラス

  • 抽象的な何かを表示するクラスで、機能のクラスの最上位にいるクラスです
  • コンストラクタには、実装を表すクラスのインスタンスを渡します
  • 引数で渡されたインスタンスはimplのフィールドに保存され、2つのクラス階層の橋になります
  • open,print,closeメソッドは、Displayクラスのインターフェースで、implフィールドの実装メソッドを用います
    • Displayのインターフェースが、DisplayImplに変換されていることになります
public class Display {

    private DisplayImpl impl; //実装を表すインスタンス

    public Display(DisplayImpl impl) {
        this.impl = impl; 
    }
    public void open() {
        impl.rawOpen();
    }
    public void print() {
        impl.rawPrint();
    }
    public void close() {
        impl.rawClose();
    }
    public final void display() {
        open();
        print();                    
        close();
    }
}

<機能のクラス階層> CountDisplayクラス

  • Displayクラスの機能に、指定回数表示する、という機能を追加したクラスです
public class CountDisplay extends Display {
    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }
    public void multiDisplay(int times) {       // times回繰り返して表示する
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}

<実装のクラス階層> DisplayImplクラス

  • 実装のクラス階層の最上位に位置する抽象クラスです
    • 抽象メソッドを3つ持ち、それぞれDisplayクラスのメソッド(open,print,close)に対応します
public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

<実装のクラス階層> StringDisplayImplクラス

  • 文字列を表示するクラスで、DisplayImplクラスのサブクラスとして抽象メソッドを実装します
public class StringDisplayImpl extends DisplayImpl {
    private String string;                              
    private int width;                                  
    public StringDisplayImpl(String string) {           
        this.string = string;                               
        this.width = string.getBytes().length;             
    }
    public void rawOpen() {
        printLine();
    }
    public void rawPrint() {
        System.out.println("|" + string + "|");         
    }
    public void rawClose() {
        printLine();
    }
    private void printLine() {
        System.out.print("+");                          
        for (int i = 0; i < width; i++) {               
            System.out.print("-");                      
        }
        System.out.println("+");                        
    }
}

Mainクラスで動作確認

  • 4クラスを組み合わせて文字列を表示します
    • Displayクラス、CountDisplayクラスも共に、StringDisplayImplクラスのインスタンスが実装を担っています
public class Main {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello, Japan.")); //Displayクラスのインスタンス代入
        Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World.")); //CountDisplayクラスのインスタンス代入
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe.")); 
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
    }
}

Bridgeパターンのメリット

  • 機能と実装のクラスを分けておくことで、それぞれのクラス階層を独立に拡張できます
    • 機能を追加したい場合、機能のクラス階層にクラスを追加します。実装のクラス階層は変更する必要がありません
    • しかも、追加した機能は全ての実装で使用できます
  • クラス間が硬い結びつきになる"継承"ではなく、緩い結びつきになる、"委譲"を行います
    • Abstraction (Display) クラスでは、implフィールドに実装のインスタンスを保持し、処理は実装クラスに任せて、たらい回しにしています
  • 実装の変更を行いたい場合は、別のConcreteImplementerのインスタンスをAbstraction やRefinedAbstration クラスに渡したらいいので、Mainクラス以外変更する必要がありません

今日のポイント

  • 機能のクラス階層と、実装のクラス階層を、橋渡しするパターンです
  • 機能のクラス階層と実装のクラス階層が階層構造の中に混在しないように、2つの独立したクラス階層に分離して、それぞれの階層の橋渡しをします
  • 機能と実装のクラスを分けておくことで、それぞれのクラス階層を独立に拡張できます
    • 機能を追加する場合、機能のクラス階層にクラスを追加します。
      • 実装のクラス階層は変更する必要がなく、追加した機能は全ての実装で使用できます
    • 実装の変更を行いたい場合は、別のConcreteImplementerのインスタンスをAbstraction やRefinedAbstration クラスに渡すだけで良く、Mainクラス以外変更する必要がありません

 本日もお疲れ様です😊  

 

 

【Java】Abstract Factoryパターン【デザインパターン】

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

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

目次

Abstract Factoryパターンについて

Abstract Factoryパターンとは

  • 部品の具体的な実装には注目せず、インターフェースに注目し、そのインターフェースで部品を組み立て製品にまとめるデザインパターンです
    • オブジェクト指向における抽象的(Abstract)とは具体的にどのように実装されているか考えずインターフェースのみに注目している状態のことです

FactoryとAbstract Factory の関係について

  • 複数のクラスを利用しているアプリケーションで、それらのインスタンスを得る際に、毎回new クラス名()で生成するのではなく、Factoryクラスを作り、そのメソッドを経由してクラスのインスタンスを得るようにします
    • こうすることで、クラスの生成処理がFactoryクラスにまとめて記述でき、インスタンスの生成処理がアプリケーションから切り離せます
    • よって、アプリケーションに変更を加えることなく、インスタンスの生成処理を変更することができるようになります
  • さらに、Factoryクラスを抽象クラスにして、クラスのインスタンスを生成するメソッドをFactoryの具象クラスに記述すると、 アプリケーションで利用しているクラスを簡単に入れ替えることができることになります

サンプルプログラム

  • Abstract Factoryパターンを使って、階層構造を持ったLink一覧を、HTML形式で出力する例を取り上げます
  • 各クラスの役割は以下のようになっています
名前 役割
Factory 抽象的な工場を表すクラス
Link、Tray、Pageを作成
Item LinkとTrayを統一的に扱うクラス
Link 抽象的な部品:HTMLリンクを表すクラス
Tray 抽象的な部品:LinkやTrayを集めたクラス
Page 抽象的な部品:HTMLページを表すクラス
ListFactory 具体的な工場を表すクラス
ListLink、ListTray、ListPageを作成
ListLink 具体的な部品:HTMLリンクを表すクラス
ListTray 具体的な部品:LinkやTrayを集めたクラス
ListPage 具体的な部品:HTMLページを表すクラス

f:id:mohuNeko:20201229094809p:plain

Itemクラス

  • LinkとTrayを同一視するためのクラスです
  • makeHTMLはサブクラスで実装します
package factory;

public abstract class Item {
    protected String caption;  //見出し
    public Item(String caption) {
        this.caption = caption;
    }
    public abstract String makeHTML(); //HTML文字列が戻る
}

Linkクラス

  • スーパークラス(Item)のmakeHTMLを実装していないので、Linkクラスも抽象クラスです
package factory;

public abstract class Link extends Item {
    protected String url;
    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}

Trayクラス

  • 複数のLinkやTrayを集めてひとまとまりにしたものを表すクラスです
    • お盆の上に箇条書き項目を乗せていくイメージです
  • Itemクラスの抽象メソッドを継承していますが、実装していないので、Trayクラスも抽象クラスです
package factory;

public abstract class Link extends Item {
    protected String url;
    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}

Pageクラス

  • PageクラスはHTMLページ全体を抽象的に表現したクラスです
    • 抽象的な製品に当たります
  • addメソッドでItemを追加し、追加したものがページで表示されます
package factory;
import java.io.*;
import java.util.ArrayList;

public abstract class Page {
    protected String title;
    protected String author;
    protected ArrayList content = new ArrayList();
    public Page(String title, String author) {
        this.title = title;
        this.author = author;
    }
    public void add(Item item) {
        content.add(item);
    }
    public void output() { //Template Methodパターン
        try {
            String filename = title + ".html";
            Writer writer = new FileWriter(filename);
            writer.write(this.makeHTML()); //自分自身のHTMLメソッド
            writer.close();
            System.out.println(filename + " を作成しました。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public abstract String makeHTML(); //抽象メソッドを呼ぶ
}

Factoryクラス

  • getFactoryメソッドで、クラス名を指定して具体的な工場のインスタンスを生成します
    • 引数のclassnameに"listfactory.ListFactory"のようにクラス名を文字列で指定します
    • forNameメソッドでクラスを動的に読み込み、newInstanceメソッドでそのクラスのインスタンスを作成します
    • 戻り値は"抽象的な工場"であることに注意です
  • 抽象的な工場で部品や製品を作る抽象メソッドを宣言し、実装はサブクラスに任せます
package factory;

public abstract class Factory {
    public static Factory getFactory(String classname) {
        Factory factory = null;
        try {
            factory = (Factory)Class.forName(classname).newInstance();
        } catch (ClassNotFoundException e) {
            System.err.println("クラス " + classname + " が見つかりません。");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory;
    }
    public abstract Link createLink(String caption, String url);
    public abstract Tray createTray(String caption);
    public abstract Page createPage(String title, String author);
}

Mainクラスで動作確認

  • 抽象的な工場を使って、抽象的な部品を作って、抽象的な製品を組み立てます
  • このクラスでは、具体的な部品、製品、工場を使っていません
  • 具体的な工場は、コマンドライン引数で指定します
    • javac -encoding EUC-JP Main.java listfactory/ListFactory.java
    • java Main listfactory.ListFactory
      • ListFactoryクラスの工場を作るときは、パッケージも含めてパスを指定します
  • factoryを使ってLinkを作り、Trayを作って、Trayの中にLink、Trayを乗せて(add)、Pageを作成(output)します
import factory.*;

public class Main {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java Main class.name.of.ConcreteFactory");
            System.out.println("Example 1: java Main listfactory.ListFactory");
            System.out.println("Example 2: java Main tablefactory.TableFactory");
            System.exit(0);
        }
        Factory factory = Factory.getFactory(args[0]);

        Link hatena = factory.createLink("はてなブログ", "https://hatenablog.com/");
        Link qiita = factory.createLink("Qiita", "https://qiita.com/");

        Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
        Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/");
        Link excite = factory.createLink("Excite", "http://www.excite.com/");
        Link google = factory.createLink("Google", "http://www.google.com/");

        Tray traymatome = factory.createTray("まとめサイト");
        traymatome.add(hatena);
        traymatome.add(qiita);

        Tray trayyahoo = factory.createTray("Yahoo!");
        trayyahoo.add(us_yahoo);
        trayyahoo.add(jp_yahoo);

        Tray traysearch = factory.createTray("検索エンジン");
        traysearch.add(trayyahoo);
        traysearch.add(excite);
        traysearch.add(google);

        Page page = factory.createPage("お気に入りリスト", "モフネコ");
        page.add(traymatome);
        page.add(traysearch);
        page.output();
    }
}
  • 実行結果

f:id:mohuNeko:20201229110912p:plain

ListFactoryクラス

  • Factoryクラスの抽象メソッド(createLink、createTray、createPage)を実装します
package listfactory;
import factory.*;

public class ListFactory extends Factory {
    public Link createLink(String caption, String url) {
        return new ListLink(caption, url);
    }
    public Tray createTray(String caption) {
        return new ListTray(caption);
    }
    public Page createPage(String title, String author) {
        return new ListPage(title, author);
    }
}

ListLinkクラス

  • Linkクラスの抽象メソッドmakeHTMLを実装します
package listfactory;
import factory.*;

public class ListLink extends Link {
    public ListLink(String caption, String url) {
        super(caption, url);
    }
    public String makeHTML() {
        return "  <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
    }
}

ListTrayクラス

  • Trayクラスのサブクラスで、makeHTMLを実装します
  • trayフィールドにHTMLに出力するItemが入っているので、これらをHTMLで表現します
  • 変数itemの中身がListLinkのインスタンスなのか、ListTrayのインスタンスなのかを気にせず、item.makeHTMLすることができます
  • つまり、変数itemはItem型で、ItemクラスでmakeHTMLメソッドが宣言されているので、そのサブクラスでは何も考えなくても、makeHTMLを呼び出せます
  • あとはitemというインスタンスが、処理の中身を知っているので、いい感じにmakeHTMLメソッドを処理してくれます
package listfactory;
import factory.*;
import java.util.Iterator;

public class ListTray extends Tray {
    public ListTray(String caption) {
        super(caption);
    }
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<li>\n");
        buffer.append(caption + "\n");
        buffer.append("<ul>\n");
        Iterator it = tray.iterator();
        while (it.hasNext()) {
            Item item = (Item)it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</li>\n");
        return buffer.toString();
    }
}

ListPageクラス

  • makeHMLメソッドでフィールドの内容を使ってページを構成します
package listfactory;
import factory.*;
import java.util.Iterator;

public class ListPage extends Page {
    public ListPage(String title, String author) {
        super(title, author);
    }
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><head><title>" + title + "</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<h1>" + title + "</h1>\n");
        buffer.append("<ul>\n");
        Iterator it = content.iterator(); //Pageクラスから継承しているフィールド
        while (it.hasNext()) {
            Item item = (Item)it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}

Abstract Factoryパターンのメリット

  • 例えば新たな具体的な工場を追加する場合、Factory、Link、Tray、Pageのサブクラス作り、それぞれの抽象メソッドを実装します
  • つまり、factoryパッケージのクラスが持っている抽象的な部分を具体化していくだけで変更が完了します
  • それに、いくら具体的な工場を追加・修正しても、抽象的な工場を修正する必要がありません
  • 一方、factoryパッケージに部品を新たに作る際には、すでに存在する具体的な工場全てに追加する必要があるので、修正が大変です
    • 例えばfactoryクラスにPictureを追加した場合、ListFactoryにcreateImageメソッドを追加し、ListPictureクラスを作成する必要があります

今日のポイント

  • 部品の具体的な実装には注目せず、インターフェースに注目し、そのインターフェースで部品を組み立て製品にまとめるデザインパターンです
  • Factoryクラスを抽象クラスにして、クラスのインスタンスを生成するメソッドをFactoryの具象クラスに記述すると、 アプリケーションで利用しているクラスを簡単に入れ替えることができることになります
  • 具体的な工場を追加したい場合、factoryパッケージのクラスが持っている抽象的な部分を具体化するだけで変更が完了します
    • いくら具体的な工場を追加・修正しても、抽象的な工場を修正する必要がありません
  • 一方、factoryパッケージに部品を新たに作る際には、すでに存在する具体的な工場全てに追加する必要があるので、修正が大変です

 本日もお疲れ様です😊