mohuneko’s blog

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

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

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

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

目次

Prototypeパターンについて

Prototypeパターンとは

サンプルプログラム

  • Prototypeパターンを使って、文字列を枠線で囲んだり、下線を引いて表示する例を取り上げます
  • 各クラスやインターフェースの役割は以下のようになっています
名前 役割
Product (Prototype) 抽象メソッドuse,createCloneを宣言するインターフェース
Manager (Client) createCloneを使ってインスタンスを複製するクラス
ManagerBox (ConcretePrototype) 文字列を枠線で囲って表示するクラス
use,createCloneを実装
UnderlinePen (ConcretePrototype) 文字列に下線を引いて表示するクラス
use,createCloneを実装

f:id:mohuNeko:20201227155401p:plain

Productインターフェース

  • java.lang.Cloneableインターフェースを継承し、cloneメソッドで自動的に複製を行います
    • Cloneableインターフェースはマーカーインターフェースです
package framework;
import java.lang.Cloneable;

public interface Product extends Cloneable {
    public abstract void use(String s);
    public abstract Product createClone();
}

Managerクラス

  • インスタンスの複製を行います
  • showcaseフィールドに、インスタンスの名前とインスタンスの対応関係をHashMapで保存します
  • registerメソッドでは、製品の名前とProductインターフェースを与えられたら、showcaseに登録します
    • 引数のProductインターフェース(proto)は、Productインターフェースを実装したクラスのインスタンスで、use,createCloneメソッドを呼ぶことができます
  • ProductインターフェースやManagerクラスにはManagerBoxやUnderlinePenクラス名は使用しません
    • つまり、ProductインターフェースやManagerクラスは、独立に修正することができます(疎結合
    • Productインターフェースが、Managerクラスと他のクラスを紐づけています
package framework;
import java.util.*;

public class Manager {

    private HashMap showcase = new HashMap();

    public void register(String name, Product proto) {
        showcase.put(name, proto);
    }

    public Product create(String protoname) {
        Product p = (Product)showcase.get(protoname);
        return p.createClone();
    }
}

MessageBoxクラス

  • Productインターフェースを実装します
  • createCloneメソッドで呼び出しているcloneメソッドは自分自身の複製を行うメソッドで、インスタンスが持つフィールド値も新しいインスタンスにコピーします
    • cloneメソッドは浅いコピー(shallow copy)で、参照のみコピーします
  • cloneメソッドでコピーできるのは、java.lang.Cloneable;インターフェース実装クラスのみです
  • インターフェースが実装されていないと、CloneNotSupportedExceptionが投げられるので、try...catch構文で例外処理を実装します
    • MessageBoxクラスで実装しているProductインターフェースで、Cloneableインターフェースを実装しているので、例外は投げられません
  • cloneメソッドは自分とそのサブクラスからしか呼べないので、別のメソッド(createClone)で包む必要があります
import framework.*;

public class MessageBox implements Product {

    private char decochar; //囲み文字(*など)

    public MessageBox(char decochar) {
        this.decochar = decochar;
    }

    public void use(String s) {

        int length = s.getBytes().length;
        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
        System.out.println(decochar + " "  + s + " " + decochar);

        for (int i = 0; i < length + 4; i++) {
            System.out.print(decochar);
        }
        System.out.println("");
    }

    public Product createClone() {

        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
       return p;
    }
}

UnderlinePenクラス

  • Productインターフェースを実装します
  • 与えられた文字列を引用符で括り、ulcharで与えられた文字で下線を引きます
import framework.*;

public class UnderlinePen implements Product {

    private char ulchar;

    public UnderlinePen(char ulchar) {
        this.ulchar = ulchar;
    }

    public void use(String s) {
        int length = s.getBytes().length;

        System.out.println("\""  + s + "\"");
        System.out.print(" ");
        for (int i = 0; i < length; i++) {
            System.out.print(ulchar);
        }
        System.out.println("");
    }

    public Product createClone() {

        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
}

Mainクラスで動作確認

import framework.*;

public class Main {
    public static void main(String[] args) {

        // 準備
        Manager manager = new Manager();
        UnderlinePen upen = new UnderlinePen('~');
        MessageBox mbox = new MessageBox('*');
        MessageBox sbox = new MessageBox('/');
        manager.register("strong message", upen);
        manager.register("warning box", mbox);
        manager.register("slash box", sbox);

        // 生成
        Product p1 = manager.create("strong message");
        p1.use("Hello, world.");
        Product p2 = manager.create("warning box");
        p2.use("Hello, world.");
        Product p3 = manager.create("slash box");
        p3.use("Hello, world.");
    }
}

Prototypeパターンのメリット

  • ManagerクラスはcreateCloneを呼びますが、どのクラスのインスタンスを複製するかは知らなくとも、Productインターフェースを実装するクラスであれば、そのインスタンスを複製できます
    • Productインターフェースを実装するクラスのインスタンスを作成し、Managerに登録しておけば好きな時に複製できます
  • →こうすることで雛形をいくらでも増やすことができます~で文字列に下線を引いたり、*/で文字列に枠をつける等)
    • それぞれ別のクラスにしていたら、クラスの数が多くなり管理が大変になります。。
  • インスタンスの複製(clone)をframeworkパッケージに閉じ込めています
  • インスタンス生成するフレームワークを、特定のインスタンスに依存しない様に作ることができます

今日のポイント

  • Prototypeパターンは、クラスからインスタンスを生成するのではなく、模型となるインスタンスから別のインスタンスを複製(clone)して生成するパターンです
  • PrototypeのインターフェースやClientクラスにはConcretePrototypeクラス名は使用しません(=疎結合である)
    • つまり、PrototypeのインターフェースやClientクラスは、切り離して部品として再利用、修正することができます
  • インスタンスの複製(clone)をframeworkパッケージに閉じ込めることで、インスタンス生成するフレームワークを、特定のインスタンスに依存しない様に作ることができます

 本日もお疲れ様です😊