views:

79

answers:

5

I want to initialize an instance of one of the subclasses of a superclass depending on the arguments to init:

[[Vehicle alloc] initWithItinerary: shortWay]; // returns a bicycle
[[Vehicle alloc] initWithItinerary: longWay];  // returns a car

I can't find examples of code like this. I wonder if this is not idiomatic in Objective C, or I simply am not looking in the right places.

+6  A: 

You could do this via a custom init method, but it'd be kind of tedious (you'd have to invoke [super init], but then call [self release], etc...). It'd be much simpler to create a class method on Vehicle and use that as your factory method. For example:

+ (id) vehicleWithItinerary:(id)someItinerary {
  if ([someItinerary isAShortWay]) {
    return [[[Bicycle alloc] initWithItinerary:someItinerary] autorelease];
  } else if ([someItinerary isAMediumWay]) {
    return [[[RocketPack alloc] initWithItinerary:someItinerary] autorelease];
  } else if ([someItinerary isALongWay]) {
    return [[[Car alloc] initWithItinerary:someItinerary] autorelease];
  }
  return nil;
}
Dave DeLong
+1  A: 

Look at [UIButton buttonWithType:] for an example of how Apple does this. Instead of init, they use a static method of the base class to allocate an instance of the appropriate derived class.

You can also pass around Class objects. Maybe the itinerary knows the Class or class name to allocate. You can do something like this:

[[[itinerary classToAllocate] alloc] initWithItinerary:itinerary];

or

[[NSClassFromString( [itinerary classNameToAllocate] ) alloc] initWithItinerary:itinerary];

You are allowed to release self and create a new object in init, although this is rarely used. Just watch out for recursion.

-(id) initWithItinerary:(Itinerary *)inItinerary {
  [self release]; // super init never called - safe if you wrote super classes
  self = [[[inItinerary classToAllocate] alloc] init];
  self.itinerary = inItinerary;
  return self;
}
drawnonward
I wonder if you can elaborate on your concern around recursion.
iter
In my last example, the base class implements initWithItinerary. If the derived classes also had such a method and called super initWithItinerary it would loop. Easy enough to design around as long as you are looking for it.
drawnonward
A: 

You might want to add an enum to the header file:

typedef enum {Bike, Car, JetPack } vehicleType

That way your initWithItinerary: method can simply be:

if(VehicleType == Bike)
{
    //do bike stuff
}
else if(VehicleType == Car)
{
    //do car stuff
}
This is a rather creative approach.
iter
+1  A: 

This is called a class cluster. Several Cocoa classes work this way, including NSArray and NSString. The object returned from NSArray's init methods is never the same object that received the message. It's not that common outside of Cocoa, though, just because it's usually more complicated than people want to bother with. Basically, you figure out what actual class you want to use in your initializer, create an instance of that class, release yourself and return the other instance.

Chuck
A: 

Why not have a method as part of the "way" that gives you a vehicle of the appropriate type for the way. e.g.

e.g.

// Somwhere before you use them.  Car and Bicycle are subclasses of Vehicle
[shortWay setAppropriateVehicleType: [Bicycle class]];
[longWay setAppropriateVehicleType: [Car class]];

// when you need a vehicle

Vehicle* vehicle = [[[shortWay appropriateVehicleType] alloc] init];
JeremyP
My idea is that the caller doesn't need to know which implementation it gets. If the caller wants a specific subclass and it knows the subclass, it can simply call [[alloc] init] on it. You may want to review the Factory concept.
iter
And with my idea, the caller doesn't know which implementation it gets. Since the correct class to return is a property of the itinerary, it seems obvious to me to make it explicitly so as I have done. The answer you have actually chosen is conceptually very similar to mine since the itinerary does maintain the class as a property and an object of that class is eventually allocated. The difference is my version results in a cleaner implementation that does not rely on class clusters.
JeremyP