Polymorphic methods


#1

Hi there,

Having an awesome time reading the book so far!

I came across something that I’m hoping some more experienced Obj-C developers can help me with. I’ve run into a weird situation and I can’t quite figure out why the behavior I’m seeing is happening. I’ve got the following classes (renamed here to simplify things):

@interface Animal @property (nonatomic, copy) NSString *attributeCommonToAllAnimals; @end

@interface Dog : Animal @property (nonatomic, copy) NSString *somethingSpecificToDogs; @end

@interface Cat : Animal @property (nonatomic, copy) NSString *somethingSpecificToCats; @end

@interface Bird : Animal @property (nonatomic, copy) NSString *somethingSpecificToBirds; @end

I’ve then got the following method:

[code]- (Animal *)returnAnAnimal{
Animal *animal = [[Animal alloc] init];

if ( someCondition ){
    animal = (Dog *)[Dog new];
    animal.somethingSpecificToDogs = true;
} else if ( someOtherCondition ){
    animal = (Cat *)[Cat new];
    animal.somethingSpecificToCats = true;
} else {
    animal = (Bird *)[Bird new];
    animal.somethingSpecificToBird = true;
}

return animal;

}
[/code]

For some reason, any of the subclass-specific properties are not accessible from within the method. When I try to set those values (ex: animal.somethingSpecificToDogs), Xcode complains with the following:

Is there something I’m not doing properly with respect to the inheritance chain, or perhaps in how I’m declaring my return type on the method?

Any advice is appreciated.

Thanks!


#2

First of all, this is not related to the problem you are experiencing, but it is better to make your Animal class a subclass of NSObject:

@interface Animal : NSObject
@property (nonatomic, copy) NSString *attributeCommonToAllAnimals;
@end

As for the problem:

...
animal = (Dog *)[Dog new];
animal.somethingSpecificToDogs = true;   // Error
...

The second statement is not valid semantically because the Animal class does not declare a property named somethingSpecificToDogs.

You can fix the problem by simply assigning each object to a variable specifically typed as the object’s class.

- (Animal *)returnAnAnimal{
    Animal * animal = [[Animal alloc] init];

    if (someCondition) {
        Dog * dog = [Dog new];
        dog.somethingSpecificToDogs = true;
        animal = dog; // or return dog;
    }
    ...
    return animal;
}

Key point: A dog is an Animal, but an Animal may not be a Dog.

Also, as an aside, for properties or methods to be polymorphic it is necessary that they have the same signature.

@interface

[Become a competent programmer: pretty-function.org]


#3

First of all, this is not related to the problem you are experiencing, but it is better to make your Animal class a subclass of NSObject:

@interface Animal : NSObject
@property (nonatomic, copy) NSString *attributeCommonToAllAnimals;
@end

[/quote]

My fault for not including that. It does actually extend NSObject. I missed that when re-typing the code into the discussion.

That’s the part that confuses me… I thought that because I’m assigning a new instance of Dog to the animal pointer, that I would, in fact, have access to the somethingSpecificToDogs property, since it’s declared in the Dog class. I’m still not clear on why that won’t work.

That does work, but for some reason it just feels wrong. Hehehe…

Again, that’s where the confusion lies. I understand that an Animal may not be a Dog, but in the code above, I’m casting (and assigning) an instance of Dog to the animal pointer. Is it because the pointer is initially referencing a different Object type? Or is there another reason for this not being valid?

[quote]
Also, as an aside, for properties or methods to be polymorphic it is necessary that they have the same signature.
@interface
[Become a competent programmer: pretty-function.org][/quote]

You’re right in that the Dog and Animal class don’t have the exact same signature. Maybe polymorphic was the wrong choice to describe the problem. Is there a better term you would use to describe a method that returns a (subclass) object of a certain class-type?

Thanks for your response. Much appreciated!


#4

It feels wrong, but it is correct semantically. (I am sure you know the difference between syntax and semantics, but in a nutshell it is form versus meaning.)

Pointer is not referencing a different object type, because a Dog is still an Animal but a more specific animal. The reason is purely semantics: it would be incorrect to treat an Animal which is not a Dog as a Dog. For example, unless a Dog is highly talented, it can not make a chirping sound as a Cricket can.

Even if you know that you have casted a Dog to an Animal, the compiler does not care about that and does not even keep a record of it. So when you try to treat an Animal as a Dog, the compiler stops you to prevent a semantical absurdity.

Look at the following example, which uses id instead of Animal to the ask the compiler to relax its type checking and thus to allow semantic absurdities.

#import <Foundation/Foundation.h>

@interface Animal : NSObject

- (void)stopThat;

@end

@interface Dog : Animal

- (void)startBarking;
- (void)startChirping; // What!

@end

@interface Cricket : Animal

- (void)startBarking;    // What!
- (void)startChirping;

@end

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        Dog * dog = [Dog new];
        Cricket * cricket = [Cricket new];
                
        // If we use id instead of Animal,
        // the compiler will stop checking and will let absurdities creep in.
        id animal = dog;  // Animal * animal = dog;
        [animal startBarking];
        [animal startChirping]; // Absurd!
        [animal stopThat];
        
        animal = cricket;
        [animal startBarking];  // Absurd!
        [animal startChirping];
        [animal stopThat];
    }
    return 0;
}

