C# > Object-Oriented Programming (OOP) > Polymorphism > Interfaces and Implementation

Polymorphism with Interfaces: Shape Area Calculation

This code snippet demonstrates polymorphism in C# using interfaces. It defines an IShape interface with a method to calculate the area, and then implements this interface in different shape classes (Circle, Rectangle, and Triangle). This allows you to treat different shape objects uniformly through the IShape interface, showcasing runtime polymorphism.

Interface Definition: IShape

This code defines an interface named IShape. Interfaces in C# define a contract that classes can implement. Any class that implements IShape must provide an implementation for the CalculateArea() method. This enforces a consistent structure for any object representing a shape.

public interface IShape
{
    double CalculateArea();
}

Class Implementation: Circle

This code defines a class named Circle that implements the IShape interface. It has a private field _radius to store the radius of the circle. The constructor initializes the radius, and the CalculateArea() method calculates the area of the circle using the formula πr². Critically, it provides a concrete implementation of the method declared in the interface.

public class Circle : IShape
{
    private double _radius;

    public Circle(double radius)
    {
        _radius = radius;
    }

    public double CalculateArea()
    {
        return Math.PI * _radius * _radius;
    }
}

Class Implementation: Rectangle

This code defines a class named Rectangle that also implements the IShape interface. It has private fields _width and _height to store the dimensions of the rectangle. The constructor initializes these dimensions, and the CalculateArea() method calculates the area of the rectangle by multiplying width and height.

public class Rectangle : IShape
{
    private double _width;
    private double _height;

    public Rectangle(double width, double height)
    {
        _width = width;
        _height = height;
    }

    public double CalculateArea()
    {
        return _width * _height;
    }
}

Class Implementation: Triangle

This code defines a class named Triangle that implements the IShape interface. It has private fields _base and _height to store the base and height of the triangle. The constructor initializes these dimensions, and the CalculateArea() method calculates the area of the triangle by multiplying 0.5 with base and height.

public class Triangle : IShape
{
    private double _base;
    private double _height;

    public Triangle(double baseValue, double height)
    {
        _base = baseValue;
        _height = height;
    }

    public double CalculateArea()
    {
        return 0.5 * _base * _height;
    }
}

Polymorphic Usage

This code demonstrates how the IShape interface enables polymorphism. We create instances of Circle, Rectangle and Triangle, but we store them as IShape objects. This allows us to treat them uniformly. The loop iterates through an array of IShape objects, calling CalculateArea() on each. The correct CalculateArea() method is called based on the actual type of the object at runtime (e.g., the Circle version is called for the circle object). This is polymorphism in action.

IShape circle = new Circle(5);
IShape rectangle = new Rectangle(4, 6);
IShape triangle = new Triangle(3, 8);

Console.WriteLine("Circle Area: " + circle.CalculateArea());
Console.WriteLine("Rectangle Area: " + rectangle.CalculateArea());
Console.WriteLine("Triangle Area: " + triangle.CalculateArea());

// Demonstrating polymorphism in a collection
IShape[] shapes = { circle, rectangle, triangle };

double totalArea = 0;
foreach (IShape shape in shapes)
{
    totalArea += shape.CalculateArea();
}

Console.WriteLine("Total Area: " + totalArea);

Concepts Behind the Snippet

This snippet illustrates several key OOP concepts:

  • Interface: Defines a contract that classes must adhere to.
  • Implementation: Classes provide concrete implementations of the interface's methods.
  • Polymorphism: The ability to treat objects of different classes in a uniform way through a common interface.
Polymorphism allows for more flexible and maintainable code because you can add new shape types without modifying the code that uses the IShape interface.

Real-Life Use Case

Imagine a drawing application. You might have various drawing tools (e.g., lines, circles, squares). Each tool could be represented by a class that implements an IDrawable interface. The application can then treat all tools uniformly through the IDrawable interface, regardless of their specific implementation details. This makes it easy to add new tools without changing the core drawing logic.

Best Practices

  • Interface Segregation Principle: Design interfaces to be small and specific to the functionality they represent. Avoid creating large, monolithic interfaces.
  • Favor Composition over Inheritance: While inheritance can achieve polymorphism, interfaces and composition often lead to more flexible and maintainable designs.
  • Consider Abstract Classes: If some behavior is common across multiple implementations, consider using an abstract class to provide a default implementation, while still allowing subclasses to override specific parts. However, C# only allows single inheritance, so interfaces are often preferred.

Interview Tip

Be prepared to explain the difference between interfaces and abstract classes, and when to use each. Also, be ready to discuss the benefits of using interfaces for achieving polymorphism.

When to Use Them

Use interfaces when you want to define a contract that multiple unrelated classes can implement. This is especially useful when you need to treat objects of different types uniformly. Interfaces are also helpful for decoupling components and promoting loose coupling.

Memory Footprint

The memory footprint of interfaces themselves is minimal. The primary overhead comes from the objects that implement the interface. Each object will store a reference to its type information (used for dynamic dispatching of methods), which contributes to its memory footprint.

Alternatives

  • Abstract Classes: Provide a base class with some implemented methods and some abstract methods that subclasses must implement.
  • Delegates and Events: Can be used to achieve a form of polymorphism through function pointers.
  • Dynamic Typing (dynamic keyword): Bypasses compile-time type checking and relies on runtime type resolution. This can be useful for interoperability with dynamic languages, but it sacrifices compile-time safety.

Pros

  • Loose Coupling: Interfaces promote loose coupling between components, making the code more flexible and maintainable.
  • Multiple Inheritance (of behavior): A class can implement multiple interfaces, allowing it to inherit behavior from multiple sources.
  • Testability: Interfaces make it easier to write unit tests by allowing you to mock or stub dependencies.

Cons

  • Increased Complexity: Introducing interfaces can sometimes add complexity to the code, especially in simple scenarios.
  • Implementation Overhead: Classes must provide concrete implementations for all methods defined in the interface.

FAQ

  • What is the difference between an interface and an abstract class?

    An interface defines a contract that classes must implement, without providing any implementation details. An abstract class can provide partial implementations and abstract methods that subclasses must implement. A class can implement multiple interfaces, but it can only inherit from a single abstract class.
  • Why use interfaces instead of concrete classes?

    Using interfaces promotes loose coupling, making the code more flexible and maintainable. It allows you to treat objects of different types uniformly and facilitates unit testing.
  • Can an interface contain fields?

    No, interfaces cannot contain fields (instance variables). They can only contain method declarations, properties, events, and indexers.