Explaining the term "polymorphism" is common question in job interviews, exam questions and books on object-oriented programming and design. I'm bothered by this because the term is so abstract as to be worthless. There is no implementation that is best described as "polymorphic" — actual implementations always use more specific terms, leading to a situation where "polymorphism" is only ever used in theory and only serves to obfuscate and confuse. In this post, I'll look at why I think the term is too vague and which more specific terms are typically used instead.
polymorphism (noun) theory, programming
A term used to describe a variable that may refer to objects whose class is not known at compile time and which respond at run time according to the actual class of the object to which they refer.The Free On-line Dictionary of Computing
If you've ever studied object-oriented design, then you have probably noticed that one of the first definitions taught is that of "polymorphism". Perhaps you think this is reasonable; afterall, polymorphism can be used to explain many traits of object-oriented languages.
For example, in Objective-C code where a single message is sent to an object, any object which responds to the message's selector can be exchanged for any other object that responds to that selector at runtime. The completely dynamic nature of Objective-C method lookup means that Objective-C is one of the most "polymorphic" of all compiled languages.
Programmers don't use the term
Given the broad scope for potential use, it may seem strange that few programmers ever really use the term "polymorphism". In fact, if you ever hear a programmer using the term, they're likely only doing it to try to sound like an execu-speak-bot. "We'll proactively synergize our existing product with technologies leveraging polymorphism!"
The reality is that "polymorphism" is never the most accurate description of why something is done. Since there is no single case where it is the best description, it ends up never getting used. Instead, the terms that programmers use are the terms that describe actual implementations.
Polymorphism is an ontological classification of a handful of different programming techniques that may substitute one type for another. But type substitution is never the end-goal in these situations, instead the type substitution is an artefact of an implementation that is trying to achieve a specific design or behavioral goal that is not itself correctly called polymorphism.
Let's look at some of these design goals and see the actual terms used to describe them.
Method overridingThe canonical example of polymorphism given by most textbooks goes by another, far more common name: method overriding.
MyBaseClass *newObject = [[[MySubClass alloc] init] autorelease]; [newObject instanceMethodDeclaredInMyBaseClass];
In this example, when
instanceMethodDeclaredInMyBaseClass is invoked, it could be the base implementation or it could be an override defined in the subclass — the distinction has no impact on how we invoke the method nor does it impact on the type of the variable
newObject. The only determining factor is whether the
MySubClass class overrides this method from the base class.
No one would ever describe this by saying: "here we polymorphically invoke the base or subclass method".
Instead, we always describe this more accurately: "here we invoke a method which the subclass may have overridden".
An alternate implementation of a method on a subclass so that instances of the subclass may substitute different behaviors for the method relative to the base class.
An alternate implementation of the interface of a base class. The subclass represents a more specific version of the more general base class by adding extra data and methods to the definition but retaining all data and methods from the base class.
By retaining the same data and set of methods, a subclass object is normally interchangeable with a base class object and can be indistinguishable without runtime type information.
Since subclasses and method overrides are so fundamental to object oriented design and implementation, these terms are the most informative and most familiar to all relevant audiences. Sometimes extending, deriving or (more broadly) inheritance can be used to describe the situation (depending on why the subclass was created and how the sentence is worded) but again, these are terms related to the design principles involved.
The term polymorphism implies that we might have switched an unrelated object into the place of the expected type. That isn't what inheritance hierarchies do. While there is more to newObject than just MyBaseClass, it is also a MyBaseClass instance. This is why the following code:
[newObject isKindOfClass:[MyBaseClass class]]
returns YES if object is a MyClass or a MySubClass. MySubClass has all the same data as MyClass and all the same methods.
Yes, there is a mechanism that resolves a message sent to an object and invokes the correct implementation for the specific runtime type of the object. In Objective-C, this is the "messaging system". The equivalent in C++ is the vtable. You will never hear anyone call this the "polymorphism system" or the "polymorphic abstraction layer".
A collection of objects, where the type of the object is unimportant
In Objective-C, objects of type id are frequently used by collections classes (NSArray, NSDictionary, NSSet, etc) because these collection classes don't care what objects they are handling.
NSArray *array = [NSArray arrayWithObjects:someObject, someOtherObject, nil];
This usage does count as "polymorphism" because the exact class of someObject and someOtherObject doesn't need to be known at compile-time.
Most of the time, a programmer will simply say: "NSArray takes objects of type 'id'", and leave it at that. This is literally what happens; it is the best description.
If pressed for a more abstract description, the best term is actually:
A style of computer programs where a part of the program is left variable or unknown, to be filled in at a later date.
Generic programming states that the code has been written to allow any object or, in the case of
NSArray, any "first class" or
NSObject. In contrast to the implied polymorphic case where the code is written to expect type "X" and you are expected to provide something that behaves like type "X", generic programming implies that the code doesn't care what type it is given and won't place any significant requirements on the type.
Using the term "generic" also relates this useage to other languages and setups where the generic use isn't polymorphic. For example, collections programming in C++ for the previous example might look like this:
std::vector<MyClass> array; array.push_back(someMyClassObject); array.push_back(someOtherMyClassObject);
This is still called "generic" programming. The definition of Class std::vector<T> doesn't know about MyClass, yet it's not really polymorphic because the type is known at compile time. C++ generic programming gets the type when the object is declared not when the Class is defined.
There is a final polymorphic case in Objective-C. There are extremely rare situations in Objective-C where an object of one class might be substituted with an object of an unrelated class. This isn't easily possible in less dynamic languages like C++ but the dynamic binding of selectors to method implementations in Objective-C permits some unique design solutions.
Examples of this type of solution include:
NSProxy, which receives messages on behalf of another object at the other end of an NSConnection.
NSKeyValueObservingconstructed classes which intercept messages to Observed objects through "isa" swizzling so that NSKeyValueObserving notifications can be sent when data setting messages are received.
- Invoking a specific method, with no regard for the class or type. In Objective-C, this includes most informal protocols, including many
In these cases, the Objective-C messaging system is being extended. In the
NSProxy case, the messaging system is extended to send messages to a remote location. In the
NSKeyValueObserving case, it is extended to perform extra work when certain messages are received.
Since these cases involve the substitution of an unrelated class, they are the most deserving of the term of all the cases explored so far. Scour the documentation on them however, and you'll find that they are never described as such.
The reality is that these classes represent very special cases in programming. Due to their peculiarities, it is important to be specific about the design philosophy employed.
an object of one class functioning as an interface to a different object, possibly of a different class
NSProxy explicitly notes its design approach in its name. Specifically,
NSProxy is a "Remote Proxy".
NSKeyValueObserving constructed classes are always noted as "isa" swizzling — an Objective-C specific implementation approach used specifically to extend the messaging system. In essence, this approach wraps the observed class in another class which sends
NSKeyValueObserving notifications when appropriate before invoking the underlying class' methods.
While this could be argued as being a "smart proxy" (a proxy object which performs tracking actions as references or messages to the underlying object are made or sent), wrapping objects which add runtime behavior are normally called "Decorators".
an object of one class wrapping an object of another class at runtime, where the outer object exposes the same interface as the inner object.
This wrapping is done to introduce additional functionality to the inner object's interface.
A final type of unrelated object usage are methods sent to an object, regardless of the type. We can do this safely in Objective-C by asking an object if it handles a specific message (using
respondsToSelector:) and only send the message if the object will handle it.
This type of introspective method invoking falls under the larger banner of duck typing.
duck typing (dynamic typing)
a situation where you send a message to an object of unknown type, but where you assuming the object will handle the message, and let the object decide how to respond to the message in its own way. From the informal test: if it looks like a duck and quacks like a duck, then it is a duck.
Duck typing revolves around the idea that an object need not have a type. Instead, objects need only claim to adhere to a certain model. Once an object claims to adhere to a model, then you can operate with it within the bounds of that model.
Often with duck typing, the fact that the programmer has used a variable in a certain context means that the programmer is asserting it will work within that context. In Objective-C, this approach was typically described using informal protocols (declared categories on
NSObject that had no generic implementation) and more recently by optional protocols.
Polymorphism's use as a term is fundamentally limited by how vague it is. It does not describe a particular model or a particular implementation; instead, every implementation which could fall under the greater banner of polymorphism has a description which is more specific and better understood because of this.
The reality is that there is no real situation where the term "polymorphism" aids understanding of an implementation. Given this, the term has no real value to programmers.
Interviewers and examiners: please ask relevant questions instead; stop torturing already stressed programmers by asking them to explain purely abstract terms that they will never need to use. Authors: please use relevant terminology in books. Certainly don't begin explaining the term "object oriented" to beginners by first describing "polymorphism" as the fundamental underlying mode of operation. Programming theory is dry and dull enough already.