さて、Compositeパターンの2回目だが、実例の紹介に移ろう。取り上げるのは、もちろんこのパターンの典型的な応用例になる、ビューにあたるクラスだ。

階層的なビューを実現するNSView

アプリケーションの構成要素をモデル、ビュー、コントローラの3つに分割する、MVCアーキテクチャ。このデザインは、多くのGUIフレームワークで採用されている。この3つの中で、ビューの実装がCompositeパターンの具体的な応用例であることは、議論を待たないだろう。

ビューは、ウィンドウやボタンといった、ユーザの目に見える部分を担当する。複雑なビューを作り上げるには、階層構造が不可欠だ。そこで、多くのフレームワークでは、panelやcontainerといった名称で、他のビューを内部に配置することのできるビューが登場する。ビューが、ビュー自身を含むこの構造は、Compositeパターンそのものだ。

Coocaでビュー機能を提供するのは、NSViewだ。画面描画のためにウィンドウ上に配置されるものは、すべてこのNSViewを継承することになる。NSViewは、他のビューをサブビューとして保持することができる。これにより、Compositeパターンの階層構造ができる。親のビューは、スーパービューと呼ぶ。

NSViewのメソッドのうち、ビューの階層関係に関わるものを紹介しよう。これらが、Compositeパターンを形作ることになる。

List 1.

// スーパービューを取得する
- (NSView*)superview
// すべてのサブビューを取得する
- (NSArray*)subviews
// サブビューを追加する
- (void)addSubview:(NSView*)view
// 他のビューとの関係を指定して、サブビューを追加する
- (void)addSubview:(NSView*)view positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView
// スーパービューから自分自身を取り除く
- (void)removeFromSuperview

親や子のビューを取り出すのは、superviewとsubviews。あるビューにサブビューを追加するには、addSubview:を使う。これには、引数の増えたaddSubview:positioned:relativeTo:というメソッドもある。こちらでは、すでに追加されているサブビューの上に置くか、下に置くか、という位置関係を指定することができる。

サブビューを取り除くには、removeFromSuperviewというメソッドを使う。これは、サブビューの方を呼び出して、スーパービューから自分自身を取り除かせることになる。addSubview:との対で、removeSubview:というメソッドを期待するが、そうではないところに気を付けよう。

CompositeパターンとしてのNSView

NSViewをCompositeパターンとして図示してみよう。

前回紹介したように、Compositeパターンの登場人物は、共通要素としてのComponent、末端クラスとしてのLeaf、子Componentを持つためのComposite、がある。

さて、NSViewの場合、これらがどうあてはまるのか。NSViewは、ビュー階層の最も基本的なクラスだ。だから、Componentになる。さらに、NSView自身が、サブビューを管理するための配列を持っている。つまり、Compositeでもある。ということは、NSViewではComponentとCompositeは同一で、Leafは存在しないことになる。

もう一つ、注意しておきたいことがある。Compositeパターンの特徴は、階層構造を持つことと、共通のオペレーションがComponentで定義されていることだ。もちろん、NSViewでもいくつかのオペレーションが定義されている。だが、Cocoaにおいては、それよりもNSViewの継承元となるNSResponderで定義されるメソッドが重要となる。このことを、心に留めておいてほしい。

これらの関係を図に表すと、次のようになるだろう。

Compositeパターンとしては、NSViewだけで事足りる。NSViewが、サブビューを管理する配列を保持して、自分自身を加えることができるからだ。それだけではなく、NSResponderが存在しているところにも注目してほしい。

ComponentかCompositeか?

CompositeパターンとしてのNSViewを評価してみよう。

まず、ComponentとCompositeが同一である実装は、どのような問題があるのだろうか。

NSViewの実際の動作を見てみると、すべてのビューがサブビューを持つわけではない。たとえば、ボタンを表すNSButtonは、サブビューを持つ必要はない。だが、NSViewの実装では、NSViewがサブビュー管理のための配列を持っている。したがって、NSButtonではこの配列は無駄になる。これが問題点か。

また、オペレーションの伝播にも特徴がある。通常のCompositeパターンでは、共通オペレーションはComponentで定義される。だが、NSViewの場合は、NSResponderで定義されることになる。これは、Cocoaにおけるオペレーションの伝達は、NSViewを基本とするCompositeパターンではなく、NSResponderを基本とするChain of Responsibilityパターンで行われるからだ。

この、NSViewとNSResponderが絡み合うオペレーションの伝播は、Chain of Responsibilityパターンの項で、詳しく説明したいと思う。

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

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