Jump to content

Polymorphism in object-oriented programming

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by 62.101.126.214 (talk) at 11:39, 3 December 2008. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

In simple terms, polymorphism is the ability of one type, A, to appear as and be used like another type, B. In strongly typed languages, this usually means that type A somehow derives from type B, or type A implements an interface that represents type B.

In non-strongly typed languages (dynamically typed languages) types are implicitly polymorphic to the extent they have similar features (fields, methods, operators). In fact, this is one of the principal benefits (and pitfalls) of dynamic typing.

Operator Overloading the numerical operators +,-,/,* allow polymorphic treatment of the various numerical types Integer, UnSigned Integer, Float, Decimal, etc; each of which have different ranges, bit patterns, and representations. Another common example is the use of the "+" operator which allows similar or polymorphic treatment of numbers (addition), strings (concatenation), and lists (attachment). This is a lesser used feature of polymorphism.

The primary usage of polymorphism in industry (object-oriented programming theory) is the ability of objects belonging to different types to respond to method, field, or property calls of the same name, each one according to an appropriate type-specific behavior. The programmer (and the program) does not have to know the exact type of the object in advance, and so the exact behavior is determined at run time (this is called late binding or dynamic binding).

The different objects involved only need to present a compatible interface to the clients (the calling routines). That is, there must be public or internal methods, fields, events, and properties with the same name and the same parameter sets in all the Superclasses, Subclasses, and potentially Interfaces. In principle, the object types may be unrelated, but since they share a common interface, they are often implemented as Subclasses of the same Superclass. Though it is not required, it is understood that the different methods will also produce similar results (for example, returning values of the same type).

How Inheritance Works With Polymorphism

If a Dog is commanded to speak(), this may emit a Bark. However, if a Pig is commanded to speak(), this may emit an Oink. They both inherit speak() from Animal, but their Subclass methods override the methods of the Superclass; this is Overriding Polymorphism and Inheritance. Adding a walk class to Animal would give both Pig and Dog object's the same walk method.

Inheritance combined with Polymorphism means that if class B inherits from class A, it doesn’t have to inherit everything about class A; it can do some of the things that class A does differently. This means that the same “verb” can result in different actions as appropriate for a specific class. Calling code can issue the same command to their Superclass or Interface and get appropriately different results from each one.

Examples

C#

The ideal implementation of polymorphism in c# is to use interfaces stored in a common class API which has no user code dependencies.

// Assembly: Common Classes
// Namespace: CommonClasses

public interface IAnimal
{
    string Name
    { 
         get; 
    }
    string Talk();
}

// Assembly: Animals
// Namespace: Animals

public class AnimalBase
{
    private string _name;
    AnimalBase(string name)
    {
       _name = name;
    }
    public string Name
    {
       get
       {
          return _name;
       }
    }
}

// Assembly: Animals
// Namespace: Animals

public class Cat : AnimalBase, IAnimal
{
    public Cat(String name) :
        base(name)
    {
    }

    public string Talk() {
        return "Meowww!";
    }
}

// Assembly: Animals
// Namespace: Animals

public class Dog : AnimalBase, IAnimal
{
    public Dog(string name) : 
        base(name)
    {
    }

    public string Talk() {
        return "Arf! Arf!";
    }
}

// Assembly: Program
// Namespace: Program
// References and Uses Assemblies: Common Classes, Animals

public class TestAnimals
{
    // prints the following:
    //
    // Missy: Meowww!
    // Mr. Bojangles: Meowww!
    // Lassie: Arf! Arf!
    //
    public static void Main(String[] args)
    {
        List<IAnimal> animals = new List<IAnimal>();
        animals.Add(new Cat("Missy"));
        animals.Add(new Cat("Mr. Bojangles"));
        animals.Add(new Dog("Lassie"));

        foreach(IAnimal animal in animals)
        {
             Console.WriteLine(animal.Name + ": " + animal.Talk());
        }    
    }
}

The reason this is ideal is because then objects can be used in any assembly, regardless of any circular reference issues present. This is a slightly modified implementation of the Strategy Design Pattern. Implementing a base class is not required but is typically useful for code reuse through inheritance.


This example also uses generics, which enable many things, including compile time type safety for collections, and no boxing, unboxing, or casting performance penalties. Combining interfaces, design patterns, and generics creates very flexible, extensible, readable, maintainable, high performance designs. [1]

Python

