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