@implementation Animal
@end

@implementation Dog

- (void)startBarking
{
    NSLog (@"%@: Bark, Bark, Bark...", [self className]);
}

- (void)startChirping
{
    NSLog (@"%@: I can't chirp because I am a %@!", [self className], [self className]);
}

- (void)stopThat
{
    NSLog (@"%@: Stopped.", [self className]);
}
@end

@implementation Cricket

- (void)startBarking
{
    NSLog (@"%@: I can't bark because I am a %@!", [self className], [self className]);
}

- (void)startChirping
{
    NSLog (@"%@: Chirp, Chirp, Chirp...", [self className]);
}

- (void)stopThat
{
    NSLog (@"%@: Stopped.", [self className]);
}

@end

A factory method if you have in mind what’s in the following pseudo-code:

class AirCraft {
   Plane makePlane ()
   Helicopter makeHelicopter ()
}

class Plane : AirCraft {
   ...
}

class Helicopter : AirCraft {
   ...
}

#5

You’re right in that it’s semantically correct. I think I just need to wrap my head around it and leave my emotions behind :wink:

I think where my “confusion” lies is simply in how I’m reading the code. When I attempt to instantiate (and cast) an instance of Dog to the animal pointer, my brain reads it as:

“Take the animal variable and re-use it by assigning it this newly-created Dog instance.”

Is it fair to say that because the animal variable was previously pointing to an Animal object, that it can ONLY be used to point to other (true) Animal objects? Or is that not the best way to describe what’s happening here?

[quote]A factory method if you have in mind what’s in the following pseudo-code:

[code]
class AirCraft {
Plane makePlane ()
Helicopter makeHelicopter ()
}

class Plane : AirCraft {

}

class Helicopter : AirCraft {

}
[/code][/quote]

I had thought about using the Factory pattern as well, but in the actual case where I’ve written my code, I don’t know that the factory is the best approach to take. What I was trying to do (in my real code) was to parse an XML response and create a specific instance of an Animal object (ie: Dog, Cat, Bird) by simply specifying a return type of (Animal *). I just assumed that returning an instance of a class that extends Animal was enough to satisfy the contract.

Still not sure I truly understand why it’s not working… but I’ll have to just know that it’s not allowed.

Thanks for your help thus far. I really appreciate you taking the time to answer.


#6

Again using pseudo-code, and the language metaphor, here is the core concept (this is all you need to know):

class Foo {
   // Speaks only Foo language
   ...
}

class FooBar: Foo {
   // A FooBar is also a Foo
   // Speaks Foo and FooBar languages
   ...
}

class JibberJabber: FooBar {
   // A JibberJabber is not only a FooBar but is also a Foo
   // Speaks Foo, FooBar, and JibberJabber languages
   ...
}


JibberJabber jibberJabber = JibberJabber (...)

FooBar fooBar = jibberJabber
// fooBar is still JibberJabber but you can only talk to it in FooBar's language

Foo foo = jibberJabber
// foo is still JibberJabber but you can only talk to it in Foo's language

Foo foo = fooBar
// foo is still JibberJabber but you can only talk to it in Foo's language

if (foo speaks JibberJabber) {
    // YES
    JibberJabber jibberJabber = foo as JibberJabber
}

Foo foo = FooBar (...)
// foo is still FooBar but you can only talk to it in Foo's language

if (foo speaks JibberJabber) {
    // NO
    JibberJabber jibberJabber = foo as JibberJabber
}
else if (foo speaks FooBar) {
    // YES
    FooBar fooBar = foo as FooBar
}

#7

[quote=“ibex10”]Again using pseudo-code, and the language metaphor, here is the core concept (this is all you need to know):

FooBar fooBar = jibberJabber
// fooBar is still JibberJabber but you can only talk to it in FooBar's language

[/quote]

I think I get it… Is this because “jibberJabber” is implicitly cast to a FooBar? If so, my guess for why my original code doesn’t work is because you can cast “up” to the Base/Parent, but you can’t cast down to a subclass-type?


#8

[quote=“frankieshakes”][quote=“ibex10”]Again using pseudo-code, and the language metaphor, here is the core concept (this is all you need to know):

FooBar fooBar = jibberJabber
// fooBar is still JibberJabber but you can only talk to it in FooBar's language

[/quote]
I think I get it… Is this because “jibberJabber” is implicitly cast to a FooBar? If so, my guess for why my original code doesn’t work is because you can cast “up” to the Base/Parent, but you can’t cast down to a subclass-type?[/quote]

Yes.

Yes, but you can still cast down to a subclass instance whenever it makes sense to do so.

class Person {
   ...
   // class method to create an instance of a subclass of this class
   Person personFromInputStream (InputStream)
}

class Student : Person {
    ...
}

class Worker: Person {
    ...
}
...

// Extract Workers and Students from an input stream...
//
InputStream istream = ...

while (istream) {
   Person person = Person.personFromInputStream (istream)

   // What kind of person?
   if (person is Worker) {
        Worker worker = person as Worker
         ...
   }
   else if (person is Student) {
        Student student = person as Student
        ...
   }
   else {
         ...
   }
}