Bookmark and Share

Objective-C Programming Tutorial


SoBackSanA


Chapter 3. Polymorphism and Dynamic Binding


3.1 Polymorphism

This tutorial is written assuming you are already familiar with Object Oriented Programming either from C++ or Java.
So, I am not going to dive into the details of OO.

Objects of the derived class use base-class methods without changing them. However, sometimes we face the situations in which we want a method to behave differently for the derived class than it does for the base class. That is, the way a particular method behaves may depend on the object that invokes it. In other words, the code which is executed when a message is sent to an object depends on both the receiver's class and the name of the method in the message. In traditional procedural languages, the code which is executed by a function call is determined by the name of the function only. This more sophisticated behavior is called "polymorphic" because you can have multiple behaviors for a method depending on the context.



The example below demonstrates how an object from a different class knows which method to use.
In this example, we have two methods with same names: "print" and "add:".


@Interface: <Vector.h>

#import <Foundation/Foundation.h>

@interface Vector: NSObject 
{ 
      double vec1;
      double vec2;
} 

@property double vec1, vec2;
-(void) print;
-(void) setVec1: (double) v1 andVec2: (double) v2;
-(Vector *) add: (Vector *) v;
@end 

@Implementation: <Vector.m>

#import "Vector.h"

@implementation Vector 

@synthesize vec1, vec2;

-(void) setVec1: (double) v1 andVec2: (double) v2
{ 
	vec1 = v1;
	vec2 = v2;
} 

-(Vector *) add: (Vector *) v
{
      Vector *result = [[Vector alloc] init];
      [result setVec1: vec1 + [v vec1] 
              andVec2: vec2 + [v vec2]];
      return result;
}

-(void) print
{
     NSLog(@"(%g,%g)",vec1,vec2);
}

@end

@Interface: <Scalar.h>

#import <Foundation/Foundation.h>

@interface Scalar: NSObject 
{ 
	double scal; 
} 

@property double scal;
-(void) print;
-(void) setScal: (double) sval;
-(Scalar *) add: (Scalar *) s;
@end 

@Implementation: <Scalar.m>

#import "Scalar.h"

@implementation Scalar 

@synthesize scal;

-(void) setScal: (double) s
{ 
	scal = s;
} 

-(Scalar *) add: (Scalar *) s
{
      Scalar *result = [[Scalar alloc] init];
      [result setScal: scal + [s scal]];
      return result;
}

-(void) print
{
      NSLog(@"%g",scal);
}
@end

The main program: <main.m>

#import "Vector.h"
#import "Scalar.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) 
{	
      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
      Scalar *scA =[[Scalar alloc] init];
      Scalar *scB =[[Scalar alloc] init];
      Scalar *scAandB =[[Scalar alloc] init];
      Vector *vecA =[[Vector alloc] init];	
      Vector *vecB =[[Vector alloc] init];
      Vector *vecAandB =[[Vector alloc] init];
	
      //set the values
      [scA setScal: 10.5];
      [scB setScal: 13.1];
      [vecA setVec1: 3.2 andVec2: 4.7];
      [vecB setVec1: 32.2 andVec2: 47.7];
	
      // print it
      [vecA print];
      NSLog(@" + ");
      [vecB print];
      NSLog(@" = ");
      vecAandB = [vecA add: vecB];
      [vecAandB print];

      [scA print];
      NSLog(@" + ");
      [scB print];
      NSLog(@" = ");
      scAandB = [scA add: scB];
      [scAandB print];

	
      // free memory
      [scA release];
      [scB release];
      [scAandB release];
      [vecA release];
      [vecB release];
      [vecAandB release];
	
      [pool drain];
      return 0;		
}

The output is:

(3.2,4.7)
 +
(32.2,47.7)
 =
(35.4,52.4)
10.5
 +
13.1
 =
23.6

Both the Vector and Scalar classes have "print" and "add:" methods. How does the system tell which methods to be executed when the system processes following messages?

      vecAandB = [vecA add: vecB];
      [vecAandB print];

That's because the Objective-C knows vecA, the receiver of the first message, is an object of Vector. So, it chooses the "add:" method from Vector class not from Scalar class. Same thing happens to the "print" method.



3.2 Dynamic Binding(Late Binding): id Data Type

Which block of excutable code gets executed when a program calls a function? The compilier has to answer this question. Interpreting a function call in the source code as executing a particular block of function code is termed "binding" the function name. Binding that takes place during compilation is called "static binding" or "early binding". But there are cases when the compiler doesn't know which object the user is going to choose. So, the compiler has to generate code that allows the correct method to be selected as the program runs. This is called "dynamic binding" or "late binding".

The "id" data type is used to store an object of nay type. It is a generic object type. The follwing example shows the real power of "id" data type when it is used to store different types of objects during the execution.


The main program using "id" data type: <idDataType.m>

#import "Vector.h"
#import "Scalar.h"
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) 
{	
      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
      id anyObj;
      Scalar *scA =[[Scalar alloc] init];
      Vector *vecA =[[Vector alloc] init];	
	
      //set the values
      [scA setScal: 10.5];
      [vecA setVec1: 3.2 andVec2: 4.7];
	
      anyObj = scA;
      [anyObj print];

      anyObj = vecA;
      [anyObj print];
	
      // free memory
      [scA release];
      [vecA release];

      [pool drain];
      return 0;		
}

The output is:

10.5
(3.2,4.7)

The variable "anyObj" is declared as an "id" object type. So, "anyObj" can be used to hold any type of object.

The asignment "anyObj = vecA" stores the "Vector" instance variable "vecA". What can you do with "anyObj"? You can call any of the methods that you can use on a "Vector" object with "anyObj", even though the type of "anyObj" is an "id" and not a "Vector". However, if "anyObj" can store any type of object, how does the system know which method to invoke? That is when it sees the message:

      [anyObj print];
How does it know which "print" method to use? During execution of the program, before the system sends the "print" message to "anyObj", it first checks the class of the object stored by "anyObj". In our case, this variable contains a "Vector", so the "print" method defined in the "Vector" class is used.


SoBackSanB