myString = 'Hello, world!'
myList = [0, 'one', 1, 'two', 3, 'five', 8]

print myString[:5]  # prints Hello
print myList[:5]    # prints [0, 'one', 1, 'two', 3]

print 'e' in myString   # prints True
print 5 in myList       # prints False

However, the most common examples of polymorphism are found in custom classes. Consider the example below, where two subclasses (Cat and Dog) are derived from an Animal superclass. Two Cat objects and one Dog are instantiated and given names, and then they are gathered in an array "animals" and their "talk" method is called.

class Animal:
    def __init__(self, name):    # Constructor of the class
        self.name = name

class Cat(Animal):
    def talk(self):
        return 'Meow!'

class Dog(Animal):
    def talk(self):
        return 'Woof! Woof!'

animals = [Cat('Missy'), 
           Cat('Mr. Bojangles'), 
           Dog('Lassie')]

for animal in animals:
    print animal.name + ': ' + animal.talk()

# prints the following:
#
# Missy: Meow!
# Mr. Bojangles: Meow!
# Lassie: Woof! Woof!

Note that Python makes polymorphism particularly easy to write, since the language is dynamically (and implicitly) typed: a name can be bound to objects of any type (or class) without having to explicitly specify the type, and a list holds mixed type (unlike a C array or a Java array, be it generic or not). Note the inevitable trade-off though: a language that generates fewer compile-time errors tends to generate more run-time errors, requiring explicit (unit) testing.

Dynamic language performance is hindered by the extra checks and searches that occur at each call site. Straightforward implementations have to repeatedly search class precedence lists for members and potentially resolve overloads on method argument types each time you execute a particular line of code. In an expression such as o.m(x, y) or x + y, dynamic languages need to check exactly what kind of object o is, what is m bound to for o, what type x is, what type y is, or what "+" means for the actual runtime type of x and y. In a statically typed language (or with enough type hints in the code and type inferencing), you can emit exactly the instructions or runtime function calls that are appropriate at each call site. You can do this because you know from the static types what is needed at compile time.

Dynamic languages provide great productivity enhancements and powerful terse expressions due to their dynamic capabilities. However, in practice code tends to execute on the same types of objects each time. This means you can improve performance by remembering the results of method searches the first time a section of code executes. For example, with x + y, if x and y are integers the first time that expression executes, we can remember a code sequence or exactly what runtime function performs addition given two integers. Then each time that expression executes, there is no search involved. The code just checks that x and y are integers again, and dispatches to the right code with no searching. The result can literally be reduced to inlined code generation with a couple of type checks and an add instruction, depending on the semantics of an operation and method caching mechanisms used. [2]

C++

#include <iostream>
#include <string>

using namespace std;

class Animal
{
	public:
	Animal(const string& name) : name(name) { }
	virtual const string talk() = 0;
	const string name;
};

class Cat : public Animal
{
	public:
	Cat(const string& name) : Animal(name) { }
	virtual const string talk() { return "Meow!"; }
};

class Dog : public Animal
{
	public:
	Dog(const string& name) : Animal(name) { }
	virtual const string talk() { return "Arf! Arf"; }
};

// prints the following:
//
// Missy: Meow!
// Mr. Bojangles: Meow!
// Lassie: Arf! Arf!
//
int main()
{
	Animal* animals[] =
	{
		new Cat("Missy"),
		new Cat("Mr. Bojangles"),
		new Dog("Lassie")
	};

	for(int i = 0; i < 3; i++)
		cout << animals[i]->name << ": " << animals[i]->talk() << endl;

	return 0;
}

Note that the talk() method is explicitly declared as virtual. This is because polymorphic method calls have relatively high overhead in C++ [3]. This overhead is lessened by treating all method calls as non-polymorphic, unless explicitly marked as virtual by the developer.

Java

interface Animal 
{
    String getName();
    String talk();
}

abstract class AnimalBase implements Animal
{
    private final String name;

    protected Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Cat extends AnimalBase 
{
    public Cat(String name) {
        super(name);
    }

    public String talk() {
        return "Meowww!";
    }
}

class Dog extends AnimalBase 
{
    public Dog(String name) {
        super(name);
    }

