9

私は2つのクラスを持っています。1つはもう1つのサブクラスです(たとえばAnimal、 とDog)。スーパークラスにはいくつかの初期化子 (たとえばinitAnimal) があり、サブクラスにはいくつかの初期化子 (たとえばinitDog) があります。問題は、(コンパイラの観点から) のようなことを行うことが完全に合法であることですDog *adog = [[Dog alloc] initAnimal]。スーパークラス初期化子を使用してクラスを初期化します。サブクラスには、初期化されていることを確認したい追加のインスタンス変数がいくつかある可能性があるため、これは好きではありません。ヘッダー ファイルを調べれば解決しますが、コンパイラにチェックさせる簡単な方法はありますか? 私はひどく明白な何かが欠けていると感じていますが、私はそれに指を置くことができません:-)

更新:initDoginitAnimalは最良の例ではありませんでした。2 つのまったく異なる初期化子 ( initforAnimalinitWithFurforなどDog) を意味していました。すべての犬にファーを割り当てたい場合は、イニシャライザーのファー部分を作成して、ファーなしで犬オブジェクトを取得できないようにします。しかし、スーパークラスinitでインスタンスを誤って初期化することは依然として簡単であり、私はうんざりしています。

指定された初期化子を取り上げてくれてありがとう、ジェイソン。以前は思いつきませんでしたが、スーパークラスの指定された初期化子をオーバーロードして、そこに適切なデフォルトを設定することができました。しかし、クラス自体のイニシャライザ以外のイニシャライザを使用することを何らかの方法で違法にすることができれば、なお良いと思います。他にアイデアはありますか?

4

1 に答える 1

21

通常、Objective-C では、クラスごとに指定された初期化子を作成し、サブクラスは同じ初期化子を使用します。したがって、initAnimal と initDog を使用する代わりに、init を使用するだけです。次に、dog サブクラスは独自の init メソッドを定義し、親クラスで指定された初期化子を呼び出します。

@implementation Dog
-(id)init
{
    if( (self = [super init]) ) {  // call init in Animal and assign to self
        // do something specific to a dog
    }
    return self;
}
@end

クラスは割り当ての右側で宣言されているため、initDog と initAnimal を指定する必要はありません...

更新:質問の追加情報を反映するために、回答に以下を追加しています

サブクラスが指定された初期化子以外の初期化子を呼び出さないようにする方法はいくつかありますが、最終的に選択する方法は主に設計全体に基づいています。Objective-C の優れている点の 1 つは、柔軟性が高いことです。ここでは、まず 2 つの例を示します。

まず、親クラスとは異なる指定イニシャライザを持つサブクラスを作成すると、親のイニシャライザをオーバーロードして例外をスローできます。これにより、プログラマーはクラスのプロトコルに違反していることをすぐに知ることができます...ただし、これを行うには十分な理由が必要であり、サブクラスが使用しない可能性があることを十分に文書化する必要があることを述べおく必要がありますスーパークラスと同じ初期化子。

@implementation Dog
-(id)init
{
    // Dog does not respond to this initializer
    NSAssert( false, @"Dog classes must use one of the designated initializers; see the documentation for more information." );

    [self autorelease];
    return nil;
}

-(id)initWithFur:(FurOptionsType)furOptions
{
    if( (self = [super init]) ) {
        // do stuff and set up the fur based on the options
    }
    return self;
}
@end

それを行う別の方法は、初期化子を元の例のようにすることです。その場合、親クラスのデフォルトの init を変更して、常に失敗するようにすることができます。次に、親クラスのプライベート イニシャライザを作成し、すべてのユーザがサブクラスで適切なイニシャライザを呼び出すようにします。このケースは明らかにもっと複雑です:

@interface Animal : NSObject
-(id)initAnimal;
@end

@interface Animal ()
-(id)_prvInitAnimal;
@end

@interface Dog : Animal
-(id)initDog;
@end

@implementation Animal
-(id)init
{
    NSAssert( false, @"Objects must call designated initializers; see documentation for details." );

    [self autorelease];
    return nil;
}

-(id)initAnimal
{
    NSAssert( [self isMemberOfClass:[Animal class]], @"Only Animal may call initAnimal" );

    // core animal initialization done in private initializer
    return [self _prvInitAnimal];
}

-(id)_prvInitAnimal
{
    if( (self = [super init]) ) {
        // do standard animal initialization
    }
    return self;
}
@end

@implementation Dog
-(id)initDog
{
    if( (self = [super _prvInitAnimal]) ) {
        // do some dog related stuff
    }
    return self;
}
@end

ここでは、Animal および Dog クラスのインターフェースと実装を確認できます。Animal は指定された最上位オブジェクトであるため、NSObject の init の実装をオーバーライドします。アニマルまたはアニマルのサブクラスのいずれかで init を呼び出すと、ドキュメントを参照してアサーション エラーが発生します。Animal は、プライベート カテゴリのプライベート イニシャライザも定義します。private カテゴリはコードに残り、Animal のサブクラスは super を呼び出すときにこの private 初期化子を呼び出します。その目的は、Animal のスーパークラス (この場合は NSObject) で init を呼び出し、必要な一般的な初期化を行うことです。

最後に、Animal の initAnimal メソッドの最初の行は、レシーバーが実際には Animal であり、サブクラスではないという主張です。レシーバーが Animal でない場合、プログラムはアサーション エラーで失敗し、プログラマーにドキュメントを参照してもらいます。

これらは、特定の要件で何かを設計する方法の 2 つの例にすぎません。ただし、Cocoa およびほとんどの OO 設計フレームワークでは非標準であるため、設計上の制約を考慮し、このタイプの設計が本当に必要かどうかを確認することを強くお勧めします。たとえば、さまざまな動物のルート レベル オブジェクトを作成することを検討し、代わりに Animal プロトコルを使用して、さまざまな「動物」のすべてが特定の動物ジェネリック メッセージに応答することを要求することができます。そうすれば、各アニマル (およびアニマルの真のサブクラス) は、指定されたイニシャライザ自体を処理でき、そのような特定の非標準的な方法で動作するスーパークラスに依存する必要がなくなります。

于 2008-11-10T10:07:10.387 に答える