Sample Application for GOF's Design Pattern


FLYWEIGHT で自作イメージフォントを表示しようぜ!

■この例題で登場するデザインパターン■
FLYWEIGHT

 『FLYWEIGHT で自作イメージフォントを表示しようぜ!』 (な、ながい・・・)では、コンソールから入力された文字を自作のイメージフォントを使って表示するプログラムを 実現してみました。実行例を以下に示します。(C++版では、このプログラムの実現のために、自作のWin32ヘルパークラスを使っています。Win32ヘルパークラスについては、『AMIGO FOR Win32』 を参照してください。)

$ java FlyweightSample

開始直後に以下のプロンプトと自作イメージフォントによる文字を表示した画面が出る。文字列が非共有オブジェクト(UnsharedFlyweight)で、一つ一つの文字フォントが、共有オブジェクト(SharedFlyweight)で構成されている。

input >

input > Yoshinori Oota[enter]

と入力すると、入力した文字列が反映される。この中で”S”は、新たに生成される共有オブジェクトになるため、コンソールに『S is created.』 と表示され、次のように画面が更新されます。

input> end
   
// ここでマウス操作でダイアログを消す

ここで、プログラム終了

 FLYWEIGHT は、このようなイメージフォントなどシステム内の固定的なリソースを使いまわすような場合に威力を発揮します。

 何も考えずに、画像表示のたびにビットマップデータを読み込み生成していたのでは、システムのメモリはいくらあっても足りないですし、レスポンスも満足できるものにはなりませんよね。固定的なデータやリソースをシステム内部で何度も呼び出す処理に、FLYWEIGHT を適用すれば、メモリの節約にもなるし、記憶メディアへのアクセス回数が減りますのでシステム・パフォーマンスの向上も期待できます。

 ただし、だからといって、闇雲にすべてを FLYWEIGHT にすればよいというものでありません。特に、共有オブジェクトは、状態を持つようなものではあってはいけません。ほとんどスタティックに近い変化しないものであることが必要です。状態を持つようなリソースを共有オブジェクトに設定すると、共有オブジェクトを参照している全ての非共有オブジェクトが影響受けます。プログラムの振舞いはまったく予期できなくなり、不幸のどん底に陥ること間違いなしです。

 また、FLYWEIGHT 導入には、FLYWEIGHT 導入が本当にコスト的に見合うかをよく検討しましょう。例えば、『FLYWEIGHTの骸骨』 では、char データを共有オブジェクトにしていますが、常識的に考えれば普通はこんなことしません。『FLYWEIGHTの骸骨』 は、FLYWEIGHTのデータ構造を簡単に表現するためにあのようにしているだけですので念のため) 言うまでもないことですが、 UnshradFlyweight は、SharedFlyweight への参照を保持するためのメモリは確実に消費しますので、リソース保持に対するメモリ消費がゼロというわけではありません。『FLYWEIGHTの骸骨』のように、直接生成したほうがコストが低い場合や、共有オブジェクトが、実はほとんど共有されない可能性がある場合は、本当に FLYWEIGHT が必要かどうか、頭を冷やして、よく考え直しましょう。



■ 『FLYWEIGHTで自作イメージフォントを表示しようぜ!』の構造

 


■ 『FLYWEIGHTで自作イメージフォントを表示しようぜ!』のJavaプログラムコード

□ Java2版
FlyweightSample.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.io.InputStreamReader;
import java.io.BufferedReader;

public class FlyweightSample { 
    static public void main(String args[]) {
        FlyweightFactory factory = new FlyweightFactory();
        FlyweightCanvas canvas = new FlyweightCanvas();

        // title("FLYWEIGHT PATTERN")をキャンバス上の1行3列目に設定
        Flyweight title = factory.getFlyweight("FLYWEIGHT PATTERN");
        title.setPoint(1,3);
        canvas.add(title);

        // foot("COPYRIGHT YOHTA")をキャンバス上の4行7列目に設定
        Flyweight foot = factory.getFlyweight("COPYRIGHT YOHTA");
        foot.setPoint(4,7); 
        canvas.add(foot);

        canvas.update(); // title,footをキャンバス上に描画

        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                System.out.print("Input > ");
                String input = reader.readLine();
                if (input.equals("end")) {
                    canvas.end();
                    break;
                }
                canvas.remove(title);
                title = factory.getFlyweight(input);
                title.setPoint(1, 3);
                canvas.add(title);
                canvas.update();
            }
        } catch (java.io.IOException e) {
            e.printStackTrace();
            System.err.println(e);
            return;
        }
    }
}
FlyweightFactory.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.util.HashMap;

