Intro to Object-Oriented Design: Part 1/2

This tutorial series will teach you the basics of object-oriented design. In this first part: Inheritance, and the Model-View-Controller pattern. By Ellen Shapiro.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Creating Subclasses via Inheritance

First, add the following new method to Vehicle.h:

//Convenience method to get the vehicle's details.
-(NSString *)vehicleDetailsString;

That's the publicly-declared method that can be used from clients such as VehicleDetailsViewController. There's no need for them to know about each individual property; instead, they can just ask for vehicleDetailsString, receive a fully-formatted string, and use it.

Switch over to Vehicle.m and add the implementation:

-(NSString *)vehicleDetailsString
{
    //Setup the basic details string based on the properties in the base Vehicle class.
    NSMutableString *basicDetailsString = [NSMutableString string];
    [basicDetailsString appendString:@"Basic vehicle details:\n\n"];
    [basicDetailsString appendFormat:@"Brand name: %@\n", self.brandName];
    [basicDetailsString appendFormat:@"Model name: %@\n", self.modelName];
    [basicDetailsString appendFormat:@"Model year: %d\n", self.modelYear];
    [basicDetailsString appendFormat:@"Power source: %@\n", self.powerSource];
    [basicDetailsString appendFormat:@"# of wheels: %d", self.numberOfWheels];
    
    return [basicDetailsString copy];
}

The method is similar to what you added to VehicleDetailViewController.m, except it returns the generated string rather than display it somewhere directly.

Now, you can use inheritance to take this basic vehicle string and add the specific details for the Car class. Open Car.m and add the override implementation of vehicleDetailsString:

- (NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
    
    //Initialize mutable string
    NSMutableString *carDetailsBuilder = [NSMutableString string];
    [carDetailsBuilder appendString:@"\n\nCar-Specific Details:\n\n"];
    
    //String helpers for booleans
    NSString *yes = @"Yes\n";
    NSString *no = @"No\n";
    
    //Add info about car-specific features.
    [carDetailsBuilder appendString:@"Has sunroof: "];
    if (self.hasSunroof) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
    
    [carDetailsBuilder appendString:@"Is Hatchback: "];
    if (self.isHatchback) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
    
    [carDetailsBuilder appendString:@"Is Convertible: "];
    if (self.isConvertible) {
        [carDetailsBuilder appendString:yes];
    } else {
        [carDetailsBuilder appendString:no];
    }
    
    [carDetailsBuilder appendFormat:@"Number of doors: %d", self.numberOfDoors];
    
    //Create the final string by combining basic and car-specific details.
    NSString *carDetails = [basicDetails stringByAppendingString:carDetailsBuilder];
    
    return carDetails;
}

Car's version of the method starts by calling the superclass's implementation to get the vehicle details. It then builds the car-specific details string into carDetailsBuilder and then combines the two at the very end.

Now replace configureView in VehicleDetailViewController.m with the much simpler implementation below to display this string that you’ve created:

- (void)configureView
{
    // Update the user interface for the detail vehicle, if it exists.
    if (self.detailVehicle) {
        //Set the View Controller title, which will display in the Navigation bar.
        self.title = [self.detailVehicle vehicleTitleString];
        self.vehicleDetailsLabel.text = [self.detailVehicle vehicleDetailsString];
    }
}

Build and run your application; select one of the cars, and you should now be able to see both general details and car-specific details as shown below:

Basic and car-specific details

Your VehicleDetailViewController class now allows the Vehicle and Car classes to determine the data to be displayed. The only thing ViewController is doing is connecting that information up with the view!

The real power in this is evident when you create further subclasses of Vehicle. Start with a fairly simple one for a motorcycle.

Go to File\New\File\CocoaTouch\Objective-C Class, and create a new subclass of Vehicle called Motorcycle.

Since motorcycles can have either a deep booming engine noise, or a high-pitched whine of an engine noise, each Motorcycle object you create you should specify which type of noise it makes.

Add a single string property for the Engine Noise in Motorcycle.h just after the @interface line:

@property (nonatomic, strong) NSString *engineNoise;

Then, open Motorcycle.m. Add the following init method:

