Abstract Factory パターン

1月 1, 2003 · Posted in GoFデザインパターン · Comment 

抽象的な工場を作る

製品を作成する工場と、その工場が作成する製品を共に抽象化することにより、クライアントが目的にあった工場を選択できるようにする。

特徴

工場を抽象化する。

クライアントが指定する工場により、作成できる製品が異なる。

最大限に抽象化を行う

工場と製品を共に抽象化することにより、組み合わせを柔軟に指定できる。

どんなときに有効か。

部品の組み合わせパターンが多いときに有効。
組み合わせて使うサブクラス群をまとめて切り替えるときに有効。

サンプルコード

※上手くまとまっていないので、後日修正予定。

Factory クラス(抽象的な工場)

abstract class Factory{
    public abstract Header getHeader();
    public abstract Body getBody(String document);
    public abstract Footer getFooter();
}

Item クラス(抽象的な部品)

abstract class Item{
    public abstract void print();
}

Body クラス(抽象的な部品)

abstract class Body extends Item{
}

Header クラス(抽象的な部品)

abstract public class Header extends Item{
}

Footer クラス(抽象的な部品)

abstract public class Footer extends Item{
}

– ここまでが抽象的なクラス

NormalFactory クラス(具体的なクラス)

class NormalFactory extends Factory{
    public Header getHeader(){
        return new NormalHeader();
    }

    public Body getBody(String document){
        return new NormalBody(document);
    }

    public Footer getFooter(){
        return new NormalFooter();
    }
}

NormalHeader クラス(具体的なクラス)

public class NormalHeader extends Header{
    public void print(){
        System.out.println("--top--");
    }
}

NormalBody クラス(具体的なクラス)

public class NormalBody extends Body{
    private String document;
    public NormalBody(String document){
        this.document = document;
    }

    public void print(){
        System.out.println(document);
    }
}

NormalFooter クラス(具体的なクラス)

public class HtmlFooter extends Footer{
    public void print(){
        System.out.println("");
        System.out.println("");
    }
}

– ここまでが具体的なクラス

Main クラス(クライアント)

class Main{
    public static void main(String[] args){
        Factory factory = new NormalFactory();
        printDocument(factory);
    }

    public static void printDocument(Factory factory){
        Header header = factory.getHeader();
        Body body = factory.getBody("一行目\r\n二行目");
        Footer footer = factory.getFooter();

        header.print();
        body.print();
        footer.print();
    }
}

かなり柔軟な構成。具体的には、新たにHTML形式で出力したくなった場合などに、既存のクラスに変更を加える必要が一切なく、Factoryと各部品を新たに作成するだけでよい。

抽象度をかなり高くしているため、一度定義した部品を自由に組み合わせて使用することができる。

部品が増えるとクラスが増えてしまう。新しい種類の部品を追加する場合、修正範囲が広がってしまう。などの欠点も持っている。

Adapter パターン

1月 1, 2003 · Posted in GoFデザインパターン · Comment 

クラスの橋渡し

クラスのインタフェースを変更したいが、クラスそのものは変更したくない場合や、互換性のないクラスに関係を持たせたい場合に有効。継承、もしくは委譲を使用してアダプタクラスを作成する。

特徴

異なる環境に対応させるために、仲介者を用意する。

(例)外部とのインタフェースがUSBだけのパソコンで、PS/2のマウスを使用したい。そこで変換用コネクタを使用する。

外部とのインタフェースがUSBだけのパソコン

class UsbComputer{
    useUsb(){}
}

変換用コネクタ(仲介者)。PS/2マウスを使用する。

class Connecter extends UsbComputer{
    usePS2(){
        super.useUsb();
    }
}

サンプルコード

既存のクラス

class RectInfo{
    private int width;
    private int height;

    // 幅と高さを設定
    public RectInfo(int w, int h){
        width  = w;
        height = h;
    }

    // 幅を返す
    public int getWidth(){
        return width;
    }

    // 高さを返す
    public int getHeight(){
        return height;
    }
}

アダプタクラス

class RectInfoAdapter{
    private RectInfo rectinfo;

    public RectInfoAdapter(RectInfo rectinfo){
        this.rectinfo = rectinfo;
    }

    // 幅を返す。処理自体は委譲する。
    public int getYoko(){
        return rectinfo.getWidth();
    }