public class FlyweightFactory {
    private HashMap  _flyweightPool = new HashMap();

    public Flyweight getFlyweight(String text) {
        UnsharedFlyweight extrinsic = new UnsharedFlyweight();

        int length = text.length();
        for (int i = 0; i < length; ++i) {
            String key = String.valueOf(text.charAt(i));

            // すでに生成された共有オブジェクトか検索
            Flyweight intrinsic = (Flyweight)_flyweightPool.get(key);  
            if (intrinsic == null) {
                // プールに共有オブジェクトがなかったら生成しプールに格納する
                intrinsic = new SharedFlyweight(key);
                _flyweightPool.put(key, intrinsic);
            }
            extrinsic.add(intrinsic); 
        }
        return extrinsic;
    }
}
Flyweight.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.

abstract public class Flyweight {
    abstract void setCanvas(FlyweightCanvas canvas);
    abstract public void setPoint(int row, int line); // 描画する行・列を指定※
    abstract public void clear();
    abstract public void draw();
}

/* (※) 自作フォントは20x20pixのため、行/20pix、列/20pixの換算になる */
UnsharedFlyweight.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.util.LinkedList;
import java.util.ListIterator;

public class UnsharedFlyweight extends Flyweight { 
    private LinkedList _extrinsic = new LinkedList();
    private int _row = 0;
    private int _line = 0;

    public void add(Flyweight intrinsic) { 
        _extrinsic.add(intrinsic);
    }

    void setCanvas(FlyweightCanvas canvas) {
        ListIterator it = _extrinsic.listIterator();
        while (it.hasNext()) {
            Flyweight intrinsic = (Flyweight)it.next();
            intrinsic.setCanvas(canvas);
        }
    }

    public void setPoint(int row, int line) {
        _row  = row;
        _line = line;
    }

    public void clear() {
        int x = _row;
        ListIterator it = _extrinsic.listIterator();
        while (it.hasNext()) {
            Flyweight intrinsic = (Flyweight)it.next();
            intrinsic.setPoint(x, _line); 
            intrinsic.clear();
            ++x; // 1文字(1列)づつ位置を進める
        }
        _extrinsic.clear(); // 再度描画されないよう文字列を消去
    }

    public void draw() { 
        int x = _row;
        ListIterator it = _extrinsic.listIterator();
        while (it.hasNext()) {
            Flyweight intrinsic = (Flyweight)it.next();
            intrinsic.setPoint(x, _line);
            intrinsic.draw();
            ++x;
        }
    }
} 
SharedFlyweight.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.Graphics;
import java.awt.Canvas;
import java.awt.Image;

public class SharedFlyweight extends Flyweight { 
    private Canvas _canvas = null;
    private Image _fontImg = null;
    private int _x = 0;
    private int _y = 0;

    public SharedFlyweight(String key, Canvas canvas) { 
        String imagePath = null;
        if (key.equals(" ")) {
            // スペース用フォント
            imagePath = "Font/SP.jpg";   
        } else {
            // アルファベットフォントのパス(例:"Font/A.jpg")
            imagePath = "Font/"+key+".jpg"; 
        }
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        _fontImg = toolkit.getImage(imagePath);
        System.out.println(key + " is created.");
    }

    void setCanvas(FlyweightCanvas canvas) {
        _canvas = canvas;
        if (_canvas == null) return;
        MediaTracker tracker = new MediaTracker(_canvas);
        tracker.addImage(_fontImg, 0);
        try {
            tracker.waitForID(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.err.println(e);
        }
    }

    public void setPoint(int row, int line) {
        _x = row*20;   // 列指定をピクセル単位に変換(列/20pix)
        _y = line*20;  // 行指定をピクセル単位に変換(行/20pix)
    }

    public void clear() {
        if (_canvas == null) return;
        Graphics g = _canvas.getGraphics();
        g.clearRect(_x, _y, 20, 20); // イメージフォントの幅/高さ分消去
        g.dispose();
    }

    public void draw() {
        if (_canvas == null) return;
        Graphics g = _canvas.getGraphics();
        g.drawImage(_fontImg, _x, _y, null);
        g.dispose();
    } 
}
FlyweightCanavas.java
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
import java.awt.Frame;
import java.awt.Canvas;
import java.awt.Graphics;
import java.util.LinkedList;
import java.util.ListIterator;

public class FlyweightCanvas extends Canvas {
    private Frame _frame = new Frame("FlyweightSample");

    // 描画対象となる文字列(UnsharedFlyweight)を格納
    private LinkedList _literals = new LinkedList();

    public FlyweightCanvas() { 
        _frame.setSize(400, 200);
        _frame.add(this);
        _frame.show();
    }

    public void paint(Graphics g) {
        ListIterator it = _literals.listIterator();
        while (it.hasNext()) {
            Flyweight flyweight = (Flyweight)it.next();
            flyweight.draw(); // 文字列(UnsharedFlyweight)を描画
        }
    }

    public void add(Flyweight flyweight) {
        flyweight.setCanvas(this);
       _literals.add(flyweight);
    }

    public void remove(Flyweight flyweight) {
        flyweight.clear();
        _literals.remove(flyweight);
        flyweight.setCanvas(null);
    }

    public void draw() {
        paint(getGraphics());
    }

    public void end() {
        _frame.dispose();
    }
}


■ 『FLYWEIGHTで自作イメージフォントを表示しようぜ!』のC++(Win32)プログラムコード

 このコードをコンパイルするには、『AMIGO FOR Win32』ThreadWrapperWinFrameWinCanvas が必要です。

□ C++版
FlyweightSample.cpp
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include "FlyweightFactory.h"
#include "FlyweightCanvas.h"
#include "Flyweight.h"

int main(int argc, char* argv[]) {
    using namespace std;

    FlyweightFactory* factory = new FlyweightFactory();
    FlyweightCanvas* canvas = new FlyweightCanvas();

    // title("FLYWEIGHT PATTERN")をキャンバス上の1行3列目に設定
    Flyweight* title = factory->getFlyweight("FLYWEIGHT PATTERN");
    title->setPoint(1,3); 
    canvas->add(title);

    // foot("COPYRIGHT YOHTA")をキャンバス上の4行7列目に設定
    Flyweight* foot = factory->getFlyweight("COPYRIGHT YOHTA");
    foot->setPoint(4,7);
    canvas->add(foot);

    canvas->update(); // title,footをキャンバス上に描画

    string input("");
    char line[128];
    while (true) {
        cout << "input > ";
        cin.getline(line,128];
        string input(line);
        if (input == "end") { // endが入力されたらプログラム終了
            break;
        }
        canvas->remove(title);
        delete title;
        title = factory->getFlyweight(input);
        title->setPoint(1,3);
        canvas->add(title);
        canvas->update();
    }

    delete title;
    delete foot;
    delete canvas;
    delete factory; // Factoryは、必ず最後に削除!
}
FlyweightFactory
// Copyright(C) 2003 Yoshinori Oota All rights reserved.
#ifndef FLYWEIGHTFACTORY_HEADER_GUARD__
#define FLYWEIGHTFACTORY_HEADER_GUARD__

#include <map>
#include <string>

class Flyweight;
class UnsharedFlyweight;

class FlyweightFactory {
public:
    FlyweightFactory();
    ~FlyweightFactory();
    Flyweight* getFlyweight(const std::string& key);
private:
    std::map<char, Flyweight*> flyweightPool_;
};

#endif  // FLYWEIGHTFACTORY_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <cctype>
#include "FlyweightFactory.h"
#include "SharedFlyweight.h"
#include "UnsharedFlyweight.h"

FlyweightFactory::FlyweightFactory() { }
FlyweightFactory::~FlyweightFactory() { 
    std::map<char, Flyweight*>::iterator it = flyweightPool_.begin();
    while (it != flyweightPool_.end()) {
        delete (*it).second;
        ++it;
    }
}

Flyweight* FlyweightFactory::getFlyweight(const std::string& key) {

    UnsharedFlyweight* extrinsic = new UnsharedFlyweight(); 

    int length = key.length();
    for (int i = 0; i < length; ++i) {
        char val = key[i];
        val = (char)std::toupper(val);
        Flyweight* intrinsic = NULL;

        // すでに生成された共有オブジェクトか検索
        std::map<char, Flyweight*>::iterator it = flyweightPool_.find(val);
        if (it == flyweights_.end()) {
            // プールに共有オブジェクトがなかったら生成しプールに格納する
            intrinsic = new SharedFlyweight(val);
            flyweightPool_[val] = intrinsic;
        } else {
            intrinsic = (*it).second;
        }
        extrinsic->add(intrinsic);
    }
    return extrinsic;
}
Flyweight
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef FLYWEIGHT_HEADER_GUARD__
#define FLYWEIGHT_HEADER_GUARD__

#include <windows.h>

class FlyweightCanvas;

class Flyweight {
public:
    Flyweight();
    virtual ~Flyweight();
    virtual void setCanvas(FlyweightCanvas* canvas);
    virtual void setPoint(int row, int line) = 0;
    virtual void paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps);
    virtual void clear();
};

#endif  // FLYWEIGHT_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "Flyweight.h"

Flyweight::Flyweight() { }
Flyweight::~Flyweight() { }

void Flyweight::setCanvas(FlyweightCanvas* canvas) {
    /* default do nothing. */
}

void Flyweight::paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps) {
    /* default do nothing. */
}

void Flyweight::clear() {
    /* default do nothing. */
}
UnsharedFlyweight
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef UNSHAREDFLYWEIGHT_HEADER_GUARD__
#define UNSHAREDFLYWEIGHT_HEADER_GUARD__

#include <list>
#include "Flyweight.h"

class SharedFlyweight;

class UnsharedFlyweight : public Flyweight {
public:
    UnsharedFlyweight();
    virtual ~UnsharedFlyweight();
    void add(Flyweight* intrinsic);
    virtual void setCanvas(FlyweightCanvas* canvas);
    virtual void setPoint(int x, int y);
    virtual void paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps);
    
private:
    FlyweightCanvas* canvas_;
    int row_;
    int line_;
    std::list<Flyweight*> extrinsic_;
};

#endif  // UNSHAREDFLYWEIGHT_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "UnsharedFlyweight.h"
#include "SharedFlyweight.h"
#include "FlyweightCanvas.h"

UnsharedFlyweight::UnsharedFlyweight() : canvas_(NULL), row_(0), line_(0) { }
UnsharedFlyweight::~UnsharedFlyweight() {
    if (canvas_ != NULL) {
        canvas_->remove(this);
    }
}

void UnsharedFlyweight::add(Flyweight* intrinsic) {
    extrinsic_.push_back(intrinsic);
}

void UnsharedFlyweight::setPoint(int row, int line) {
    row_ = row;
    line_ = line;
}

void UnsharedFlyweight::paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps) {
    int x = row_;
    std::list<Flyweight*>::iterator it = extrinsic_.begin();
    while (it != extrinsic_.end()) {
        (*it)->setPoint(x, line_); 
        (*it)->paint(hWnd, hdc, ps);
        ++it;
        ++x; // 1文字(1列)づつ描画位置を進める
    }
}
SharedFlyweight
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef SHAREDFLYWEIGHT_HEADER_GUARD__
#define SHAREDFLYWEIGHT_HEADER_GUARD__

#include "Flyweight.h"

class SharedFlyweight : public Flyweight {
public:
    SharedFlyweight(char key);
    virtual ~SharedFlyweight();
    virtual void setPoint(int x, int y);
    virtual void paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps);
private:
    int x_;
    int y_;
    HBITMAP hImage_; // 共有イメージフォント
};

#endif  // SHAREDFLYWEIGHT_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include "SharedFlyweight.h"

SharedFlyweight::SharedFlyweight(char key) : x_(0), y_(0), hImage_(NULL) {
    std::string fontPath("");
    if (key == ' ') {
        // スペース用フォントのパス
        fontPath += "Font\\SP.bmp"; 
    } else {
        // アルファベット用フォントのパス (例:"Font/A.jpg")
        fontPath += "Font\\";
        fontPath += key;
        fontPath += ".bmp"; 
    }

    hImage_ = (HBITMAP)LoadImage(NULL, fontPath.c_str(), IMAGE_BITMAP, 
                          0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE); 
    if (hImage_ != NULL) {
        std::cout << key << " is created." << std::endl;
    } else {
        std::cout << key << " is failed." << std::endl;
    }
}

SharedFlyweight::~SharedFlyweight() {
    DeleteObject(hImage_);
}

void SharedFlyweight::setPoint(int row, int line) {
    x_ = row*20; // 列指定をピクセル単位に変換(列/20pix)
    y_ = line*20; // 行指定をピクセル単位に変換(行/20pix)
}

void SharedFlyweight::paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps) {
    if (hImage_ == NULL) return;
    HDC memdc = CreateCompatibleDC(hdc);
    SelectObject(memdc, hImage_);
    BitBlt(hdc, x_, y_, 20, 20, memdc, 0, 0, SRCCOPY);
    DeleteDC(memdc);
}
FlyweightCanvas
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef FLYWEIGHTCANVAS_HEADER_GUARD__
#define FLYWEIGHTCANVAS_HEADER_GUARD__

#include <list>
#include "WinCanvas.h"

class Flyweight;

class FlyweightCanvas : public WinCanvas {
public:
    FlyweightCanvas();
    virtual ~FlyweightCanvas();
    void add(Flyweight* literal);
    void remove(Flyweight* literal);
    virtual void paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps);
    virtual void update();
private:
    WinFrame* frame_;
    std::list<Flyweight*> literals_;
};

#endif  // FLYWEIGHTCANVAS_HEADER_GUARD__
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "WinFrame.h"
#include "FlyweightCanvas.h"
#include "Flyweight.h"

FlyweightCanvas::FlyweightCanvas() : frame_(WinFrame::Instance("FlyweightSample")) { 
    frame_->setSize(400,200);
    frame_->add(this);
    frame_->start();
}

FlyweightCanvas::~FlyweightCanvas() {
    literals_.erase(literals_.begin(), literals_.end());
    frame_->join(); // フレームの終了を待つ
    if (frame_ != NULL) {
        frame_->Destroy();
    }
    frame_ = NULL;
}

void FlyweightCanvas::add(Flyweight* literal) {
    literal->setCanvas(this);
    literals_.push_back(literal);
}

void FlyweightCanvas::remove(Flyweight* literal) {
    literals_.erase(find(literals_.begin(), literals_.end(), literal));
    literal->setCanvas(NULL);
}

void FlyweightCanvas::paint(HWND hWnd, HDC hdc, PAINTSTRUCT& ps) {
    std::list<Flyweight*>::iterator it = literals_.begin();
    while (it != literals_.end()) {
        (*it)->paint(hWnd, hdc, ps);
        ++it;
    }
}

void FlyweightCanvas::update() {
    WinCanvas::update()
}


【C++版補足】
・C++版では、本来はコピーコンストラクタ、代入演算子を定義すべきですが、コードが冗長になるため省いています。

■C++版で、必要なソースコード■

FlyweightSample.cpp, Flyweight.h, Flyweight.cpp, FlyweightFactory.h, FlyweightFactory.cpp, FlyweightCanvas.h, FlyweightCanvas.cpp, SharedFlyweight.h, SharedFlyweight.cpp, UnsharedFlyweight.h, UnsharedFlyweight.cpp,
ThreadWrapper.h, ThreadWrapper.cpp, WinCanvas.h, WinCanvas.cpp, WinFrame.h, WinFrame.cpp

※ ThreadWrapper, WinCanvas, WinFrame は 『AMIGO FOR Win32』 を参照してください。


■コンパイル方法■

$ g++ *.cpp -lgdi32 -o FlyweightSample.exe

『AMIGO FOR Win32』 で、Win32のグラフィックAPIを使っているので、GDIライブラリをリンクしてください


■その他■
イメージフォントは、こちらに圧縮形式でおいておきます。BMPファイルですので、Javaで用いる場合は、JPGへ変換して使ってください。イメージフォントは、実行ディレクトリの .\Font 以下にファイル名を変えずに展開してください。

ご意見、ご感想はこちらまで。

Copyright(C) 2000-2003 Yoshinori Oota All rights reserved.

本ホームページのリンクは自由です。複製、転載される場合は、必ず著者までご連絡をください。