Flyweightパターンとは

GoFのデザインパターンは、生成に関するもの、構造に関するもの、振る舞いに関するもの3つに分類されている。この連載では、順に紹介しているわけだが、構造に関するものの解説もいよいよ佳境に入ってきた。今回は、Fylweightパターンである。

Flyweightパターンを一言で言えば、同じオブジェクトを作るときは1つだけ作ってそれを使い回そう、ということである。例として挙げられているのは、グリフ情報を管理するオブジェクトだ。グリフ情報とは、文字の形を表すもののことだ。つまり、'a'という文字のグリフ、'b'という文字のグリフ、といったように、文字ごとに存在する。だが逆に言えば、同じ文字であればグリフ情報も同じということになる。

さて、コンピュータの画面でテキストを表示しようと思ったら、グリフ情報が必要になる。このグリフ情報をグリフオブジェクトで管理することにしよう。だが、テキストの文字1つずつに1つのグリフオブジェクトを割り当てるのでは効率が悪い。なぜなら、同じ文字ならば同じグリフオブジェクトが使えるからだ。そこで、文字ごとに1つのグリフオブジェクトを作成して、それを使い回すことにする。これならば、アルファベットならば、52個(26文字の大文字、小文字)のグリフオブジェクトがあれば、すべてのテキストをサポートできることになる。

これを実現するのが、Flyweightパターンだ。グリフの例からも分かるように、このパターンはメモリの消費量を抑えてパフォーマンスを上げることが目的になる。実装のときは、このことを第一に考えるようにしたい。

Flyweightパターンの登場人物

Flyweightパターンでは、最低2つのクラスが登場する。

1つは、Flyweightクラス。これが共有されるオブジェクトになる。

もう1つは、FlyweightFactoryクラスだ。名前にFactoryが付いていることから分かるように、Flyweightオブジェクトを作成するFactoryクラスだ。このクラスが、共有状態も管理することになる。

図に書けば、こうなるだろう。

Clientは、FlyweightFactoryを使ってFlyweightオブジェクトを取得する。取得した後は、自由にそのオブジェクトを使えばよい。

FlyweightFactoryには、Abstract Factoryパターンや、Factory Methodパターンを使うこともあるだろう。

Flyweightパターンの実装

では、いつも通りに、Objective-Cでこのパターンを実装してみよう。ここでは、先ほど紹介したグリフ情報のように、アルファベット1文字ごとに、1つのオブジェクトを作るとしよう。

まずは、Flyweightクラスだ。初期化するときは、引数としてchar型の文字を渡すとする。

List 1.

@interface Flyweight : NSObject
{
    char    _c;
}
- (id)initWithChar:(char)c;

@end

@implementation Flyweight

- (id)initWithChar:(char)c
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    _c = c;
    
    return self;
}

@end

これは、ほぼObjective-Cの基本通りのクラスで、特に凝ったことはしてない。

続いて、FlyweightFactoryクラスに移ろう。このクラスには、インスタンスを1つだけ作るSingletonパターンを適用しよう。

そして、共有するFlyweightオブジェクトを管理するための仕組みが必要だ。そのために、インスタンス変数で作成したオブジェクトを管理できるようにする。また、Flyweightオブジェクトを取得するためのメソッドも用意しよう。

List 2.

@interface FlyweightFactory : NSObject
{
    Flyweight*  _flyweights[128];
}
+ (id)sharedInstance;
- (id)flyweightWithChar:(char)c;
@end

_flyweightsという、Flywegihtの配列をインスタンス変数として用意した。ここに、作成したFlyweightオブジェクトを挿入して管理する。Cocoaの配列クラスであるNSMutableArrayを使ってもよいのだが、パフォーマンスを重視したいので配列を使ってみた。

メソッドは、2つ。sharedInstanceは、FlyweightFactoryのインスタンスを取得するためのもの。flyweightWithChar:が、Flyweightオブジェクトの作成と取得を行う、Flyweightパターンの鍵となるメソッドだ。

これらの実装を見てみよう。

List 3.

@implementation FlyweightFactory

+ (id)sharedInstance
{
    static FlyweightFactory*    _instance = nil;
    if (!_instance) {
        _instance = [[FlyweightFactory alloc] init];
    }
    
    return _instance;
}

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    memset(_flyweights, 0, sizeof(_flyweights));
    
    return self;
}

- (id)flyweightWithChar:(char)c
{
    Flyweight*  flyweight;
    flyweight = _flyweights[c];
    if (!flyweight) {
        flyweight = [[Flyweight alloc] initWithChar:c];
        _flyweights[c] = flyweight;
    }
    
    return flyweight;
}

@end

sharedInstanceは、Singletonパターンでよく使われる形になっている。これは、この連載の第50回で説明した通りだ。

初期化メソッドであるinitでは、インスタンス変数_flyweightsの初期化を行っている。すべてNULLにしておく。

最も重要なのが、flyweightWithChar:メソッドだ。このメソッドは、引数として渡されたchar文字に対応するFlyweightオブジェクトを返すことになる。まず、インスタンス変数_flyweightsの中を探して、すでに対応するオブジェクトがあるかどうかチェックする。あった場合は、それをそのまま返せばいい。なかった場合は、Flyweightオブジェクトを作成する。作成したオブジェクトは、_flyweightsの中に入れておく。これで、次に呼ばれたときはこのオブジェクトを使い回すはずだ。

実際にこのクラスを使ってみよう。次のようになる。

List 4.

Flyweight*  a0;
a0 = [[FlyweightFactory sharedInstance] flyweightWithChar:'a'];

Flyweight*  a1;
a1 = [[FlyweightFactory sharedInstance] flyweightWithChar:'a'];

NSLog(@"a0 is 0x%x", a0);
NSLog(@"a1 is 0x%x", a1);

文字'a'を引数として、a0とa1という2つのFlyweightオブジェクトを作ってみた。これらは、同じインスタンスになるはずである。このように、無駄にオブジェクトを作成する事がなくなるのが、Flyweightパターンだ。

次回は、Cocoaでの実例を見てみよう。

提供:毎日キャリアバン ク

毎日キャリアバンクではITエンジニア出身のキャリアコンサルタントで形成する IT専門のチームを編成し、キャリアに応じた専任コンサルタントがご相談を承り ます。キャリアチェンジから市場価値の可能性、ご収入などの相談から面接のア ドバイスまでお気軽にご相談ください。求人情報誌や転職情報サイトなどで一般 に公開されていないような「急募求人案件」も随時ご紹介が可能です。まずはご 登録ください!