技術メモ

技術メモ

ラフなメモ

Javaのデザインパターンを整理(構造編)

Javaデザインパターンについて復習します。プログラムをデザインするにあたって、参考にできる引き出しは多く持っておきたいです。

デザインパターンとは

過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したもの

有名なのはGoFの23のデザインパターンです。それぞれのデザインパターンは3つの分類に分かれています。

  • オブジェクトの「生成」に関するパターン
  • プログラムの「構造」に関するパターン
  • オブジェクトの「振る舞い」に関するパターン

構造に関するパターン

パターン名 概要
Adapter 元々関連性のない2つのクラスを接続するクラスを作る
Composite 再帰的な構造を表現する
Bridge クラスなどの実装と、呼出し側の間の橋渡しをするクラスを用意し、実装を隠蔽する
Decorator あるインスタンスに対し、動的に付加機能を追加する。Filterとも呼ばれる
Facade 複数のサブシステムの窓口となる共通のインタフェースを提供する
Flyweight 多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす
Proxy 共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる

Adapterパターン

  • 概要
    • インターフェースに互換性のないクラスどおしを組み合わせることを目的としています。
    • 既存システムと新システムのインターフェースの違いを吸収するAdaptorを用いることで、少ない変更で既存システムを新システムに適応することができます。
    • Adaptorパターンの実装は、継承によるものと、委譲によるものと、2種類存在します。
  • 何が嬉しいか
    • 既存のクラスを変更することなく、別インターフェースで処理を呼び出すことができます。
継承によるAdaptor
クラス図

f:id:tutuz:20190511170603p:plain

実装例

ProductAdapterクラスが既存のProductクラスのメソッドをラップしています。

interface ProductPrice {
    public int getPrice();
}

class Product {
    private int cost;

    public int getCost() {
        return cost;
    }
}

class ProductAdapter extends Product implements ProductPrice {
    public int getPrice() {
        return this.getCost();
    }
}
委譲によるAdaptor
クラス図

f:id:tutuz:20190511171239p:plain

実装例
abstract class ProductPrice {
    abstract int getPrice();
}

class Product {
    private int cost;

    public int getCost() {
        return cost;
    }
}

class ProductAdapter extends ProductPrice {
    private Product product;

    public ProductAdapter() {
        this.product = new Product();
    }

    @Override
    public int getPrice() {
        return product.getCost();
    }
}

Compositeパターン

  • 概要
  • 何が嬉しいか
    • 再帰的な構造の記述が容易になり、メンテナンス性も向上する。
クラス図

このパターンは実装例と合わせてクラス図を見ると分かりやすいかな...と思っています。

f:id:tutuz:20190511175305p:plain

実装例

Component

public interface Entry {
    public void find(int depth);

    void add(Entry entry);

    void remove();
}

Composite

import java.util.ArrayList;
import java.util.List;

public class Directory implements Entry {

    private String name;
    private List<Entry> list;

    public Directory(String name) {
        this.name = name;
        list = new ArrayList<>();
    }

    @Override
    public void find(int depth) {
        for (int i = 0; i < depth; i++) System.out.print("  ");
        System.out.printf("dir  :%s\n", this.name);
        for (Entry entry : list) {
            entry.find(depth + 1);
        }
    }

    @Override
    public void add(Entry entry) {
        list.add(entry);
    }

    @Override
    public void remove() {
        for (Entry entry : list) {
            entry.remove();
        }
        System.out.printf("directory [%s] removed.\n", this.name);
    }
}

Leaf

public class File implements Entry {

    private String name;
    public File(String name) {
        this.name = name;
    }

    @Override
    public void find(int depth) {
        for (int i = 0; i < depth; i++) System.out.print("  ");
        System.out.printf("file :%s\n", this.name);
    }

    @Override
    public void add(Entry entry) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void remove() {
        System.out.printf("%s removed.\n", this.name);
    }
}

client

public class Main {

    public static void main(String[] args) {
        File file1 = new File("file1");
        File file2 = new File("file2");

        Directory dir1 = new Directory("dir1");
        dir1.add(file1);
        Directory dir2 = new Directory("dir2");
        dir1.add(dir2);
        dir2.add(file2);
        dir1.find(0);
    }
}

結果

dir  :dir1
  file :file1
  dir  :dir2
    file :file2

なお、client側で親子関係が循環した場合は、Component#operation() を実行すると無限ループになります。つまり以下のようの実装すると無限ループになります。

public class Main {
    public static void main(String[] args) {
        Directory dir3 = new Directory("dir3");
        dir3.add(dir3);
        dir3.find(0);
    }
}

参考

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで