W S
W S

Reputation: 115

how can I use an Objective-C factory method in a superclass, when instantiating a subclass?

This question is about subclassing a class that has a factory method. I can't get the subclass to work properly.

I have a class Car. I want it to return a Car instance only if it is capable of making a trip of a specified distance, given also its fuel consumption and fuel capacity. I made a class (factory) method for this.

It does exactly what I want when I call the factory method from my view controller to instantiate a car (code is shown further down).

@interface Car : NSObject
@end

@implementation Car
- (instancetype) init 
  { return [super init]; }

+ (Car *) carWithFuelConsumption:(double)miles_per_gallon 
                       andGallonsInTank:(double)gallons 
                          forTripLength:(double)miles_to_go 
{
if (miles_per_gallon * gallons) > miles_to_go) {
      Car *goodCar = [[self alloc] init];
      return goodCar;
} else {
       return nil;
}

Now I need to have trucks also, for which I need to keep track of their cargo capacity. I made Truck a subclass of Car.

@interface Truck : Car
@property (double) cargoCapacityLbs;
@end

@implementation Truck
- (instancetype) init {
    self = [super init];
  if (self) {
    self.cargoCapacity= 2000.0;
  }
    return self;
}

In my view controller I can instantiate a car just fine:

Car *car1 = [Car carWith... 20 ...5 ... 60]; //5 gallons enough for 60 miles
Car *car2 = [Car carWith... 20 ...5 ... 90]; 
/// car2 is nil, 5 gallons NOT enough for 60 miles at 20 miles per gallon.

But a truck gets me

A) a compiler warning

 Truck *t1 = [Car carWith... 20 ...5 ... 60]; //5 gallons enough for 60 miles

**incompatible pointer types assigning to Truck from Car**

Same thing if I change Car to Truck, like this:

Truck *truck1 = [Truck carWith... 20 ...5 ... 60]; //5 gallons enough for 60 miles

B) AND a crash when trying to access truck1.cargoCapacityLbs

cargoCapacityLbs ..unrecognized selector sent to instance <....>**

I think there is something fundamental I am not understanding about subclassing. I've tried calling the factory method directly in the Truck initialization method, no luck.

I am thinking of rewriting the Car class, to not use a factory method, but that seems bad because then the validation can't be done in the Car class, and would have to be done in the view controller.

Any suggestions?

Upvotes: 0

Views: 174

Answers (2)

W S
W S

Reputation: 115

Instancetype did it. But I wasn't very happy with having

 Car *goodCar = [[self alloc] init];

in the factory method.

I also came across this blog post http://ernstsson.net/post/80915338055/objective-c-class-factory-methods

So I changed Car:

@interface Car
@property double burn;
@property double gallons;
@end

@implementation Car

- (instancetype) init {
  return nil; 
 ///preclude use of an incomplete initializer
 }

 - (instancetype) initWithBurn:(double)MPG andGallonsInTank:(double) gallons {

self = [super init];
if (self) {
    _burn = MPG;
    _gallons = gallons;
}
return self;
}

+ (instancetype) createWithFuelConsumption:(double)MPG andGallonsInTank:(double)Gallons forTripLength:(double) miles {

if  (MPG * Gallons < miles) {       
     return nil;
} else {        
   return [[self alloc] initWithBurn:MPG andGallonsInTank:Gallons];        
}
}

Now there's no more reference to the Car class in the factory method.

(Couldn't post this as a comment, it's too long)

Upvotes: 0

Vladimir Kofman
Vladimir Kofman

Reputation: 1993

A) Your approach of using factory method is legitimate and should work properly. To get rid of compiler warning, use instancetype as result type in the factory method declaration:

+ (instancetype) carWithFuelConsumption:(double)miles_per_gallon
                andGallonsInTank:(double)gallons
                   forTripLength:(double)miles_to_go;

B) From your code above, you should be able access cargoCapacityLbs without crashing (perhaps you've just misspelled the name?). Here's the code I've used and it worked properly:

@interface Car : NSObject

+ (instancetype) carWithFuelConsumption:(double)miles_per_gallon
                andGallonsInTank:(double)gallons
                   forTripLength:(double)miles_to_go;
@end

@implementation Car

+ (instancetype) carWithFuelConsumption:(double)miles_per_gallon
                andGallonsInTank:(double)gallons
                   forTripLength:(double)miles_to_go
{
    if(miles_per_gallon * gallons > miles_to_go) {
        Car *goodCar = [[self alloc] init];
        return goodCar;
    } else {
        return nil;
    }
}
@end

@interface Truck : Car
@property double cargoCapacityLbs;
@end

@implementation Truck
- (instancetype) init {
    self = [super init];

    if (self) {
        self.cargoCapacityLbs = 2000.0;
    }

    return self;
}
@end


Truck* t1  = [Truck carWithFuelConsumption:5 andGallonsInTank:44 forTripLength:3];
NSLog(@"%f", t1.cargoCapacityLbs);

Upvotes: 2

Related Questions