Explore interface-based polymorphism

Completed

Interface-based polymorphism allows you to define a contract that classes must implement, enabling you to achieve polymorphic behavior without relying on class inheritance. Interfaces define a set of methods and properties that a class must implement, but do not provide any implementation details. Classes can implement multiple interfaces, allowing them to define behavior for different aspects of the application.

To explore interface-based polymorphism, you can begin with a scenario that's similar to the inheritance-based polymorphism scenario from the previous unit.

Consider the following code sample that demonstrates interface-based polymorphism in C#:


// Define an interface
public interface ISound
{
    void MakeSound();
}

// Implement the interface in different classes
public class Dog : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Cat : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The cat meows.");
    }
}

public class Cow : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The cow moos.");
    }
}

class Program
{
    static void Main()
    {
        // Create an array of Animal myObjects
        ISound[] myObjects = new ISound[3];

        ISound object1 = new Dog();
        ISound object2 = new Cat();
        ISound object3 = new Cow();

        myObjects[0] = object1;
        myObjects[1] = object2;
        myObjects[2] = object3;

        // Demonstrate polymorphism
        foreach (ISound currentObject in myObjects)
        {
            currentObject.MakeSound();
        }
    }
}

In this code sample, interface-based polymorphism is demonstrated by defining an ISound interface that includes a MakeSound method. The Dog, Cat, and Cow classes implement the ISound interface by providing their own implementations of the MakeSound method. The Main method creates an array of ISound objects and assigns instances of Dog, Cat, and Cow to the array. The foreach loop iterates over the array and calls the MakeSound method on each object, demonstrating polymorphic behavior.

At this point, interface-based polymorphism may appear interchangeable with inheritance-based polymorphism. Updating the sample code with addition class type should help to demonstrate the difference.

Consider the following updated code sample:


// Define an interface
public interface ISound
{
    void MakeSound();
}

// Implement the interface in different classes
public class Dog : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Cat : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The cat meows.");
    }
}

public class Cow : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The cow moos.");
    }
}

public class Doorbell : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The doorbell rings.");
    }
}

public class CarHorn : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The car horn honks.");
    }
}

class Program
{
    static void Main()
    {
        // Create an array of Animal myObjects
        ISound[] myObjects = new ISound[3];

        ISound object1 = new Dog();
        ISound object2 = new Cat();
        ISound object3 = new Cow();
        ISound object4 = new Doorbell();
        ISound object5 = new CarHorn();

        myObjects[0] = object1;
        myObjects[1] = object2;
        myObjects[2] = object3;
        myObjects[3] = object4;
        myObjects[4] = object5;

        // Demonstrate polymorphism
        foreach (ISound currentObject in myObjects)
        {
            currentObject.MakeSound();
        }
    }
}

This updated sample code includes two additional classes, Doorbell and CarHorn, that implement the ISound interface. These additional classes help to demonstrate interface-based polymorphism's greater flexibility in defining behavior that's not tied to a specific class hierarchy.

Interface-based polymorphism provides additional benefits, such as allowing classes to implement multiple interfaces and defining contracts for specific functionality. Interfaces can be used to define common behavior that can be shared across different classes, enabling polymorphism without the need for a common base class.

Consider the following code that demonstrates objects with single and multiple interfaces:


// Define an interface for sound
public interface ISound
{
    void MakeSound();
}

// Define an interface for movement
public interface IMovable
{
    void Move();
}

// Implement the ISound and IMovable interfaces in different classes
public class Dog : ISound, IMovable
{
    public void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }

    public void Move()
    {
        Console.WriteLine("The dog runs.");
    }
}

public class Cat : ISound, IMovable
{
    public void MakeSound()
    {
        Console.WriteLine("The cat meows.");
    }

    public void Move()
    {
        Console.WriteLine("The cat jumps.");
    }
}

public class Cow : ISound, IMovable
{
    public void MakeSound()
    {
        Console.WriteLine("The cow moos.");
    }

    public void Move()
    {
        Console.WriteLine("The cow walks.");
    }
}

public class Doorbell : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The doorbell rings.");
    }
}

public class CarHorn : ISound
{
    public void MakeSound()
    {
        Console.WriteLine("The car horn honks.");
    }
}

class Program
{
    static void Main()
    {
        // Create an array of ISound objects
        ISound[] soundObjects = new ISound[5];

        ISound object1 = new Dog();
        ISound object2 = new Cat();
        ISound object3 = new Cow();
        ISound object4 = new Doorbell();
        ISound object5 = new CarHorn();

        soundObjects[0] = object1;
        soundObjects[1] = object2;
        soundObjects[2] = object3;
        soundObjects[3] = object4;
        soundObjects[4] = object5;

        // Demonstrate polymorphism with ISound
        Console.WriteLine("Demonstrating ISound polymorphism:");
        foreach (ISound currentObject in soundObjects)
        {
            currentObject.MakeSound();
        }

        // Create an array of IMovable objects
        IMovable[] movableObjects = new IMovable[3];

        IMovable movableObject1 = new Dog();
        IMovable movableObject2 = new Cat();
        IMovable movableObject3 = new Cow();

        movableObjects[0] = movableObject1;
        movableObjects[1] = movableObject2;
        movableObjects[2] = movableObject3;

        // Demonstrate polymorphism with IMovable
        Console.WriteLine("\nDemonstrating IMovable polymorphism:");
        foreach (IMovable currentObject in movableObjects)
        {
            currentObject.Move();
        }

        // Demonstrate objects with single and multiple interfaces
        Console.WriteLine("\nDemonstrating objects with single and multiple interfaces:");
        Dog dog = new Dog();
        Cat cat = new Cat();
        Cow cow = new Cow();
        Doorbell doorbell = new Doorbell();
        CarHorn carHorn = new CarHorn();

        dog.MakeSound();
        dog.Move();

        cat.MakeSound();
        cat.Move();

        cow.MakeSound();
        cow.Move();

        doorbell.MakeSound();

        carHorn.MakeSound();
    }
}

Avoid common pitfalls when implementing interface-based polymorphism

When your goal is interface-based polymorphism, here are some things to avoid and some things to ensure:

  • Avoid implementing unrelated interfaces.
  • Avoid default implementations in interfaces. While C# allows default implementations in interfaces, it can lead to confusion and reduce the clarity of the interface's purpose. Prefer abstract classes for shared implementations.
  • Avoid overcomplicating interface hierarchies. Keep interface hierarchies simple and avoid deep inheritance chains. Complex hierarchies can make the code harder to understand and maintain.
  • Ensure that you follow the interface segregation principle (ISP). The ISP states that no client should be forced to depend on methods it doesn't use. Create smaller, more specific interfaces rather than one large interface.

Summary

Interface-based polymorphism allows you to define contracts that classes must implement, enabling you to achieve polymorphic behavior without relying on class inheritance. By implementing interfaces, you can create flexible and maintainable code that promotes loose coupling and code reusability. Interfaces provide a way to define common behavior that can be shared across different classes, allowing you to work with objects of different types through a common interface. By understanding interface-based polymorphism, you can design modular and extensible applications that are easier to maintain and evolve over time.