Thursday, March 8, 2012

Covariant return type. Liskov Principle as a language feature

Given these classes in Java:

class Animal 
{
    protected int _generation = 0;

    public Animal() {
        this(1);
    }
    
    public Animal(int generation) {
        _generation = generation;
    }

    public Animal spawn() {
        System.out.println("From Animal Spawn");
        return new Animal(_generation+1);
    }
}


class Dog extends Animal 
{    
    public Dog() {
        this(1);
    }
    
    public Dog(int generation) {
        _generation = generation;        
    }
    

    @Override
    public Dog spawn() {
        System.out.println("From Dog Spawn");
        return new Dog(_generation + 1);
    }
}


And this usage:

Animal a = new Dog();  
Animal x = a.spawn();
 
 
Dog b = new Dog();
Dog y = b.spawn();  


Output:
From Dog Spawn
From Dog Spawn

Java allows the above code as it supports covariant type on overriding method, it supports Liskov substitution principle on overriding methods, it allows overriding methods to return different type, as long as that type is a derived type from the overridden method. If you change the return type of Dog's spawn to other type, say String, it will result to compiler error.


Prior to Java 1.5, covariant is not possible, overriding method have to return the exactly same return type of the overridden method. And users will be forced to cast things around:

Java 1.4 and below:
class Dog extends Animal 
{
    Animal spawn() {        
        return new Dog(_generation + 1);
    } 
}

And you have to use this in Java 1.4 and below:
Dog b = new Dog();
Dog y = (Dog) b.spawn(); // removing the cast will result to compilation error, type mismatch


Covariant is supported in C++ too:

#include <cstdio>

class Animal
{
    protected: int _generation = 0;

    public: Animal() : Animal(1) {  
    }

    public: Animal(int generation) {
        _generation = generation;
    }

    public: virtual Animal* spawn()
    {
        puts("Spawn From Animal");
        return new Animal(_generation + 1);
    }
};

class Dog : public Animal
{
    public: Dog() : Dog(1) {  
    }

    public: Dog(int generation) {
        _generation = generation;
    }

    public: virtual Dog* spawn()
    {
        puts("Spawn From Dog");
        return new Dog(_generation + 1);
    }
};

int main()
{
    Animal* a = new Dog();
    Animal* x = a->spawn();

    Dog* b = new Dog();
    Dog* y = b->spawn();

    return 0;
}


C# 4 has no support for covariant return type. It has no support for Liskov Substitution Principle when overriding methods.

public class Test 
{
    public static void Main(string[] args) 
    {
        Animal a = new Dog();  
        Animal x = a.Spawn();
 
 
        Dog b = new Dog();
        Dog y = b.Spawn();
    }

}


class Animal 
{
    
    protected int _generation = 0;

    public Animal() : this(1) 
    {    
    }
    
    public Animal(int generation) 
    {
        _generation = generation;
    }

    public virtual Animal Spawn() 
    {
        System.Console.WriteLine("From Animal Spawn");
        return new Animal(_generation + 1);
    }
}


class Dog : Animal 
{    
    
    public Dog() : this(1)
    {
    }
    
    public Dog(int generation) 
    {
        _generation = generation;        
    }
    

    // Compilation error: 'Dog.Spawn()': return type must be 'Animal' to match overridden member 'Animal.Spawn()'
    public override Dog Spawn() 
    {
        System.Console.WriteLine("From Dog Spawn");
        return new Dog(_generation + 1);
    }
}


To fix that in C# 4, you have to make the return type of the overriding method to exactly the same return type of overridden method

public class Test 
{
    public static void Main(string[] args) 
    {
        Animal a = new Dog();  
        Animal x = a.Spawn();
 
 
        Dog b = new Dog();
        Dog y = (Dog) b.Spawn();
    }

}


class Animal 
{
    
    protected int _generation = 0;

    public Animal()
    {
        _generation = 1;
    }
    
    public Animal(int generation) 
    {
        _generation = 1;
    }

    public virtual Animal Spawn() 
    {
        System.Console.WriteLine("From Animal Spawn");
        return new Animal(_generation + 1);
    }
}


class Dog : Animal 
{    
    
    public Dog()
    {
    }
    
    public Dog(int generation) 
    {
        _generation = generation;        
    }
    

    public override Animal Spawn() 
    {
        System.Console.WriteLine("From Dog Spawn");
        return new Dog(_generation + 1);
    }
}

Then when receiving the returned value from the overriding method, cast it to its derived type when necessary:

Dog b = new Dog();
Dog y = (Dog) b.Spawn(); // cast is needed, covariant is not yet supported in C# for overriding methods. Liskov substitution principle is not yet possible on overriding methods.

No comments:

Post a Comment