#pragma mark - Initialization
- (id)init
{
    if (self = [super init]) {
        self.numberOfWheels = 2;
        self.powerSource = @"gas engine";
    }
    
    return self;
}

Since all motorcycles have two wheels and are gas-powered (for the sake of this example, anything that's electric-powered would be considered a scooter, not a motorcycle), you can set up the number of wheels and power source when the object is instantiated.

Next, add the following methods to override all the superclass methods that will just return nil otherwise:

#pragma mark - Superclass Overrides
-(NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ Open throttle.", [self changeGears:@"Forward"]];
}

-(NSString *)goBackward
{
    return [NSString stringWithFormat:@"%@ Walk %@ backwards using feet.", [self changeGears:@"Neutral"], self.modelName];
}

-(NSString *)stopMoving
{
    return @"Squeeze brakes.";
}

-(NSString *)makeNoise
{
    return self.engineNoise;
}

Finally, override the vehicleDetailsString method in order to add the Motorcycle-specific details to the vehicleDetailsString as shown below:

- (NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
    
    //Initialize mutable string
    NSMutableString *motorcycleDetailsBuilder = [NSMutableString string];
    [motorcycleDetailsBuilder appendString:@"\n\nMotorcycle-Specific Details:\n\n"];
    
    //Add info about motorcycle-specific features.
    [motorcycleDetailsBuilder appendFormat:@"Engine Noise: %@", self.engineNoise];
    
    //Create the final string by combining basic and motorcycle-specific details.
    NSString *motorcycleDetails = [basicDetails stringByAppendingString:motorcycleDetailsBuilder];
    
    return motorcycleDetails;
}

Now, it’s time to create some instances of Motorcycle.

Open VehicleListTableViewController.m and make sure it can see the Motorcycle class by adding the following import:

#import "Motorcycle.h"

Next, find setupVehicleArray and add the following code below the Cars you’ve already added but above where you sort the array:

    //Create a motorcycle
    Motorcycle *harley = [[Motorcycle alloc] init];
    harley.brandName = @"Harley-Davidson";
    harley.modelName = @"Softail";
    harley.modelYear = 1979;
    harley.engineNoise = @"Vrrrrrrrroooooooooom!";
    
    //Add it to the array.
    [self.vehicles addObject:harley];
    
    //Create another motorcycle
    Motorcycle *kawasaki = [[Motorcycle alloc] init];
    kawasaki.brandName = @"Kawasaki";
    kawasaki.modelName = @"Ninja";
    kawasaki.modelYear = 2005;
    kawasaki.engineNoise = @"Neeeeeeeeeeeeeeeeow!";
    
    //Add it to the array
    [self.vehicles addObject:kawasaki];

The above code simply instantiates two Motorcycle objects and adds them to the vehicles array.

Build and run your application; you’ll see the two Motorcycle objects you added in the list:

Added Motorcycles

Tap on one of them, and you'll be taken to the details for that Motorcycle, as shown below:

Motorcycle Details

Whether it's a car or a motorcycle (or even a plain old vehicle), you can call vehicleDetailsString and get the relevant details.

By using proper separation between the model, the view, and the view controller and inheritance, you’re now able to display data for several subclasses of the same superclass without having to write tons of extra code to handle different subclass types. Less code written == happier developers! :]

Housing Logic in the Model Class

You can also use this approach to keep some of the more complicated logic encapsulated within the model class. Think about a Truck object: many different types of vehicles are referred to as a “truck”, ranging from pickup trucks to tractor-trailers. Your truck class will have a little bit of logic to change the truck's behavior based on the number of cubic feet of cargo it can haul.

Go to File\New\File\CocoaTouch\Objective-C Class, and create a new subclass of Vehicle called Truck.

Add the following integer property to Truck.h to hold the truck's capacity data:

@property (nonatomic, assign) NSInteger cargoCapacityCubicFeet;

Since there are so many different types of trucks, you don't need to create an init method that provides any of those details automatically. You can simply override the superclass methods which would be the same no matter the size of the truck.

Open Truck.m and add the following methods:

#pragma mark - Superclass overrides
- (NSString *)goForward
{
    return [NSString stringWithFormat:@"%@ Depress gas pedal.", [self changeGears:@"Drive"]];
}

