Skip to content

Polymorphism

Polymorphism#

Polymorphism allows objects of different types to be treated through a common interface while maintaining their specific behavior. It enables a single method call to behave differently depending on the actual object type at runtime through dynamic method dispatch.

Core Concept#

Polymorphism promotes code flexibility and extensibility by allowing new types to be added without modifying existing code. Instead of writing separate methods for each type, you can write a single method that works with any object implementing the common interface.

Example: Instead of playGuitar(), playPiano(), and playViolin(), you write one playInstrument() method that works with any musical instrument.

How Polymorphism Works#

graph LR
    A[Client Code] --> B[playInstrument method]
    B --> C[Instrument Interface]

    style A fill:#e8f5e8
    style B fill:#e3f2fd
    style C fill:#c8e6c9

Runtime Decision Making: The program determines which specific implementation to execute based on the actual object type.

graph TD
    C[Instrument Interface] --> D[Guitar.play]
    C --> E[Piano.play]
    C --> F[Violin.play]
    C --> G[Drums.play]

    D --> H["Strumming strings"]
    E --> I["Pressing keys"]
    F --> J["Bowing strings"]
    G --> K["Hitting percussion"]

    style C fill:#c8e6c9
    style D fill:#ffecb3
    style E fill:#ffecb3
    style F fill:#ffecb3
    style G fill:#ffecb3

Benefits#

  • Extensibility: Add new types without modifying existing code
  • Maintainability: Single interface for multiple implementations
  • Scalability: Build frameworks that work with future classes

Implementation Examples#

public abstract class Instrument {
    public abstract void play();
}

public class Guitar extends Instrument {
    @Override
    public void play() {
        System.out.println("Strumming the guitar!");
    }
}

public class Piano extends Instrument {
    @Override
    public void play() {
        System.out.println("Playing the piano!");
    }
}

public void playInstrument(Instrument instrument) {
    instrument.play(); // Dynamic method invocation
}
abstract class Instrument {
    abstract fun play()
}

class Guitar : Instrument() {
    override fun play() {
        println("Strumming the guitar!")
    }
}

class Piano : Instrument() {
    override fun play() {
        println("Playing the piano!")
    }
}

fun playInstrument(instrument: Instrument) {
    instrument.play() // Dynamic method invocation
}
abstract class Instrument {
    abstract play(): void;
}

class Guitar extends Instrument {
    play(): void {
        console.log("Strumming the guitar!");
    }
}

class Piano extends Instrument {
    play(): void {
        console.log("Playing the piano!");
    }
}

function playInstrument(instrument: Instrument) {
    instrument.play(); // Dynamic method invocation
}
abstract class Instrument {
    void play();
}

class Guitar extends Instrument {
    @override
    void play() {
        print("Strumming the guitar!");
    }
}

class Piano extends Instrument {
    @override
    void play() {
        print("Playing the piano!");
    }
}

void playInstrument(Instrument instrument) {
    instrument.play(); // Dynamic method invocation
}
class Instrument {
    func play() {
        fatalError("This method must be overridden")
    }
}

class Guitar: Instrument {
    override func play() {
        print("Strumming the guitar!")
    }
}

class Piano: Instrument {
    override func play() {
        print("Playing the piano!")
    }
}

func playInstrument(instrument: Instrument) {
    instrument.play() // Dynamic method invocation
}
from abc import ABC, abstractmethod

class Instrument(ABC):
    @abstractmethod
    def play(self):
        pass

class Guitar(Instrument):
    def play(self):
        print("Strumming the guitar!")

class Piano(Instrument):
    def play(self):
        print("Playing the piano!")

def play_instrument(instrument: Instrument):
    instrument.play() # Dynamic method invocation