Cocoa for Scientists (Part VI): Jekyll and Hyde
Author: Drew McCormack
Website: http://www.macanics.net
This marks the last part of the ‘theory’ component of our foray into Cocoa programming. It will address one of the finer points of Object-Oriented Programming (OOP): polymorphism. In the next tutorial, we will finally make our way into Xcode, and start developing ‘real’ apps with ‘real’ graphical user interfaces. It’s taken a while to get there, but you need a certain amount of background before you can begin tackling the fun stuff, and — after this tutorial — that background should be in place.
The Doctor and The Gentleman
If you’ve ever read Dr Jekyll and Mr Hyde — or seen the movie — you already have some idea of what polymorphism is: Polymorphism is about something changing in time. The Incredible Hulk, for example, only becomes a toxic green brute when the situation demands it, and Superman is also quite adaptive. Both exhibit symptoms of polymorphic behavior.
In OOP, polymorphism generally refers to the ability of one variable to represent different classes of objects at different times. Each programming language generally has its own object model, so how polymorphism is manifested varies somewhat from one language to the next, but there are also many cross-language similarities.
Filling in for Your Granddad
A form of polymorphism that you will encounter in all OOP languages is the ability of objects of a given class to stand in for objects of its superclass (or some other ancestor). This is possible, because — as we learned last time — a subclass inherits all of the instance variables and methods of its ancestor classes. Anything an ancestor class object can do, the descendent can also do, so the descendent can always stand in for one of its ancestors. This form of polymorphism is closely linked to inheritance, and is the most commonly-used form.
Let’s make things a bit more concrete by returning to the example we had last time. Firstly, we’ll restructure the classes a little by moving the numberOfSubExpressions method up to the Operator class where it belongs. Then we’ll add a new method called floatValue, which will return the result of evaluating the operator. Let’s start with the Operator.h header file:
#import <Foundation/Foundation.h>
@interface Operator : NSObject {
NSArray *subExpressions;
}
-(id)initWithSubExpressions:(NSArray *)newSubExprs;
-(unsigned)numberOfSubExpressions;
@end
@interface Operator (Abstract)
-(float)floatValue;
@end
You’ll notice that this class has a category called ‘Abstract’. We met categories last time, in relation to private methods. You will recall that categories allow you to add methods to existing classes, or even reimplement an existing method, even if you didn’t write the original class. In this case, a category has been used to declare abstract methods. So what is an abstract method? Let’s take a look at the implementation of Operator (which should be added to the file Operator.m) and find out:
#import "Operator.h"
@implementation Operator
-(id)initWithSubExpressions:(NSArray *)newSubExprs {
if ( self = [super init] ) {
subExpressions = [newSubExprs retain];
}
return self;
}
-(void)dealloc {
[subExpressions release];
[super dealloc];
}
-(unsigned)numberOfSubExpressions {
return [subExpressions count];
}
@end
You’ll notice that there is no floatValue method in the Operator class implementation. An abstract method is a method that has no implementation … at least not in the immediate class. The idea is that the Operator class represents an abstract concept, rather than anything concrete. A general operator should be able to evaluate itself, hence the declaration of an floatValue method, but there is no sensible implementation of floatValue for the class Operator, so we don’t supply one. Operator is said to be an abstract class, because it includes one or more abstract methods. You can never create and use an object of an abstract class, because it isn’t complete: it doesn’t have all of its methods implemented.
Of course, there are implementations of the method floatValue, they just don’t reside in the abstract Operator class. The subclasses of Operator supply various implementations of the method. Let’s take a look at the interface of the AdditionOperator class.
#import <Foundation/Foundation.h>
#import "Operator.h"
@interface AdditionOperator : Operator {
}
@end
Note that neither the floatValue method, nor any other methods from Operator, are redeclared. It is not necessary to redeclare the methods, though you can if you wish. The implementation of AdditionOperator has the magic sauce, the definition of floatValue:
#import "AdditionOperator.h"
@implementation AdditionOperator
-(float)floatValue {
// Evaluate leftVal + rightVal
float leftVal = [[subExpressions objectAtIndex:0] floatValue];
float rightVal = [[subExpressions objectAtIndex:1] floatValue];
return leftVal + rightVal;
}
@end
An AdditionOperator is charged with adding numbers together, so the implementation simply does that, getting the floating point values of the two subexpressions, and adding them up. The AdditionOperator is not an abstract class, but a concrete class, because it implements all of its methods, including the ones declared in ancestor classes like Operator.
Now we get to the real crux of polymorphism. To demonstrate, we will write a function that evaluates an Operator, and prints the result. Here it is, along with a main program that sets up an operator and calls the function.
#import <Foundation/Foundation.h>
#import "Operator.h"
#import "AdditionOperator.h"
void OutputOperatorValue(Operator *operator) {
float operatorValue = [operator floatValue];
NSLog(@"The operator's value was %f", operatorValue);
}
int main() {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
AdditionOperator *additionOperator =
[[AdditionOperator alloc] initWithSubExpressions:
[NSArray arrayWithObjects:
[NSNumber numberWithFloat:5.0],
[NSNumber numberWithFloat:2.0],
nil]];
OutputOperatorValue(additionOperator);
[additionOperator release];
[pool release];
return 0;
}
What’s significant about this code is that the OutputOperatorValue knows nothing about the AdditionOperator class, and yet it is being called with an AdditionOperator object as argument. This is what is meant by a descendent object substituting for one of its ancestors. The OutputOperatorValue function works with any object descended from the class Operator, because any object descended from Operator will have a floatValue method.
This may not seem so significant at first, but it is very important, and one of the strengths of OOP. Because OutputOperatorValue has no knowledge of the actual class of object it is working with, you can extend the program in future with new types of operators (eg SubtractionOperator, MultiplicationOperator, etc) — all descended from Operator — and the OutputOperatorValue will continue to work unchanged.
In practice, this means that well-designed OO programs can be extended with very minor changes to the existing code base. Adding new functionality to procedural programs typically requires wholesale changes to many places in the existing code, making extensions more difficult to implement, and possibly leading to bugs in what was previously working code.
If you want to compile the example above, you should add the source code to the files Operator.h, Operator.m, AdditionOperator.h, AdditionOperator.m, and main.m. Then issue this command
gcc -ObjC -o add *.m -framework Foundation
To run the program, just launch the add executable
./add
The output should look like this
2007-01-15 09:36:56.901 add[3502] The operator's value was 7.000000
Protocols for Polymorphism
Polymorphism via inheritance is the most common form of polymorphism, and it is supported in every OO language. There are other types though, such as polymorphism via protocols, which are more specific to Objective-C.
We met protocols last time. Because they declare methods that a class must implement, without providing any implementation of those methods, they are actually quite similar to abstract classes. To demonstrate, imagine that Operator were a protocol, rather than a class. It might be declared like this
@protocol Operator
-(float)floatValue;
@end
The AdditionOperator class would then be reimplemented, and conform to the Operator protocol. Without rewriting the example completely, here is how the interface block of AdditionOperator might look:
@interface AdditionOperator : NSObject <Operator> {
NSArray *subExpressions;
}
...
@end
So AdditionOperator now inherits directly from NSObject, but conforms to Operator, which means it must supply a floatValue method.
Polymorphism is achieved by writing code that makes use of the protocol, in the same way that the abstract class was used before:
void OutputOperatorValue(NSObject <Operator> *operator) {
float operatorValue = [operator floatValue];
NSLog(@"The operator's value was %f", operatorValue);
}
This new implementation of OutputOperatorValue will work with any object that conforms to the Operator protocol, regardless of its class.
Dynamic vs Static Polymorphism
As I’ve tried to bring home to you, there are actually many different forms and models of polymorphism. Objective-C is a so called dynamic language, which basically means the types of objects are only properly checked at run-time. Other dynamic languages include scripting languages like Python and Ruby. Static languages, like Java, Fortran, and C++, do more compile time type-checking. There are advantages and disadvantages to each approach, but most Objective-C programmers will tell you that dynamic typing is extremely powerful and flexible, and that they never want to use a statically-typed language again. Who am I to argue?
What does this mean in practice? In a language like C++, every variable must be assigned a class at compile time, and only methods belonging to that class can be invoked. In Objective-C, you can actually send any message to any object, regardless of its declared type. The compiler may issue a warning if it cannot find the method in question, but the code will still compile, and run. If you send a message that the object does not respond to, you get a run-time exception.
In Objective-C, you can also use the id type to represent any object. For example, you could rewrite the main function from earlier like this
int main() {
id pool = [NSAutoreleasePool new];
id additionOperator =
[[AdditionOperator alloc] initWithSubExpressions:
[NSArray arrayWithObjects:
[NSNumber numberWithFloat:5.0],
[NSNumber numberWithFloat:2.0],
nil]];
OutputOperatorValue(additionOperator);
[additionOperator release];
[pool release];
return 0;
}
In this example, the NSAutoreleasePool and AdditionOperator objects have just been assigned to variables of the type id. id is the type of a generic object, and can be used anywhere that an object is expected. Code like this is not possible — or at least very difficult to write — in statically-typed languages.
So why do we use static types in Objective-C at all? Static types are optional for objects in Objective-C, but still a good idea. If you can say something about the type of an object in your code, the compiler can do some compile time checking, and provide warnings for mistakes you may have made. Concrete types in your code also document to other developers how your code is intended to be used.
Informal Protocols and Delegation
One of the benefits of dynamic typing is the ability to have informal protocols. These are categories that define methods that an object may or may not answer to. Coupled with the ability to query the properties of a given class or object at run time — known in OOP as introspection — informal protocols are very powerful, and used extensively in Cocoa programming.
One example of informal protocol use is in delegation. Delegation is a design pattern that allows one object to extend the functionality of another object, without being related by inheritance. This leads to very loose coupling, which is desirable in program design.
Take the NSApplication class, for example. This is a class that is used in virtually every Cocoa app, and is responsible for start up, opening files, and things like that. NSApplication has a delegate, which can be set to any object like this:
id appDelegate = [AppDelegate new];
[[NSApplication sharedApplication] setDelegate:appDelegate];
The NSApplication header file includes a category that declares a number of methods that can optionally be implemented by the delegate. If the methods are implemented, they are invoked at the appropriate time. The category itself is not actually attached to the NSApplication class, though it is declared in NSApplication.h; it is actually a category of NSObject, because the delegate methods are implemented by the delegate object — which could be of any class descended from NSObject — not the NSApplication. Here is a little bit of the NSApplication header to give you a flavor of how the delegate methods are declared:
@interface NSObject(NSApplicationDelegate)
...
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
- (BOOL)application:(id)sender openFileWithoutUI:(NSString *)filename;
...
@end
If you write the AppDelegate class, and include a method called applicationShouldOpenUntitledFile:, and then set an AppDelegate object as the NSApplication delegate, the method will get invoked whenever the application is about to open an untitled file. The delegate can take any special action required, and can even stop the application opening the file by returning NO. All of this is possible without NSApplication having any explicit knowledge of the delegate’s class. This is simply not possible in a statically-typed language.
How does NSApplication achieve this? It uses introspection. When it is ready to open an untitled, it will execute code similar to this:
BOOL openUntitled = YES;
if ( nil != [self delegate] &&
[[self delegate] respondsToSelector:@selector(applicationShouldOpenUntitledFile:)] ) {
openUntitled = [[self delegate] applicationShouldOpenUntitledFile:self];
}
if ( openUntitled )
... // Open the untitled file
It checks first that the delegate has been set, and then uses the NSObject method respondsToSelector: to find out if the delegate actually implements applicationShouldOpenUntitledFile:. A selector is the internal representation of a method’s name — basically, a glorified string — and the @selector keyword is used to get that representation. If the delegate has been set, and it implements applicationShouldOpenUntitledFile:, NSApplication invokes the method, and then tests the return value to decide whether it needs to open the untitled file or not.
Polymorphism vs Branching
Polymorphism is one of the most powerful aspects of OOP, but also one of the most difficult to grasp for newbies. You really need to practice OOP yourself for a while to really grok it. But it can help your understanding if you realize what polymorphism is supplanting in the procedural programming model.
Polymorphism can be seen as a means of storing branching decisions. For example, in a procedural language like Fortran 90, the Operator type may be implemented something like this.
module OperatorMod
integer, parameter :: AdditionOperatorType = 1
integer, parameter :: MultiplicationOperatorType = 2
type Operator
integer :: operatorType
real, pointer :: subExpressions(:)
end type
contains
real function floatValue(op)
type (Operator) :: op
select case (op%operatorType)
case (AdditionOperatorType)
floatValue = op%subExpressions(1) + op%subExpressions(2)
case (MultiplicationOperatorType)
floatValue = op%subExpressions(1) * op%subExpressions(2)
end select
end function
end module
The floatValue function now includes a branching block like select, switch, or if. Depending on the complexity of the type, there may be many of these branches, and many may be very similar in structure, if not the same.
The problem with this duplication of branching structure is that in order to add a new option, such as a new operator, you have to track down all of the relevant branching blocks in the code, and modify them. This takes time, and increases the likelihood of introducing bugs.
OOP replaces these large branching statements with an inheritance hierarchy. When you initialize a new object, such as an AdditionOperator, you are effectively choosing one of the branches shown in the procedural code, and storing that decision — each class represents a different branch. The advantage of this is that when you need to extend the application, you simply add a new class, and leave the existing code unchanged. Writing a new class is equivalent to adding a new branch to all the relevant branching statements in the procedural code, but is easier to achieve, and less risky.
Enough Theory … Bring on the Buttons!
Don’t worry too much if you are struggling to grasp the implications of polymorphism. The more you use it, the more you will understand and appreciate it, and there will be plenty of opportunities to practice in the months to come.
Next time, we’ll move into Xcode, bidding the command line a fond farewell. Until then, stay polymorphic!



Comments
Feeling polymorphic..
Polymorphism seems an important and central issue, but why is it so difficult to understand? Thank you for trying to put things into perspective. Hopefully the upcoming tutorials will illustrate this and make things even clearer.
Learning Polymorphism
Polymorphism is definitely very important, and definitely requires time to get used to. If you follow the examples in the rest of the course, I'm sure it will become clear to you, and you will realize why it is so useful.
Drew
---------------------------
Drew McCormack
http://www.macanics.net
http://www.macresearch.org
protocols
hi drew, thanks for "convenience initializers"
a tiny question here. by making Operator a protocol, i move all the methods previously in Operator to AdditionOperator. so where do i put the declaration for Operator?
will it be in just a separate Operator.h file? and if so, do i have to import it into the classes (class.h) that conform to it or just importing it in main.m will suffice?
Protocols
I think it is best just to make a separate Operator.h file. You will have to import it into any conforming class header, because it is part of the interface declaration:
#import "Operator.h" @interface AdditionOperator : NSObject <Operator> { ... } ... @end---------------------------
Drew McCormack
http://www.macanics.net
http://www.macresearch.org