Sunday 5 April 2020

Post 79: What some people don't know about Polymorphism

In the context of programming, polymorphism means - in abstract terms - when programming constructs or part of a code, e.g. classes or methods, can be applied to multiple Objects of different types. Usually this is where the knowledge of most people end. Most people don't know there are different kinds of polymorphism. In this post I'll explain the different types of polymorphism and discuss the advantages and disadvantages of each one of them:

Subtype-Polymorphism
If people talk about polymorphism, they usually mean this type of polymorphism. This is also the most simplest kind of polymorphism. When subtyping, an object of a subtype can be used everywhere where it's supertype is used. For example:

class Supertype {}
class Subtype extends Supertype {}
...
Supertype sup1 = new Supertype();
Supertype sup2 = new Subtype(); 

In the example above, Subtype can be used where Supertype is used.

Another is example:
class MyObject1 {}
class MyObject2 {}

MyObject1 o1 = new MyObject1();MyObject2 o2 = new MyObject2();List<Object> obList = new ArrayList<>();obList.add(o1);obList.add(o2);obList.add(2);obList.add("hello");

In the above example obList can take objects of different types - from custom objects to numbers to String.

Advantages
  • Subtype-Polymorphism gives you flexibility

Disadvantages
  • It's hard to limit the flexibility
  • You lose type safety, because you only have limited control of which object is allowed to be added to a container and if you take an object out of a container, you have to cast it to it's original type in order to use it in a meaningful way
Parametic Polymorphism

Sometimes you want to reuse a class for different types but don't want to write for every type a class of their own. So, basically you only want to swap out certain parameters for a specific type. This is what parametic polymorphism is about.

When creating a class, you set a generic type T. Within the class you treat the generic type like any other type. When initializing you specify the generic type T to a specific type. For example:

public class MyObject1<T> {
    T obj;    void setObj(T obj) {
        this.obj = obj;
    }

    T getObj() {
        return this.obj;
    }
}

Let's say you want to apply this class only to Integers, then you initialize it like the following:
MyObject1<Integer> object1 = new MyObject1<Integer>();

On another instance you want to reuse the same class but for another type, then you initilize it with a different class:

MyObject1<String> object2 = new MyObject1<String>();

You can use this technique not only for one type but also multiple types. For example:

public class KeyValue<K, V> {
    private K key;    private V value;        
    void setKeyValue(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    K getKey() {
        return this.key;
    }
    
    V getValue() {
        return this.value;
    }
}

Advantages

  • Usually a much more powerful type analysis at compile time

Disadvantages

  • Parametric polymorphism offers less flexibility compared to subtype polymorphism
  • The generic parameter doesn't know about the attributes and methods of the type that will be applied to at the end

Limited parametric polymorphism

The limited parametric polymorphism builds upon the parametic polymorphism and enables you to limit the typeparameter to a certain type by defining an upper bound for your type. This is useful when you want for example to build a container of different types and need to know the existence of certain attributes or methods beforehand.

For example you have a basket of different food products and want to know which of these elements is the oldest. Using limited parametric polymorphism, you would implement like this:
public interface Ageable {
    Date getBestBeforeDate();}

public class FoodBasket<T extends Ageable> {
    private T oldestFood;
    public void put(T food) {
        if (oldestFood == null ||
                food.getBestBeforeDate().compareTo(oldestFood.getBestBeforeDate()) < 0) {
            oldestFood = food;
        }

    }

    public T getOldestFood() {
        return oldestFood;
    }
}

By using extends we are creating an upper bound for our type. Thus we know that our type has to have the attributes or methods defined in the superclass resp. interface and can work them.

Let's say you want to use the basket for a specific kind of food and want to extend it with additional functionalities, the you would go about this:

public class Egg implements Ageable {
    private Date bestBeforeDate;
    Egg(Date bestBeforeDate) {
        this.bestBeforeDate = bestBeforeDate;    }

    @Override    public Date getBestBeforeDate() {
        return this.bestBeforeDate;    }
}

public class OldEggsBasket extends FoodBasket<Egg> {
    // additional functions}

Types can also implement multiple Interfaces:

public interface Printable {
    void print();}

public class FoodBasket<T extends Ageable & Printable> {
    private T oldestFood;
    public void put(T food) {
        if (oldestFood == null ||
                food.getBestBeforeDate().compareTo(oldestFood.getBestBeforeDate()) < 0) {
            oldestFood = food;        }
        food.print();    }
    //...
 }

Advantages

  • Write reusable code, e.g. library classes, that are used by other programmers who can parameterize according to their need

Disadvantages

  • Less flexibility in comparison to the above mentioned kinds of polymorphism




Ad-hoc Polymorphism

Most developers probably know this concept already under a different name: overloading. With overloading you are providing the different methods with the same name for different signatures. The most common one is the plus (+) operator.

Advantages

  • Flexibility in naming of (overloaded) methods
Disadvantages
  • No code reuse. Therefore isn't really considered polymorphism like the other kinds of polymorphism

I am interested whether you know more advantages and disadvantages for each of the types of polymorphism.

No comments:

Post a Comment

Tweet