Python tutorials > Object-Oriented Programming (OOP) > Polymorphism > What is subtype polymorphism?

What is subtype polymorphism?

Subtype polymorphism, also known as inclusion polymorphism, is a form of polymorphism where a subtype (a class that inherits from another class) can be used in place of its supertype (the parent class). This means that you can treat objects of different classes in a uniform way, as long as they share a common base class or interface. This is a powerful concept in object-oriented programming that promotes code reusability and flexibility.

Core Concept of Subtype Polymorphism

At its core, subtype polymorphism relies on the 'is-a' relationship. If class B inherits from class A, then B 'is-a' specialized version of A. Consequently, any code that expects an object of type A should also work correctly with an object of type B. This substitution is the essence of subtype polymorphism.

This allows you to write generic code that operates on a collection of objects that share a common base class or interface, without needing to know the specific type of each object at compile time.

Simple Python Example

In this example, Animal is the base class, and Dog and Cat are subtypes. The animal_sound function accepts an Animal object. Because Dog and Cat are subtypes of Animal, we can pass them to the function without any type errors. The correct speak method is called based on the actual type of the object at runtime. This is subtype polymorphism in action.

class Animal:
    def speak(self):
        return "Generic animal sound"

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

def animal_sound(animal):
    print(animal.speak())

dog = Dog()
cat = Cat()

animal_sound(dog)  # Output: Woof!
animal_sound(cat)  # Output: Meow!

Concepts Behind the Snippet

  • Inheritance: The foundation of subtype polymorphism is inheritance. Subtypes inherit the properties and behaviors of their supertypes.
  • Method Overriding: Subtypes can override methods defined in their supertypes to provide specialized behavior.
  • Dynamic Dispatch: The specific method to be executed is determined at runtime based on the actual type of the object. This is also referred to as late binding.

Real-Life Use Case Section

Consider a drawing application. You might have a base class called Shape, and subtypes like Circle, Rectangle, and Triangle. All shapes have a draw() method. You can create a list of Shape objects and iterate through it, calling the draw() method on each object. The correct draw() method for each shape will be executed, regardless of its specific type. This simplifies the code and makes it more maintainable, as you don't need to write separate drawing logic for each type of shape.

Best Practices

  • Liskov Substitution Principle (LSP): Subtypes should be substitutable for their supertypes without altering the correctness of the program. Violating LSP can lead to unexpected behavior and bugs.
  • Design by Contract: Clearly define the expected behavior of supertypes and ensure that subtypes adhere to these contracts. This helps ensure that subtypes can be used interchangeably with supertypes.
  • Favor Composition over Inheritance: While subtype polymorphism relies on inheritance, excessive inheritance can lead to tight coupling and code that is difficult to maintain. Consider using composition when appropriate to achieve polymorphism.

Interview Tip

When discussing subtype polymorphism in an interview, be prepared to explain the 'is-a' relationship, the importance of inheritance and method overriding, and the benefits of using subtype polymorphism. Providing concrete examples will demonstrate your understanding of the concept. Also, be ready to discuss the Liskov Substitution Principle.

When to use them

Use subtype polymorphism when:

  • You have multiple classes that share a common set of operations or behaviors.
  • You want to write code that can work with objects of different types in a uniform way.
  • You want to extend the functionality of a class without modifying its existing code.
  • You want to adhere to the open/closed principle (open for extension, closed for modification).

Memory footprint

Subtype polymorphism itself doesn't directly impose a significant memory overhead. The memory footprint is mainly influenced by the size of the objects being created and the inheritance hierarchy. Deeper inheritance hierarchies can lead to larger objects due to the inherited attributes, but this is a general characteristic of inheritance, not specific to subtype polymorphism.

Alternatives

While subtype polymorphism is a powerful tool, there are alternatives, especially in dynamically typed languages like Python:

  • Duck Typing: Focuses on the object's behavior rather than its specific type. If it walks like a duck and quacks like a duck, then it's treated as a duck.
  • Composition: Achieves polymorphism by combining objects rather than inheriting from them. This can lead to more flexible and maintainable code.
  • Interfaces (Abstract Base Classes): Define a common interface that multiple classes can implement. This enforces a specific set of methods that the classes must provide.

Pros

  • Code Reusability: Promotes code reuse by allowing you to write generic code that works with objects of different types.
  • Flexibility: Makes code more flexible and adaptable to change. You can easily add new subtypes without modifying existing code.
  • Maintainability: Improves code maintainability by reducing code duplication and making it easier to understand and modify.

Cons

  • Increased Complexity: Can increase the complexity of the code if not used carefully. Overuse of inheritance can lead to tight coupling and code that is difficult to understand.
  • Potential for Violating LSP: Subtypes must adhere to the Liskov Substitution Principle to avoid unexpected behavior.
  • Runtime Errors: In dynamically typed languages, type errors might not be detected until runtime.

FAQ

  • What is the main difference between subtype polymorphism and duck typing?

    Subtype polymorphism relies on inheritance and explicit type declarations, ensuring an 'is-a' relationship. Duck typing, on the other hand, focuses on the behavior of an object; if it has the methods you need, you can use it, regardless of its type or inheritance hierarchy.

  • How does subtype polymorphism relate to the Liskov Substitution Principle?

    Subtype polymorphism is heavily reliant on the Liskov Substitution Principle (LSP). LSP states that subtypes should be substitutable for their supertypes without altering the correctness of the program. If a subtype violates LSP, it breaks the promise of subtype polymorphism, leading to unexpected behavior.

  • Is subtype polymorphism only applicable to object-oriented languages?

    Yes, subtype polymorphism is a core concept in object-oriented programming, relying on inheritance and the ability of subtypes to be used in place of their supertypes. While other programming paradigms might offer related concepts for achieving similar flexibility, subtype polymorphism is fundamentally tied to OOP principles.