- (NSString *)stopMoving
{
    return [NSString stringWithFormat:@"Depress brake pedal. %@", [self changeGears:@"Park"]];
}

Next, you’ll want to override a couple of methods so that the string returned is dependent on the amount of cargo the truck can haul. A big truck needs to sound a backup alarm, so you can create a private method (i.e., a method not declared in the .h file and thus not available to other classes) to do this.

Add the following helper method code to Truck.m:

#pragma mark - Private methods
- (NSString *)soundBackupAlarm
{
    return @"Beep! Beep! Beep! Beep!";
}

Then back in the superclass overrides section, you can use that soundBackupAlarm method to create a goBackward method that sounds the alarm for big trucks:

- (NSString *)goBackward
{
    NSMutableString *backwardString = [NSMutableString string];
    if (self.cargoCapacityCubicFeet > 100) {
        //Sound a backup alarm first
        [backwardString appendFormat:@"Wait for \"%@\", then %@", [self soundBackupAlarm], [self changeGears:@"Reverse"]];
    } else {
        [backwardString appendFormat:@"%@ Depress gas pedal.", [self changeGears:@"Reverse"]];
    }
    
    return backwardString;
}


Trucks also have very different horns; a pickup truck, for instance, would have a horn similar to that of a car, while progressively larger trucks have progressively louder horns. You can handle this with some simple if/else statements in the makeNoise method.

Add makeNoise as follows:

- (NSString *)makeNoise
{
    if (self.numberOfWheels <= 4) {
        return @"Beep beep!";
    } else if (self.numberOfWheels > 4 && self.numberOfWheels <= 8) {
        return @"Honk!";
    } else {
        return @"HOOOOOOOOONK!";
    }
}

Finally, you can override the vehicleDetailsString method in order to get the appropriate details for your Truck object. Add the method as follows:

-(NSString *)vehicleDetailsString
{
    //Get basic details from superclass
    NSString *basicDetails = [super vehicleDetailsString];
    
    //Initialize mutable string
    NSMutableString *truckDetailsBuilder = [NSMutableString string];
    [truckDetailsBuilder appendString:@"\n\nTruck-Specific Details:\n\n"];
    
    //Add info about truck-specific features.
    [truckDetailsBuilder appendFormat:@"Cargo Capacity: %d cubic feet", self.cargoCapacityCubicFeet];
    
    //Create the final string by combining basic and truck-specific details.
    NSString *truckDetails = [basicDetails stringByAppendingString:truckDetailsBuilder];
    
    return truckDetails;    
} 

Now that you’ve got your Truck object set up, you can create a few instances of it. Head back to VehicleListTableViewController.m and add the following import to the top of the file so that it knows about the existence of the Truck class:

#import "Truck.h"

Find the setupVehicleArray method and add the following code right above where you sort the array:

    //Create a truck
    Truck *silverado = [[Truck alloc] init];
    silverado.brandName = @"Chevrolet";
    silverado.modelName = @"Silverado";
    silverado.modelYear = 2011;
    silverado.numberOfWheels = 4;
    silverado.cargoCapacityCubicFeet = 53;
    silverado.powerSource = @"gas engine";
    
    //Add it to the array
    [self.vehicles addObject:silverado];
    
    //Create another truck
    Truck *eighteenWheeler = [[Truck alloc] init];
    eighteenWheeler.brandName = @"Peterbilt";
    eighteenWheeler.modelName = @"579";
    eighteenWheeler.modelYear = 2013;
    eighteenWheeler.numberOfWheels = 18;
    eighteenWheeler.cargoCapacityCubicFeet = 408;
    eighteenWheeler.powerSource = @"diesel engine";
    
    //Add it to the array
    [self.vehicles addObject:eighteenWheeler];

This will create a couple of Truck objects with the truck-specific properties to join the cars and motorcycles in the array.

Build and run you application; select one of the Trucks to verify that you can now see the appropriate Truck-specific details, as demonstrated below:

Truck-specific Details

That looks pretty great! The truck details are coming through thanks to the vehicleDetailsString method, inheritance, and overridden implementations.

Contributors

Over 300 content creators. Join our team.