Decoratorパターンとは

あるクラスがあって、その機能を少しだけ拡張したいとしよう。オブジェクト指向では、このようなときにはサブクラスを作って対応する。もちろんこれはとても有効なのだが、サブクラス化が適切ではない場合もある。たとえば、対象となるクラスがすでに複雑なクラス階層の中にあって、無闇にサブクラスを作りたくないとき。また、拡張した機能を、動的に付けたり外したりしたいとき。こういったときには、サブクラス化は適切な解ではない。

このような状況に対応するのがDecoratorパターンだ。Decoratorとは、もとのクラスを「装飾」する枠のことだ。もとのクラスをDecoratorの中に入れて被せてやる。こうした方法で少しだけ機能を拡張するのだ。入れるだけなので、取り外しも簡単にできる。

このとき、Decoratorは、もとのComponentと同じインタフェースでクライアント側からアクセスできるようにしておこう。そうすれば、呼び出し元を変更することなく、皮を付け替えるだけで動作を変更することができる。

GoF本に掲載されている例は、GUIに関するものだ。まず、テキストビューがあったとする。これを拡張して、スクロールバー付きのテキストビューや、ボーダー付きのテキストビューが欲しいとしよう。このとき、いちいちScrollableTextViewや、BorderedTextViewといったサブクラスを作っていたのでは、小さいサブクラスが山のようにできあがってしまう。スクロール機能やボーダー機能を、Dcoratorとして実現することで、そうした問題を解決するわけだ。

Decoratorパターンの登場人物

Decoratorパターンの登場人物を紹介しよう。他のパターンと比べるとシンプルだ。

まず、もとのクラスとしてComponentを定義する。このComponentを拡張するために、Decoratorという名前のクラスを作る。基本的にこのような2つのクラスで、Decoratorパターンは構成される。

また、ComponentとDecoratorで、同一のインタフェースを持たなくてはならないという制約がある。そこで、DecoratorをComponentから継承して作ることにしよう。これにより、インタフェースは同じになる。

図に表すと、次のようになるだろう。

DecoratorクラスとComponentの関係

Decoratorパターンの実装

では、Objective-Cで実装してみよう。

まず、Componentクラスを宣言する。Cocoa環境ではComponentという名前はすでに使われているので、ここではComponentObjectという名前にしてある。

List 1.

@interface ComponentObject : NSObject
{
}
- (void)operation;
@end

operationというメソッドを1つ定義している。これが、ComponentとDecoratorとが持つ共通インタフェースとなる。

次に、Decoratorを定義しよう。このクラスは、ComponentObjectを継承する。そして、拡張する対象となるComponentObjectの参照を持つことになる。そこで、インスタンス変数としてComponentObjectを保持。さらに、setComponent:というメソッドを定義して、Componentを設定できるようにしておこう。

List 2.

@interface Decorator : ComponentObject
{
    ComponentObject*    _component;
}
- (void)setComponent:(ComponentObject*)component;
@end

実装クラスでは、2つのメソッドを実装している。まず、setComponent:メソッド。ここでは、引数として渡されてくるComponentをインスタンス変数に格納する。もう1つは、共通インタフェースであるoperationメソッド。ここでは、まずDecorator独自の処理を行う。そして、保持しているComponentのoperationメソッドを呼び出してやるのだ。これにより、operation処理を伝播させることになる。

List 3.

@implementation Decorator
- (void)setComponent:(ComponentObject*)component
{
    _component = component;
}

- (void)operation
{
    // Decorator独自のoperation
    ...

    // _componentのoperationを呼び出す
    [_component operation];
}
@end

このパターンのクライアントから見ると、ComponentがDecoratorによって装飾されても、インタフェースは変わらない。装飾されていようがいまいが、同じように扱うことができるわけだ。

非形式プロトコルを使った実装

もう1つの実装を紹介しよう。Objective-Cらしく、非形式プロトコルを使うものだ。

DecoratorとComponentの関係は、同一のインタフェースさえ提供すればいいので、クラスを継承する必要は特にない。そこで、共通インタフェースを非形式プロトコルを使って宣言してやろう。

List 4.

@interface NSObject (ComponentInterface)
- (void)operation;
@end

こうすれば、DecoratorはComponentを継承する必要がなくなる。単に、ComponentInterfaceで定義されているoperationさえ実装しておけばいいのだ。

非形式プロトコルを使った場合のDecoratorを見てみよう。

List 5.

@interface Decorator : NSObject
{
    id  _component;
}
- (void)setComponent:(id)component;
@end

@implementation Decorator
- (void)setComponent:(id)component
{
    _component = component;
}

- (void)operation
{
    // Decorator独自のoperation
    ...

    // _componentのoperationを呼び出す
    if ([_component respondsToSelector:@selector(operation)]) {
        [_component operation];
    }
}
@end

まず、DecoratorはNSObjectを継承している。Componentを継承する必要はなくなった。インスタンス変数である_componentは、id型とした。これも、ComponentObjectというクラスはなくなって、どんなクラスでもComponentになれるようになったからだ。

operationメソッドの実装も変わった。Decorator独自のoperation処理をしたあと、Componentのメソッドを呼び出すのだが、respondsToSelector:を使ってoperationメソッドを実装しているかどうか確認している。継承関係の縛りがなくなった分、実行時に動的なチェックを行っているわけだ。

次回は、Decoratorの具体例を紹介しよう。もちろん、スクロールビューから始める。

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

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