    // 高さを返す。処理自体は委譲する。
    public int getTakasa(){
        return rectinfo.getHeight();
    }
}

Main

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

        // 既存のクラスを生成する
        RectInfo rInfo = new RectInfo(100, 50);

        // アダプタクラスを生成する
        RectInfoAdapter rAdapter = new RectInfoAdapter(rInfo);

        // 既存のクラスのメソッドを使用する
        System.out.println("rInfo.getWidth()     : " + rAdapter.getWidth());
        System.out.println("rInfo.getHeight()    : " + rAdapter.getHeight());

        // アダプタクラスのメソッドを使用する
        System.out.println("rAdapter.getYoko()   : " + rAdapter.getYoko());
        System.out.println("rAdapter.getTakasa() : " + rAdapter.getTakasa());
    }
}

実行結果

rInfo.getWidth()     : 100
rInfo.getHeight()    : 50
rAdapter.getYoko()   : 100
rAdapter.getTakasa() : 50

クラス図

Adapter

Bridgeパターン

1月 1, 2003 · Posted in GoFデザインパターン · Comment 

機能と実装の分離

機能と実装をひとつのクラスにまとめていると、機能や実装の追加を行っていくうちにクラス間の関係がわかりにくくなってしまう。ここでいう機能・実装とは次の通り。

機能
クラスが持っているメソッドを利用して処理を行うこと。

実装
処理を実現するためのメソッドのこと。

Bridgeパターンでは、機能と実装を委譲を使って分離する。

利点

  • 機能や実装の追加変更の際に既存のクラスに影響を与えない。
  • 機能・実装という単位でクラスが分割されているため、理解しやすい設計になる。

サンプルコード

機能を表すクラス

NormalWriterクラス

// Formatクラスのメソッドを使用して文章を出力する。
public class NormalWriter{
    private Format format;

    public NormalWriter(Format format){
        this.format = format;
    }

    // 文章出力
    public void write(){
        format.header();
        format.body();
        format.footer();
    }
}

実装を表すクラス

Formatクラス(抽象クラス)

abstract class Format {
    abstract public void header();

    // 現在時刻を表示する
    public void body(){
        System.out.println(new java.util.Date().toString());
    }

    abstract public void footer();
}

HtmlFormatクラス

// HTML文章として出力
public class HtmlFormat extends Format{
    public void header(){
        System.out.println("<head>");
        System.out.println("<title>html</title>");
        System.out.println("</head>");
        System.out.println("<body>");
    }

    public void body(){
        System.out.println("<p>");
        super.body();
        System.out.println("</p>");
    }

    public void footer(){
        System.out.println("</body>");
        System.out.println("</html>");
    }
}

XmlFormatクラス

// XML文章として出力
public class XmlFormat extends Format{
    public void header(){
        System.out.println("<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>");
        System.out.println("<contents>");
    }

    public void footer(){
        System.out.println("</contents>");
    }

    public void body(){
        System.out.println("<date>");
        super.body();
        System.out.println("</date>");
    }
}

Mainクラス(クライアント)

public class Main {
    public static void main(String[] args){
        Format format = null;
        NormalWriter writer = null;

        System.out.println("■use HtmlFormat");
        format = new HtmlFormat();
        writer = new NormalWriter(format);
        writer.write();

        System.out.println("■use XmlFormat");
        format = new XmlFormat();
        writer = new NormalWriter(format);
        writer.write();
    }
}

※実際はクライアントのオブジェクト生成をAbstractFactoryパターンで実現するのが望ましい。

出力結果

■use HtmlFormat
<head>
<title>html</title>
</head>
<body>
<p>
Wed Jun 02 11:29:22 GMT+09:00 2004
</p>
</body>
</html>
■use XmlFormat
<?xml version="1.0" encoding="Shift_JIS"?>
<contents>
<date>
Wed Jun 02 11:29:22 GMT+09:00 2004
</date>
</contents>

テキストフォーマットで出力するクラスを追加作成する場合や、各フォーマットのヘッダのみを出力するような機能を作成する場合、他のクラスへの影響はまったくないため、既存のコードを修正する必要がない。機能・実装どちらの追加変更に対しても、強い設計となっている。

クラス図

Bridge

次ページへ »