2008-11-10 5 views
7

Ich habe zwei Klassen, eine eine Unterklasse der anderen (sagen Animal und Dog). Die Oberklasse hat einige Initialisierer (sagen wir initAnimal), die Unterklasse hat einige Initialisierer (sagen wir initDog). Das Problem ist, dass es (aus der Sicht des Compilers) vollkommen legal ist, etwas wie Dog *adog = [[Dog alloc] initAnimal] zu machen, d. initialisieren Sie eine Klasse mit ihrem Superklasseninitialisierer. Ich mag das nicht, weil die Unterklasse einige zusätzliche Instanzvariablen haben kann, die ich sicherstellen will, dass sie initialisiert werden. Ein Blick in die Header-Datei löst das, aber gibt es eine einfache Möglichkeit, den Compiler auf mich überprüfen zu lassen? Ich habe das Gefühl, dass ich etwas schrecklich offensichtlich fehle, aber ich kann einfach nicht meinen Finger auf sie setzen :-)Initialisieren einer Klasse mit Superklasseninitialisierer

Update: Die initDog und initAnimal waren nicht die besten Beispiele. Ich meinte zwei wirklich unterschiedliche Initialisierer (wie init für Animal und initWithFur für Dog). Wenn ich wollte, dass jedem Hund etwas Fell zugewiesen wurde, hätte ich das Fell zum Initialisierer gemacht, damit niemand einen Hund ohne Fell bekommen konnte. Aber dann ist es immer noch leicht, die Instanz versehentlich mit der Superklasse init zu initialisieren, und dann werde ich abgespritzt.

Danke, dass Sie die designierten Initialisierer, Jason, angesprochen haben. Es ist mir noch nie in den Sinn gekommen, aber ich könnte den designierten Initialisierer der Superklasse überladen und dort einige vernünftige Standardwerte einstellen. Aber ich würde es trotzdem bevorzugen, wenn ich es irgendwie illegal machen könnte, andere Initialisierer als die der Klasse selbst zu verwenden - weitere Ideen?

+0

Okay Zoul, ich habe Updates zu meiner Antwort basierend auf den Updates zu Ihrer Frage gemacht ... es ist jetzt ziemlich lang, aber ich hoffe, es gibt Ihnen einige der Erkenntnisse, die Sie gesucht haben. –

+0

Da dies ein Top-Ergebnis ist. Die Antwort im modernen XCode kann man mit NS_UNAVAILABLE erreichen. Siehe: http://stackoverflow.com/questions/195078/is-it-possible-to-make-the-init-method-private-in-objective-c#answer-27693034 – m4js7er

Antwort

18

Im Allgemeinen erstellen Sie in Objective-C einen designierten Initialisierer für jede Klasse und dann verwenden Unterklassen den gleichen Initialisierer. Anstatt also initAnimal und initDog zu verwenden, verwenden Sie einfach init. Der Hund Unterklasse würde dann seine eigene Methode init definieren und die bezeichnete initializer in seiner übergeordneten Klasse aufrufen:

@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 

Sie haben nicht wirklich initDog und initAnimal angeben, da die Klasse auf der rechten Seite der erklärt wird, Zuordnung ...

Update: ich füge die folgende auf die Antwort zu reflektieren die zusätzliche Informationen in der Frage

Es gibt eine Reihe von Möglichkeiten, um sicherzustellen, dass Unterklassen nicht nennen initializers andere als ihr designierter Initialisierer und die Art, wie du Ultimate machst ely wählen wird weitgehend auf Ihrem gesamten Design basieren. Eines der schönen Dinge an Objective-C ist, dass es so flexibel ist. Ich werde Ihnen zwei Beispiele geben, um Ihnen den Einstieg zu erleichtern.

Wenn Sie zuerst eine Unterklasse erstellen, die einen anderen als die übergeordnete Klasse als Initialisierer hat, können Sie den Initialisierer des übergeordneten Elements überladen und eine Ausnahme auslösen. Dies wird Programmierer sofort wissen lassen, dass sie das Protokoll für Ihre Klasse verletzt haben ... aber es sollte angegeben werden, dass Sie einen guten Grund dafür haben sollten und dass es sehr gut dokumentiert sein sollte, dass die Unterklasse dies kann verwende nicht denselben Initialisierer wie die Oberklasse.

@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 

Eine andere Möglichkeit, es zu tun ist Initialisierer mehr wie Ihr ursprüngliches Beispiel zu haben. In diesem Fall könnten Sie die Standardinitialisierung für die Elternklasse so ändern, dass sie immer fehlschlägt. Sie könnten dann einen privaten Initialisierer für Ihre Elternklasse erstellen und dann sicherstellen, dass jeder den entsprechenden Initialisierer in Unterklassen aufruft.Dieser Fall ist offensichtlich komplizierter:

@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 

Hier können Sie die Schnittstelle und Implementierung der Tier- und Hunde-Klasse. Das Animal ist das designierte Objekt auf oberster Ebene und überschreibt daher die Implementierung von init durch NSObject. Jeder, der init für ein Animal oder eine der Unterklassen von Animal aufruft, erhält einen Assertion-Fehler, der sie auf die Dokumentation verweist. Animal definiert außerdem einen privaten Initialisierer für eine private Kategorie. Die private Kategorie würde mit Ihrem Code beibehalten und Unterklassen von Animal würden diesen privaten Initialisierer aufrufen, wenn sie Super aufrufen. Der Zweck ist, init in der Superklasse von Animal (in diesem Fall NSObject) aufzurufen und generische Initialisierungen durchzuführen, die notwendig sein könnten.

Schließlich ist die erste Zeile in der initAnimal-Methode von Animal eine Behauptung, dass der Empfänger tatsächlich ein Animal und nicht irgendeine Unterklasse ist. Wenn der Empfänger kein Tier ist, wird das Programm mit einem Assertionsfehler fehlschlagen und der Programmierer wird auf die Dokumentation verwiesen.

Dies sind nur zwei Beispiele, wie Sie etwas mit Ihren spezifischen Anforderungen entwerfen können. Allerdings würde ich stark vorschlagen, dass Sie Ihre Design-Einschränkungen berücksichtigen und sehen, ob Sie wirklich diese Art von Design benötigen, da es nicht-Standard in Cocoa und in den meisten OO-Design-Frameworks ist. Sie können zum Beispiel verschiedene Objekte auf Root-Level-Ebene erstellen und stattdessen einfach ein Animal-Protokoll verwenden, bei dem alle verschiedenen "Tiere" auf bestimmte generische Tiernachrichten reagieren müssen. Auf diese Weise kann jedes Tier (und wahre Unterklassen von Animal) selbst mit seinen Initiatoren umgehen und muss sich nicht auf Superklassen verlassen, die sich so spezifisch und nicht standardgemäß verhalten.

+0

Sehr nett, danke für Ihre Zeit . – zoul