    public String talk() {
        return "Arf! Arf!";
    }
}

public class TestAnimals
{
    // prints the following:
    //
    // Missy: Meowww!
    // Mr. Bojangles: Meowww!
    // Lassie: Arf! Arf!
    //
    public static void main(String[] args) {
        Animal[] animals = {
            new Cat("Missy"),
            new Cat("Mr. Bojangles"),
            new Dog("Lassie")
        };
    
        for (Animal a : animals) {
            System.out.println(a.getName() + ": " + a.talk());
        }
    }
}

Perl

Polymorphism in Perl is inherently straightforward to write because of the languages use of sigils and references. This is the Animal example in standard OO Perl...

{
    package Animal;
    sub new {
        my ( $class, $name ) = @_;
        bless { name => $name }, $class;
    }
}

{
    package Cat;
    use base qw(Animal);
    sub talk  { 'Meow' }
}

{
    package Dog;
    use base qw(Animal);
    sub talk  { 'Woof! Woof!' }
}

my $a = Cat->new('Missy');
my $b = Cat->new('Mr. Bojangles');
my $c = Dog->new('Lassie');

for my $animal ( $a, $b, $c ) {
    say $animal->{name} . ': ' . $animal->talk;
}

# prints the following:
#
# Missy: Meow
# Mr. Bojangles: Meow
# Lassie: Woof! Woof!

This means that Perl can also apply Polymorphism to the method call. Example below is written using the Moose module to show modern OO practises in Perl (and is not needed for method Polymorphism).....

{
    package Animal;
    use Moose;
    has 'name' => ( isa => 'Str', is => 'ro' );
}

{
    package Cat;
    use Moose;
    extends 'Animal';
    sub talk  { 'Meow' }
    sub likes { 'Milk' }
}

{
    package Dog;
    use Moose;
    extends 'Animal';
    sub talk  { 'Woof! Woof!' }
    sub likes { 'Bone' }
}

my @animals = (
    Cat->new( name => 'Missy' ),
    Cat->new( name => 'Mr. Bojangles' ),
    Dog->new( name => 'Lassie' ),
);

for my $animal ( @animals ) {
    for my $trait qw/talk likes/ {
        say $animal->name . ': ' . $trait . ' => ' . $animal->$trait;
    }
}

# prints the following:
#
# Missy: talk => Meow
# Missy: likes => Milk
# Mr. Bojangles: talk => Meow
# Mr. Bojangles: likes => Milk
# Lassie: talk => Woof! Woof!
# Lassie: likes => Bone

Visual Basic .NET

One way of doing polymorphism is through the definition and implementation of a common interface. Consider the example below, where two subclasses (Cat and Dog) implement the IAnimal interface. Two Cat objects and one Dog are instantiated and given names, and then they are gathered in a list and their talk method is called.

Namespace std

    Public Interface IAnimal
        ReadOnly Property Name() As String
        Function Talk() As String
    End Interface

    Public Class Cat
        Implements IAnimal

        Private mName As String

        Sub New(ByVal name As String)
            mName = name
        End Sub

        Public ReadOnly Property Name() As String Implements IAnimal.Name
            Get
                Return mName
            End Get
        End Property

        Public Function Talk() As String Implements IAnimal.Talk
            Return "Meow!"
        End Function
    End Class

    Public Class Dog
        Implements IAnimal

        Private mName As String

        Sub New(ByVal name As String)
            mName = name
        End Sub

        Public ReadOnly Property Name() As String Implements IAnimal.Name
            Get
                Return mName
            End Get
        End Property

        Public Function Talk() As String Implements IAnimal.Talk
            Return "Arf! Arf!"
        End Function
    End Class

    Public Module TestAnimals

        ' Prints the following:
        '
        ' Missy: Meow!
        ' Mr. Bojangles: Meow!
        ' Lassie: Arf! Arf!
        Public Sub Main()
            Dim animals(2) As IAnimal
            animals(0) = New Cat("Missy")
            animals(1) = New Cat("Mr. Bojangles")
            animals(2) = New Dog("Lassie")

            For Each a As IAnimal In animals
                Console.Out.WriteLine("{0}: {1}", a.Name, a.Talk)
            Next a

        End Sub
    End Module

End Namespace

Xbase++

#include "class.ch"

//
//  This program prints:
//
//  Missy  Meow!
//  Mr. Bojangles  Meow!
//  Lassie  Bark!
//  Press any key to continue...
//

/////////////////////////////
//
PROCEDURE Main()
//
/////////////////////////////

  LOCAL aAnimals := Array(3)
  LOCAL i

