Reputation: 115
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
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
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