views:

290

answers:

2

I want to create classes Car, Vehicle, and Airplane with the following properties:

  • Car and Airplane are both subclasses of Vehicle.
  • Car and Airplane both have an initWithString method.
  • The acceptable input strings for Car's and Airplane's initWithString methods do not overlap.
  • Vehicle is "almost abstract", in the sense that any initialized instance should be either a Car or an Airplane.
  • It is possible to pass a string into Vehicle and get back an instance of Car, an instance of Airplane, or nil, depending on the input string.

Any particular design pattern I should prefer? In particular for Vehicle's initWithString and/or newVehicleWithString methods.

A: 

Referring to a subclass from a superclass is not a good idea.

If you really have to do this, you should at least go with a class method like vehicleWithString:.

In fact, I doubt that the other approach (using vehicle initWithString: to create instances of Car or Airplane) would work.

Can Berk Güder
The superclass-referring-to-subclass thing is how class clusters work all throughout Cocoa.
Chuck
On the contrary! This is a design pattern called "class clusters", where `Vehicle initWithString:` decides which subclass to return an instance of. This pattern is used throughout Cocoa, especially by NSString.
codewarrior
This is precisely why I love SO, thanks. However, as far as I can tell, the subclasses in class clusters in Cocoa are all private. I still think using this pattern with public subclasses is somewhat dangerous.
Can Berk Güder
+3  A: 

What you need is the "class cluster" pattern. Your Vehicle initWithString: method could look something like this:

- (id) initWithString:(NSString *)mode {
  // note that we don't call [super init] in a class cluster. 
  // instead, we have to release self because it's an unwanted Vehicle instance
  [self release];
  if([mode isEqualToString:@"wheels"]) {
    return [[Car alloc] initWithString:@"wheels"];
  }
  if([mode isEqualToString:@"wings"]) {
    return [[Airplane alloc] initWithString:@"wings"];
  }
  return nil;  //alternately, raise NSInvalidArgumentException
}
codewarrior
I think it's useful to note that in this case it would probably be a lot more efficient to override alloc and return a special singleton instance that allocates and initializes the correct class and returns that.
Jason Coco
True, but that sounds a lot like premature optimization.
codewarrior
It seems these days everyone has to take everything to an extreme. Now people don't think about even simple solutions when there is a very clear and easy opportunity to 'optimize'. In your case you are creating and throwing away objects 100% of the time. We need balance, people :) In this instance it would be cleaner and more correct anyway--alloc is also where class clusters are created, not in init.
Jason Coco
I agree that not creating a throwaway object is an obvious thing to do. It's also true that Apple's class clusters also work like this, returning a singleton placeholder instance from `[NSDictionary alloc]` et al. But to put it another way, I'm letting the computer throw away all those objects so I won't have to create a singleton placeholder class.
codewarrior
Well I could have it both ways by not having an initWithString method in vehicle at all . . . instead having a newVehicleWithString method that allocates and initiates the correct subclass.
William Jockusch
I KNEW we were overthinking this!
codewarrior
Well, that is an extremely silly reason to be creating and throwing away all those objects, /especially/ on a resource-limited device. Alloc is really the best, most appropriate place to do this. Basically he is creating a factory here and in that pattern the true instance should be created by some singleton (or created directly).
Jason Coco