  aAnimals[1] :=  Cat():New("Missy")
  aAnimals[2] :=  Cat():New("Mr. Bojangles")
  aAnimals[3] :=  Dog():New("Lassie")

  FOR i:=1 TO LEN(aAnimals)
     ? aAnimals[i]:Name + "  " + aAnimals[i]:Talk()
  NEXT i

  WAIT

RETURN

/////////////////////////////
//
CLASS Animal
//
/////////////////////////////

   EXPORTED:
      VAR Name   READONLY

      METHOD Init
      DEFERRED CLASS METHOD Talk
ENDCLASS

METHOD Animal:Init( cName )
   ::Name := cName
RETURN Self

/////////////////////////////
//
CLASS Dog FROM Animal
//
/////////////////////////////
   EXPORTED:
   METHOD Talk
ENDCLASS

METHOD Dog:Talk()
RETURN "Bark!"

/////////////////////////////
//
CLASS Cat FROM Animal
//
/////////////////////////////
   EXPORTED:
   METHOD Talk
ENDCLASS

METHOD Cat:Talk()
RETURN "Meow!"

Parametric Polymorphism

In object-oriented programming languages, the term polymorphism has different, but related meanings; one of these, parametric polymorphism, is known as generic programming in the Object Oriented Programming Community and is supported by many languages including C++, C# and Java.

Generics allow you compile time type safety and other benefits and/or disadvantages depending on the language's implementation.

C++ implements parametric polymorphism through templates. The use of templates requires the compiler to generate a separate instance of the templated class or function for every permutation of type parameters used with it, which can lead to code bloat and difficulty debugging. A benefit C++ templates have over java and c# is that they allow for template metaprogramming, which is a way of pre-evaluating some of the code at compile-time rather than run-time.

Java parametric polymorphism is called generics and implemented through type erasure.

C# parametric polymorphism is called generics and implemented by reification, making c# the only language of the three which supports parametric polymorphism as a first class member of the language. This design choice is leveraged to provide additional functionality, such as allowing reflection with preservation of generic types, as well as alleviating some of the limitations of erasure (such as being unable to create generic arrays). This also means that there is no performance hit from runtime casts and normally expensive boxing conversions. When primitive and value types are used as generic arguments, they get specialized implementations, allowing for efficient generic collections and methods.

Mitigating Circular Dependency References Using Polymorphism In C#

The Problem

Assembly Name: Logger
Dependencies: Data Access Layer
Methods: LogError(string error)

Assembly Name: Data Access Layer
Dependencies: SqlClient, System.Data
Methods: WriteData(string data)

With this design, writing error messages from catch statements and writing them to the Logger class from the Data Access Layer class would would not work due to a circular dependency reference.

Example Broken Class In Data Access Layer

public static class WriteData
{
     public static void WriteData(string data, Logger logger) // not technically possible due to circular reference
     {
          try
          {
               SqlHelper.ExecuteNonQuery("my_stored_procedure", "some", "different", "arguments", data);
          }
          catch(Exception ex)
          {
               Logger.LogError(ex); // not technically possible due to circular reference
          }
     }
}

The Solution

Assembly Name: CommonClasses
Dependencies: None
Interfaces: ILogger

Assembly Name: Data Access Layer
Dependencies: SqlClient, System.Data, CommonClasses
Methods: WriteData(string data, ILogger logger)

Example Working Class In Data Access Layer

public static class WriteData
{
     public static void WriteData(string data, ILogger logger)  // will compile and run fine
     {
          try
          {
               SqlHelper.ExecuteNonQuery("my_stored_procedure", "some", "different", "arguments", data);
          }
          catch(Exception ex)
          {
               ILogger.LogError(ex); // will compile and run fine
          }
     }
}

It is technically a better practice to have a static initialization function the caller uses to pass in a concrete class which implements the ILogger interface to the common classes API which caches it in a static private field with an associated public or internal property for reuse throughout the application without having to reinstantiate the object. Just try to avoid using static data, and if you have to, make sure to synchronize access to it for thread safety. [4]

Operator Overloading

Overloading Polymorphism is the use of one method signature, or one operator such as "+", to perform several different functions depending on the implementation. The "+" operator, for example, may be used to perform integer addition, float addition, list concatenation, or string concatenation. Any two subclasses of Number, such as Integer and Double, are expected to add together properly in an OOP language. The language must therefore overload the concatenation operator, "+", to work this way. This is also referred to as operator overloading.

See also

References