|
|
Log In | Not a Member? |
Contact ADC |
| < Previous PageNext Page > |
クラスクラスタは、Foundationフレームワークで多用されているデザインパターンです。クラスクラスタは、複数のプライベートの具象サブクラスを、パブリックの抽象スーパークラスの下にグループ化します。この方法でクラスをグループ化すると、外から見たオブジェクト指向フレームワークのアーキテクチャが、その豊富な機能を損なうことなく簡素化されます。クラスクラスタは、“「Cocoaのデザインパターン」”で説明している抽象ファクトリのデザインパターンに基づいています。
単純な概念、複雑なインターフェイス
単純な概念、単純なインターフェイス
インスタンスの作成
複数のパブリックスーパークラスを持つクラスクラスタ
クラスクラスタ内部でのサブクラスの作成
クラスクラスタのアーキテクチャとその利点について説明するために、異なる型(char、int、float、double)の数値を格納するオブジェクトを定義するクラス階層の構築にまつわる問題について考えます。異なる型の数値には数多くの共通する機能があります(たとえば、ある型を別の型に変換したり、文字列として表現したりできます)。そのため、それらを単一のクラスで表現できる可能性があります。しかし、格納要件は異なるため、それらのすべてを同じクラスによって表現することは効率的ではありません。このことから、次のようなアーキテクチャが考えられます。
Numberは抽象スーパークラスであり、そのメソッドの中で、各サブクラスに共通する操作を宣言します。しかし、数値を格納するためのインスタンス変数は宣言しません。それらのインスタンス変数はサブクラスで宣言し、Numberで宣言したプログラムインターフェイスの中で共有します。
ここまでのデザインは比較的単純です。しかし、C言語の基本的な型で一般に行われる変換を考慮すれば、次のような図になります。
“数値を保持するためのクラスを作成する”という単純な考え方は、多数のクラスを含むように簡単に拡大できます。この考え方の単純さを反映したデザインを提示するのが、クラスクラスタアーキテクチャです。
クラスクラスタのデザインパターンをこの問題に応用することで、次のような階層が得られます(プライベートクラスを灰色で示しています)。
この階層を利用するユーザに見えるのは、Numberというパブリッククラス1つだけです。それでは、適切なサブクラスのインスタンスを割り当てることがどうして可能になるのでしょうか。その答えは、抽象スーパークラスでのインスタンス化の処理方法にあります。
クラスクラスタの抽象スーパークラスでは、自身のプライベートサブクラスのインスタンスを作成するためのメソッドを宣言する必要があります。呼び出す作成メソッドに応じて適切なサブクラスのオブジェクトを提供する処理は、スーパークラスの責任で行われます。インスタンスのクラスを選ぶことはなく、実際、選ぶこともできません。
Foundationフレームワークでは、通常は+ className...メソッドを呼び出すか、alloc...メソッドとinit...メソッドを呼び出して、オブジェクトを作成します。たとえば、FoundationフレームワークのNSNumberクラスでは、次のメッセージを送信して数値オブジェクトを作成できます。
NSNumber *aChar = [NSNumber numberWithChar:’a’]; |
NSNumber *anInt = [NSNumber numberWithInt:1]; |
NSNumber *aFloat = [NSNumber numberWithFloat:1.0]; |
NSNumber *aDouble = [NSNumber numberWithDouble:1.0]; |
(このインスタンス化の方法で作成したオブジェクトは、割り当て解除が自動的に行われます。詳細については、“「クラスファクトリメソッド」”を参照してください。多くのクラスは標準のalloc...およびinit...メソッドも備えています。これらを使用して作成したオブジェクトは、自分で割り当て解除を管理する必要があります。)
返されるオブジェクト(aChar、anInt、aFloat、aDoubleなど)は、それぞれ異なるプライベートサブクラスに属する可能性があります(実際、そうです)。各オブジェクトの所属クラスは隠蔽されますが、そのインターフェイスは抽象スーパークラス(NSNumber)によって宣言されているので、パブリックです。厳密には正しくはありませんが、aChar、anInt、aFloat、aDoubleなどのオブジェクトを、NSNumberクラスのインスタンスと見なすと扱いやすくなります。これらはNSNumberのクラスメソッドによって作成され、NSNumberによって宣言されているインスタンスメソッドを通じてアクセスされるからです。
前出の例では、1つの抽象パブリッククラスで複数のプライベートサブクラスのインターフェイスを定義しています。これは最も純粋な意味でのクラスクラスタです。クラスタのインターフェイスを宣言する抽象パブリッククラスを2つ(またはそれ以上)持たせることも可能であり、そうしたほうが望ましい場合が多くあります。その証拠として、Foundationフレームワークには次のクラスタがあります。
クラスクラスタ | パブリックスーパークラス |
|---|---|
|
|
| |
|
|
| |
|
|
| |
|
|
|
このほかにもこうしたクラスタは存在しますが、上記のクラスタは2つの抽象ノードが連携してクラスクラスタへのプログラムインターフェイスを宣言していることを明確に示します。これらのクラスタではそれぞれ、すべてのクラスタオブジェクトが応答できるメソッドを一方のパブリックノードで宣言し、内容の変更が許されるクラスタオブジェクトにのみ適切なメソッドを他方のノードで宣言しています。
クラスタのインターフェイスをこのように分類することは、オブジェクト指向フレームワークのプログラムインターフェイスをより明確にするのに役立ちます。たとえば、次のメソッドを宣言するBookオブジェクトを考えます。
- (NSString *)title; |
Bookオブジェクトでは、独自のインスタンス変数を返すか、新しい文字列オブジェクトを作成してそのオブジェクトを返せます。どちらであるかは重要ではありません。この宣言で明らかなことは、返される文字列を変更できないということです。返されたオブジェクトを変更しようとするとコンパイラが警告を発します。
クラスクラスタアーキテクチャでは、単純さを取るか拡張性を取るかというトレードオフが伴います。多数のプライベートクラスを代表する少数のパブリッククラスを用意することで、フレームワーク内のクラスの学習が容易になり、使いやすくなりますが、クラスタ内にサブクラスを作成することは多少難しくなります。しかし、サブクラスを作成する必要がほとんどない場合は、明らかにクラスタアーキテクチャが便利です。Foundationフレームワークではまさにこのような状況でクラスタを使用しています。
プログラムが必要とする機能がクラスタにない場合は、サブクラスの出番かもしれません。たとえば、NSArrayクラスクラスタのように、メモリではなくファイルを格納領域とする配列オブジェクトを作成する必要があったとします。この場合は、クラスの基盤となる格納メカニズムを変更することになるため、サブクラスを作成する必要が生じます。
その一方で、クラスタのオブジェクトをその中に埋め込んだクラスを定義すれば十分(かつ簡単)という場合もあります。たとえば、何らかのデータに変更が加えられるたびにプログラムに警告する必要があるとします。この場合、Foundationフレームワークで定義されているデータオブジェクトに対し単純な覆いを作成することが最善の方法となることがあります。このクラスのオブジェクトは、データに変更を加えるメッセージに干渉し、メッセージを横取りして、何らかの操作をそれに対して行い、その後メッセージを、埋め込まれているデータオブジェクトに転送することができます。
まとめると、オブジェクトの格納を管理する必要がある場合は、真のサブクラスを作成します。それ以外の場合は、複合オブジェクトを作成します。つまり、Foundationフレームワークの標準のオブジェクトを独自に設計したオブジェクトに埋め込んだものです。以降の各セクションでは、この2つの手法について詳しく説明します。
クラスクラスタ内に作成する新しいクラスは、次の条件を満たす必要があります。
クラスタの抽象スーパークラスのサブクラスであること。
独自の格納領域を宣言すること。
スーパークラスのプリミティブメソッドをオーバーライドすること(以下で説明します)。
クラスタの抽象スーパークラスはクラスタ階層においてパブリックに公開される唯一のノードであるため、1番目の条件は明白です。これは、新しいサブクラスではクラスタのインターフェイスが継承されるものの、インスタンス変数についてはスーパークラスでは宣言されないため、継承されないことを意味します。その結果が、2番目の条件となります。つまり、サブクラスでは必要なインスタンス変数をすべて宣言する必要があります。最後に、サブクラスでは継承したメソッドのうち、オブジェクトのインスタンス変数に直接アクセスするメソッドをすべてオーバーライドする必要があります。このようなメソッドのことをプリミティブメソッドと呼びます。
クラスのプリミティブメソッドは、クラスのインターフェイスの基礎を形成します。たとえば、NSArrayクラスを考えてみましょう。このクラスは、オブジェクトの配列を管理するオブジェクトに対するインターフェイスを宣言します。概念上、配列にはいくつかのデータ項目が格納され、各項目にはインデックスに基づいてアクセスできます。NSArrayでは、countとobjectAtIndex:の2つのプリミティブメソッドを通じてこの抽象概念を表現しています。これらのメソッドを基礎にして、派生メソッドと呼ばれる他のメソッドを実装できます。たとえば、次のようにできます。
派生メソッド | 考えられる実装 |
|---|---|
| 配列オブジェクトにというメッセージを送って、最後のオブジェクトを探します。 |
| 配列オブジェクトに繰り返し |
プリミティブメソッドと派生メソッドとでインターフェイスを区別することによって、サブクラスの作成が容易になります。作成するサブクラスでは、継承したプリミティブをオーバーライドする必要がありますが、そのようにすることで、継承するすべての派生メソッドが正しく機能することを保証できるようになります。
プリミティブと派生の区別は、完全に初期化されるオブジェクトのインターフェイスに適用されます。サブクラスでinit...メソッドをどのように扱うかという問題についても、対処する必要があります。
通常、クラスタの抽象スーパークラスではいくつかのinit...および+ classNameメソッドを宣言します。“「インスタンスの作成」”で説明したように、抽象クラスでは、init...または+ classNameのどちらのメソッドを選ぶかに応じて、どの具象サブクラスをインスタンス化するかが決まります。抽象クラスではこれらのメソッドがサブクラスの便宜のために宣言されていると考えることができます。抽象クラスはインスタンス変数がないので、初期化メソッドも必要ありません。
サブクラスでは、独自にinit...メソッドを宣言し(自身のインスタンス変数の初期化が必要な場合)、必要に応じて+ classNameメソッドを宣言してください。継承したものをそのまま利用することは避けます。初期化チェーンにおけるリンクを維持するために、スーパークラスの指定イニシャライザをサブクラス自身の指定イニシャライザメソッドの中で呼び出します(指定イニシャライザの詳細については、「Objective-Cプログラミング言語」の「指定イニシャライザ」を参照してください)。クラスクラスタの内部では、抽象スーパークラスの指定イニシャライザは必ずinitです。
これまでの説明が理解しやすいように、例を示します。NSArrayのサブクラスとしてMonthArrayというサブクラスを作成する必要があるとします。このサブクラスは、指定されたインデックス位置に対応する月名を返します。ただし、MonthArrayオブジェクトは、月名の配列をインスタンス変数としては実際に格納することはありません。代わりに、指定されたインデックス位置(objectAtIndex:)に対応する名前を返すメソッドから定数文字列を返すようにします。したがって、アプリケーションに存在するMonthArrayオブジェクトの数にかかわらず、割り当てられる文字列オブジェクトは12個のみです。
MonthArrayクラスを次のように宣言します。
#import <foundation/foundation.h> |
@interface MonthArray :NSArray |
{ |
} |
+ monthArray; |
- (unsigned)count; |
- (id)objectAtIndex:(unsigned)index; |
@end |
MonthArrayクラスには初期化するインスタンス変数がないので、init...メソッドを宣言していません。countおよびobjectAtIndex:の各メソッドは、前述のように継承したプリミティブメソッドを単純に覆っているだけです。
MonthArrayクラスの実装は次のようになります。
#import "MonthArray.h" |
@implementation MonthArray |
static MonthArray *sharedMonthArray = nil; |
static NSString *months[] = { @"January", @"February", @"March", |
@"April", @"May", @"June", @"July", @"August", @"September", |
@"October", @"November", @"December" }; |
+ monthArray |
{ |
if (!sharedMonthArray) { |
sharedMonthArray = [[MonthArray alloc] init]; |
} |
return sharedMonthArray; |
} |
- (unsigned)count |
{ |
return 12; |
} |
- objectAtIndex:(unsigned)index |
{ |
if (index >= [self count]) |
[NSException raise:NSRangeException format:@"***%s:index |
(%d) beyond bounds (%d)", sel_getName(_cmd), index, |
[self count] - 1]; |
else |
return months[index]; |
} |
@end |
MonthArrayでは継承したプリミティブメソッドをオーバーライドするので、継承した派生メソッドはオーバーライドしなくても正常に機能します。NSArrayのlastObject、containsObject:、sortedArrayUsingSelector:、objectEnumeratorその他のメソッドは、MonthArrayオブジェクトに対して問題なく機能します。
複合オブジェクトを作成するには、プライベートのクラスタオブジェクトを、独自に設計したオブジェクトに埋め込みます。複合オブジェクトでは、クラスタオブジェクトを利用して自身の基本的な機能を提供でき、何らかの特別な方法で処理する必要があるメッセージだけを横取りすることができます。この方法を使用することで、記述しなければならないコードの量が減り、Foundationフレームワークで提供されているテスト済みのコードを活用できます。
複合オブジェクトは次のように捉えることができます。
複合オブジェクトは、自身をクラスタの抽象ノードのサブクラスとして宣言する必要があります。また、サブクラスとして、スーパークラスのプリミティブメソッドをオーバーライドする必要があります。派生メソッドのオーバーライドもできますが、派生メソッドはプリミティブメソッドを通じて機能するため、その必要はありません。
NSArrayのcountメソッドを例に取りましょう。介在するオブジェクトがオーバーライドするメソッドの実装は、次のように単純です。
- (unsigned)count |
{ |
return [embeddedObject count]; |
} |
ただし、オブジェクトは、オーバーライドする任意のメソッドの実装に、それ自身の目的のためのコードを含めることができます。
複合オブジェクトの使い方を説明するために、例を示しましょう。たとえば、可変配列オブジェクトで、配列内容の変更を許可する前に、変更内容を何らかの検証条件に照らしてテストしたいとします。次の例は、標準の可変配列オブジェクトを格納するValidatingArrayというクラスを表します。ValidatingArrayでは、そのスーパークラスであるNSArrayおよびNSMutableArrayに宣言されているすべてのプリミティブメソッドをオーバーライドしています。また、array、validatingArray、initの各メソッドも宣言しています。これらを使用してインスタンスの作成と初期化を実行できます。
#import <foundation/foundation.h> |
@interface ValidatingArray :NSMutableArray |
{ |
NSMutableArray *embeddedArray; |
} |
+ validatingArray; |
- init; |
- (unsigned)count; |
- objectAtIndex:(unsigned)index; |
- (void)addObject:object; |
- (void)replaceObjectAtIndex:(unsigned)index withObject:object; |
- (void)removeLastObject; |
- (void)insertObject:object atIndex:(unsigned)index; |
- (void)removeObjectAtIndex:(unsigned)index; |
@end |
実装ファイルは、ValidatingArrayのinitメソッドの中で、埋め込みオブジェクトを作成してembeddedArray変数に割り当てる方法を示します。配列にアクセスするだけで内容を変更しないメッセージは、そのまま埋め込みオブジェクトに渡されます。内容を変更する可能性のあるメッセージは詳細に調べられ(この例では擬似コードです)、架空の検証テストに合格した場合にのみ渡されます。
#import "ValidatingArray.h" |
@implementation ValidatingArray |
- init |
{ |
self = [super init]; |
if (self) { |
embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init]; |
} |
return self; |
} |
+ validatingArray |
{ |
return [[[self alloc] init] autorelease]; |
} |
- (unsigned)count |
{ |
return [embeddedArray count]; |
} |
- objectAtIndex:(unsigned)index |
{ |
return [embeddedArray objectAtIndex:index]; |
} |
- (void)addObject:object |
{ |
if (/* 変更が有効 */) { |
[embeddedArray addObject:object]; |
} |
} |
- (void)replaceObjectAtIndex:(unsigned)index withObject:object; |
{ |
if (/* 変更が有効 */) { |
[embeddedArray replaceObjectAtIndex:index withObject:object]; |
} |
} |
- (void)removeLastObject; |
{ |
if (/* 変更が有効 */) { |
[embeddedArray removeLastObject]; |
} |
} |
- (void)insertObject:object atIndex:(unsigned)index; |
{ |
if (/* 変更が有効 */) { |
[embeddedArray insertObject:object atIndex:index]; |
} |
} |
- (void)removeObjectAtIndex:(unsigned)index; |
{ |
if (/* 変更が有効 */) { |
[embeddedArray removeObjectAtIndex:index]; |
} |
} |
| < Previous PageNext Page > |
Last updated: 2006-05-23
|
Get information on Apple products.
Visit the Apple Store online or at retail locations. 1-800-MY-APPLE Copyright © 2007 Apple Inc. All rights reserved. | Terms of use | Privacy Notice |