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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Intro to Object-Oriented Design: Part 1/2
40 mins
- Getting Started
- The Basics Of Objects
- A Minor Digression: Under The Hood With Properties
- Describing the Object
- A Minor Digression: Class Methods vs. Instance Methods
- Adding Basic Methods to Your Class
- Inheritance
- Overriding Methods
- Building out the User Interface
- Hooking Your Data to Your View
- Model-View-Controller Encapsulation of Logic
- Creating Subclasses via Inheritance
- Housing Logic in the Model Class
- Where To Go From Here?
Hooking Your Data to Your View
To hook up the data, update configureView
in VehicleDetailViewController.m to take advantage of the vehicle set by the segue as follows:
- (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];
//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.detailVehicle.brandName];
[basicDetailsString appendFormat:@"Model name: %@\n", self.detailVehicle.modelName];
[basicDetailsString appendFormat:@"Model year: %d\n", self.detailVehicle.modelYear];
[basicDetailsString appendFormat:@"Power source: %@\n", self.detailVehicle.powerSource];
[basicDetailsString appendFormat:@"# of wheels: %d", self.detailVehicle.numberOfWheels];
self.vehicleDetailsLabel.text = basicDetailsString;
}
}
Build and run your application; select a vehicle from the TableView and you'll see that the detail view is now showing the correct title and details, as so:
Model-View-Controller Encapsulation of Logic
iOS and many other modern programming languages have a design pattern known as Model-View-Controller , or MVC for short.
The idea behind MVC is that views should only care about how they are presented, models should only care about their data, and controllers should work to marry the two without necessarily knowing too much about their internal structure.
The biggest benefit to the MVC pattern is making sure that if your data model changes, you only have to make changes once.
One of the biggest rookie mistakes that programmers make is putting far too much of the logic in their UIViewController classes. This ties views and UIViewControllers too closely to one particular type of model, making it harder to reuse views to display different kinds of details.
Why would you want to do implement the MVC pattern in your app? Well, imagine that you wanted to add more specific details about a car to VehicleDetailViewController. You could start by going back into configureView and adding some information specifically about the car, as so:
//Car-specific details
[basicDetailsString appendString:@"\n\nCar-Specific Details:\n\n"];
[basicDetailsString appendFormat:@"Number of doors: %d", self.detailVehicle.numberOfDoors];
But you’ll notice that there’s one minor problem with this:
VehicleDetailsViewController only knows about the properties of the main Vehicle superclass; it doesn't know anything anything about the Car subclass.
There are a couple of ways you can fix this.
The one that seems the most immediately obvious is to just import Car.h, so that VehicleDetailViewController knows about the properties of the Car subclass. But that would mean having to add a ton of logic to handle all the properties for every single subclass.
Any time you catch yourself doing that, ask yourself: "is my view controller trying to do too much?"
In this case, the answer is yes. You can take advantage of inheritance to use the same method to supply the string to be displayed for the appropriate details for each subclass.
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:
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:
Tap on one of them, and you'll be taken to the details for that Motorcycle, as shown below:
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! :]