Objective-C複合オブジェクト

オブジェクトをその中に埋め込むクラスを定義するクラスクラスター内にサブクラスを作成できます。これらのクラスオブジェクトは複合オブジェクトです。したがって、クラスクラスターとは何か疑問に思われるかもしれません。したがって、最初にクラスクラスターとは何かを確認します。

クラスクラスター

クラスクラスターは、Foundationフレームワークが広範囲に使用するデザインパターンです。クラスクラスターは、パブリック抽象スーパークラスの下にいくつかのプライベート具象サブクラスをグループ化します。このようにクラスをグループ化すると、機能の豊富さを損なうことなく、オブジェクト指向フレームワークの公開されているアーキテクチャが簡素化されます。クラスクラスターは、abstract factory デザインパターン。

簡単にするために、同様の関数に対して複数のクラスを作成する代わりに、入力の値に基づいて処理を処理する単一のクラスを作成します。

たとえば、NSNumberには、char、int、boolなどのクラスのクラスターが多数あります。それらすべてを単一のクラスにグループ化し、単一のクラスで同様の操作を処理します。NSNumberは、実際にはこれらのプリミティブ型の値をオブジェクトにラップします。

複合オブジェクトとは何ですか?

独自の設計のオブジェクトにプライベートクラスターオブジェクトを埋め込むことにより、複合オブジェクトを作成します。この複合オブジェクトは、その基本的な機能をクラスターオブジェクトに依存でき、複合オブジェクトが特定の方法で処理したいメッセージのみをインターセプトします。このアーキテクチャにより、作成する必要のあるコードの量が減り、FoundationFrameworkによって提供されるテスト済みのコードを利用できるようになります。

これを次の図で説明します。

複合オブジェクトは、それ自体がクラスターの抽象スーパークラスのサブクラスであることを宣言する必要があります。サブクラスとして、スーパークラスのプリミティブメソッドをオーバーライドする必要があります。派生メソッドをオーバーライドすることもできますが、派生メソッドはプリミティブメソッドを介して機能するため、これは必要ありません。

NSArrayクラスのcountメソッドは一例です。介在するオブジェクトのオーバーライドするメソッドの実装は、次のように単純にすることができます。

- (unsigned)count  {
   return [embeddedObject count];
}

上記の例では、埋め込みオブジェクトは実際にはNSArray型です。

複合オブジェクトの例

ここで、完全な例を確認するために、以下に示すAppleのドキュメントの例を見てみましょう。

#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

@implementation ValidatingArray
- init {
   self = [super init];
   if (self) {
      embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init];
   }
   return self;
}

+ validatingArray {
   return [[self alloc] init] ;
}

- (unsigned)count {
   return [embeddedArray count];
}

- objectAtIndex:(unsigned)index {
   return [embeddedArray objectAtIndex:index];
}

- (void)addObject:(id)object {
   if (object != nil) {
      [embeddedArray addObject:object];
   }
}

- (void)replaceObjectAtIndex:(unsigned)index withObject:(id)object; {
   if (index <[embeddedArray count] && object != nil) {
      [embeddedArray replaceObjectAtIndex:index withObject:object];
   }
}

- (void)removeLastObject; {
   if ([embeddedArray count] > 0) {
      [embeddedArray removeLastObject];
   }
}

- (void)insertObject:(id)object atIndex:(unsigned)index; {
   if (object != nil) {
      [embeddedArray insertObject:object atIndex:index];
   }
}

- (void)removeObjectAtIndex:(unsigned)index; {
   if (index <[embeddedArray count]) {
      [embeddedArray removeObjectAtIndex:index];
   }
}

@end

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   ValidatingArray *validatingArray = [ValidatingArray validatingArray];
   
   [validatingArray addObject:@"Object1"];
   [validatingArray addObject:@"Object2"];
   [validatingArray addObject:[NSNull null]];
   [validatingArray removeObjectAtIndex:2];
   NSString *aString = [validatingArray objectAtIndex:1];
   NSLog(@"The value at Index 1 is %@",aString);
   [pool drain];
   
   return 0;
}

プログラムをコンパイルして実行すると、次の結果が得られます。

2013-09-28 22:03:54.294 demo[6247] The value at Index 1 is Object2

上記の例では、配列の1つの関数を検証しても、通常のシナリオでクラッシュにつながるnullオブジェクトを追加できないことがわかります。しかし、検証配列がそれを処理します。同様に、配列を検証する各メソッドは、通常の一連の操作とは別に検証プロセスを追加します。