# Dynamic DispatchA intermediate-level guide to Dynamic Dispatch: before-and-after java code and diagrams for a CS student.

## The Maintenance Trap of Manual Type Checking

Imagine you are building a system to manage different types of animals. At first, it is simple: you have a Dog and a Cat. But as your application grows, you add a Bird, then a Lion, then a Fish. Every time you add a new animal, you find yourself hunting through your codebase to find every `if-else` block or `switch` statement that checks the type of an object before deciding what it should do.

This is a classic "Switch Statements" code smell. You are manually managing behavior based on types, which violates the Open/Closed Principle: your existing logic must be modified every time you introduce a new class.

```java
class Dog {
    void makeSound() { System.out.println("Woof!"); }
}

class Cat {
    void makeSound() { System.out.println("Meow!"); }
}

public class AnimalService {
    // Manual type checking required to decide behavior
    void performSound(Object animal) {
        if (animal instanceof Dog) {
            ((Dog) animal).makeSound();
        } else if (animal instanceof Cat) {
            ((Cat) animal).makeSound();
        }
    }
}
```

In this version, `AnimalService` has no idea what an animal *is*; it only knows how to inspect them one by one using `instanceof`. The relationships are disconnected, making the code fragile and difficult to extend:

```mermaid
classDiagram
    direction BT
    Dog ..> AnimalService : parameter
    Cat ..> AnimalService : parameter
    AnimalService --> Dog : casts to
    AnimalService --> Cat : casts to
```

## Introducing Dynamic Dispatch

To fix this, we move the decision-making process from the service to the objects themselves. Instead of asking "Are you a Dog?", the service simply says "Make your sound." This shift is powered by dynamic dispatch (also known as polymorphism).

In dynamic dispatch, the specific method that gets executed is determined at runtime based on the **actual type** of the object, rather than the **declared type** used in the code.

```java
interface Animal {
    void makeSound();
}

class Dog implements Animal {
    @Override
    public void makeSound() { System.out.println("Woof!"); }
}

class Cat implements Animal {
    @Override
    public void makeSound() { System.out.println("Meow!"); }
}

public class AnimalService {
    // Dynamic dispatch: the JVM determines which makeSound() to call at runtime
    void performSound(Animal animal) {
        animal.makeSound();
    }
}
```

By using an interface, we have decoupled the `AnimalService` from the specific implementations. The service now works with any class that implements `Animal`.

```mermaid
classDiagram
    class Animal {
        +makeSound() void
    }
    Dog --|> Animal : implements
    Cat --|> Animal : implements
    AnimalService --> Animal : calls "makeSound()" on
```

### Declared Type vs. Actual Type

A common point of confusion for students is the difference between what the compiler sees and what actually happens in memory. 

1.  **Declared Type (The Label):** When you write `Animal animal = new Dog();`, the declared type is `Animal`. The compiler uses this to decide which methods you are *allowed* to call. Since the interface defines `makeSound()`, you can call it.
2.  **Actual Type (The Identity):** At runtime, the JVM looks at the object in memory and sees it is actually a `Dog`. This is the "actual type."

When you call `animal.makeSound()`, dynamic dispatch performs a lookup to ensure the version of the method belonging to the *actual* type is the one that runs.

### How the Dispatch Happens at Runtime

The following sequence shows how the `AnimalService` triggers a behavior without knowing the specific identity of the object it holds.

```mermaid
sequenceDiagram
    participant AS as AnimalService
    participant D as Dog
    
    AS ->> D: makeSound()
    D -->> AS: (output) "Woof!"
```

### Overloading vs. Overriding

It is important to distinguish between two terms that sound similar but behave differently:

*   **Overloading (Static/Compile-time):** Creating multiple methods in the same class with the same name but different parameters. The compiler decides which one to use when you write the code.
*   **Overriding (Dynamic/Runtime):** Providing a specific implementation of a method that is already defined in an interface or superclass. This is what enables dynamic dispatch; the decision of which version to run happens while the program is running.

## When to Use It, and When Not To

Replace Conditional with Polymorphism whenever you have complex branching logic based on object types. This makes your code extensible: you can add a `Cow` class by simply implementing `Animal`, without ever touching the `AnimalService` code again.

However, do not use dynamic dispatch for everything. If you are dealing with simple data structures or primitive values (like integers or strings) where there is no meaningful behavior to vary, trying to force an inheritance hierarchy will only add unnecessary complexity and memory overhead.

## Takeaways

- Use dynamic dispatch to eliminate `instanceof` checks and manual type casting.
- The declared type restricts what you can call; the actual type determines how that call behaves.
- Dynamic dispatch is the mechanism that allows a system to be extended with new types without modifying existing business logic.

## Usage

**Demonstrating how the same method call results in different behaviors based on actual object type**

```java
AnimalService service = new AnimalService();
service.performSound(new Dog()); // Prints: Woof!
service.performSound(new Cat()); // Prints: Meow!
```
