You have a Shape class with area() method. But "Shape" itself has no area - only Circle and Rectangle do. An abstract class lets you define the method without implementing it, forcing subclasses to provide their own implementation.

Basic abstract class

Define a class that can't be instantiated.

BasicAbstract.java
// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

// Basic Abstract Class and Method

public class BasicAbstract {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class ===\n");

        // Cannot instantiate abstract class!
        // Animal animal = new Animal();  // ERROR: Animal is abstract

        // Must use concrete subclasses
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.makeSound();
        cat.makeSound();

        System.out.println("\n=== Using Parent Reference ===");

        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.makeSound();
        a2.makeSound();

        System.out.println("\n=== Why Use Abstract? ===");
        System.out.println("""
        Abstract classes:

        1. Define a CONTRACT
           - "All animals must make a sound"
           - Subclasses MUST implement makeSound()

        2. Prevent incomplete objects
           - Cannot create a generic "Animal"
           - Must be a specific animal type

        3. Provide common structure
           - Shared fields and methods
           - Specific behavior in subclasses
        """);
    }
}

// Abstract class - cannot instantiate
abstract class Animal {
    String name;

    // Abstract method - no body!
    abstract void makeSound();

    // Notice: no { } curly braces, just semicolon!
}

// Concrete class - MUST implement abstract methods
class Dog extends Animal {
    static String dogSound = ;

    @Override
    void makeSound() {
        System.out.println(dogSound);
    }
}

class Cat extends Animal {
    static String catSound = ;

    @Override
    void makeSound() {
        System.out.println(catSound);
    }
}

// What if we forget to implement?
// class Bird extends Animal {
//     // No makeSound() implementation
//     // ERROR: Bird must implement abstract method makeSound()
// }

abstract class can't be instantiated. Can have abstract and concrete methods.

abstract class Class that can't be instantiated. May contain abstract methods.

Mix abstract and concrete methods

Provide some implementation, require the rest.

AbstractWithConcrete.java
// Mixing Abstract and Concrete Methods

public class AbstractWithConcrete {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class with Concrete Methods ===\n");

        Dog dog = new Dog("Buddy");
        Cat cat = new Cat("Whiskers");

        System.out.println("--- Dog ---");
        dog.introduce();    // Concrete (inherited)
        dog.makeSound();    // Abstract (implemented)
        dog.sleep();        // Concrete (inherited)

        System.out.println("\n--- Cat ---");
        cat.introduce();
        cat.makeSound();
        cat.sleep();

        System.out.println("\n=== Bird Overrides Concrete Method ===");

        Bird bird = new Bird("Tweety");
        bird.introduce();
        bird.makeSound();
        bird.sleep();       // Overridden!

        System.out.println("\n=== Abstract vs Concrete ===");
        System.out.println("""
        Abstract methods:
        - MUST be overridden
        - No default implementation
        - Subclass provides behavior

        Concrete methods:
        - CAN be overridden (optional)
        - Have default implementation
        - Subclass uses or replaces
        """);
    }
}

abstract class Animal {
    protected String name;

    // Constructor in abstract class
    Animal(String name) {
        this.name = name;
    }

    // CONCRETE method - has implementation
    void introduce() {
        System.out.println("Hi, I'm " + name);
    }

    // CONCRETE method - shared behavior
    void sleep() {
        System.out.println(name + " is sleeping... Zzz");
    }

    // ABSTRACT method - subclass must implement
    abstract void makeSound();
}

class Dog extends Animal {
    Dog(String name) {
        super(name);
    }

    // MUST implement abstract method
    @Override
    void makeSound() {
        System.out.println(name + " barks: Woof! Woof!");
    }

    // Does NOT override sleep() - uses inherited version
}

class Cat extends Animal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " meows: Meow!");
    }

    // Also uses inherited sleep()
}

class Bird extends Animal {
    Bird(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " chirps: Tweet! Tweet!");
    }

    // OPTIONAL: Override concrete method
    @Override
    void sleep() {
        System.out.println(name + " perches and sleeps");
    }
}

Abstract methods have no body. Concrete methods have implementation.

abstract method Method with no body: `abstract void draw();`. Subclass must implement.

Constructors in abstract classes

Abstract classes can have constructors for subclasses.

AbstractConstructor.java
// Constructors in Abstract Classes

public class AbstractConstructor {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class Constructors ===\n");

        // Create employees
        Manager manager = new Manager("Alice", 85000, "Engineering");
        Developer developer = new Developer("Bob", 75000, "Java");

        System.out.println("--- Manager ---");
        manager.displayInfo();
        manager.work();

        System.out.println("\n--- Developer ---");
        developer.displayInfo();
        developer.work();

        System.out.println("\n=== Constructor Chain ===");
        System.out.println("""
        When creating Manager("Alice", 85000, "Engineering"):

        1. Manager constructor called
        2. super(name, salary) → Employee constructor
        3. Employee initializes name, salary, id
        4. Manager initializes department

        Even though Employee is abstract,
        its constructor runs!
        """);

        System.out.println("=== Multiple Constructors ===\n");

        // Different constructors
        Product p1 = new Book("Clean Code", 44.99, "Robert Martin");
        Product p2 = new Book("Effective Java");  // Uses default price

        System.out.println("Book 1:");
        p1.displayInfo();

        System.out.println("\nBook 2:");
        p2.displayInfo();
    }
}

// Abstract class with constructor
abstract class Employee {
    protected String name;
    protected double salary;
    protected int employeeId;
    private static int nextId = 1000;

    // Constructor - called by subclasses
    Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
        this.employeeId = nextId++;
        System.out.println("Employee constructor: Creating " + name);
    }

    // Concrete method
    void displayInfo() {
        System.out.println("ID: " + employeeId);
        System.out.println("Name: " + name);
        System.out.println("Salary: $" + salary);
    }

    // Abstract method
    abstract void work();
}

class Manager extends Employee {
    private String department;

    Manager(String name, double salary, String department) {
        super(name, salary);
        this.department = department;
        System.out.println("Manager constructor: Assigned to " + department);
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Department: " + department);
    }

    @Override
    void work() {
        System.out.println(name + " is managing the " + department + " team");
    }
}

class Developer extends Employee {
    private String language;

    Developer(String name, double salary, String language) {
        super(name, salary);
        this.language = language;
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Language: " + language);
    }

    @Override
    void work() {
        System.out.println(name + " is coding in " + language);
    }
}

// Abstract class with multiple constructors
abstract class Product {
    protected String name;
    protected double price;

    // Constructor with all params
    Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // Constructor with default price
    Product(String name) {
        this(name, 0.0);  // Calls other constructor
    }

    void displayInfo() {
        System.out.println("Product: " + name);
        System.out.println("Price: $" + price);
    }

    abstract double calculateTax();
}

class Book extends Product {
    private String author;

    Book(String name, double price, String author) {
        super(name, price);
        this.author = author;
    }

    Book(String name) {
        super(name);  // Uses Product(String) constructor
        this.author = "Unknown";
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Author: " + author);
    }

    @Override
    double calculateTax() {
        return price * 0.05;  // 5% tax on books
    }
}

Subclass constructor calls super() to initialize parent's fields.

Abstract class hierarchies

Chain abstract classes for layered abstraction.

AbstractChain.java
// Abstract Class Hierarchies

public class AbstractChain {
    public static void main(String[] args) {
        System.out.println("=== Abstract Class Hierarchy ===\n");

        // Create concrete vehicles
        Car car = new Car("Toyota", "Camry", 4);
        Motorcycle bike = new Motorcycle("Harley", "Sportster", true);

        System.out.println("--- Car ---");
        car.displayInfo();
        car.start();
        car.accelerate();
        car.brake();

        System.out.println("\n--- Motorcycle ---");
        bike.displayInfo();
        bike.start();
        bike.accelerate();
        bike.brake();

        System.out.println("\n=== Hierarchy Structure ===");
        System.out.println("""

        Vehicle (abstract)           ← Base abstract class
            │
            ├── MotorVehicle (abstract) ← Still abstract!
            │       │
            │       ├── Car (concrete)
            │       └── Motorcycle (concrete)
            │
            └── Bicycle (concrete)    ← Implements all abstract

        """);

        System.out.println("=== Bicycle (extends Vehicle directly) ===\n");

        Bicycle bicycle = new Bicycle("Trek", "FX3");
        bicycle.displayInfo();
        bicycle.start();
        bicycle.accelerate();
        bicycle.brake();
    }
}

// Level 1: Top abstract class
abstract class Vehicle {
    protected String brand;
    protected String model;

    Vehicle(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }

    void displayInfo() {
        System.out.println("Brand: " + brand);
        System.out.println("Model: " + model);
    }

    // Abstract methods - ALL subclasses must implement
    abstract void start();
    abstract void accelerate();
    abstract void brake();
}

// Level 2: Abstract subclass
abstract class MotorVehicle extends Vehicle {
    protected String engineType;

    MotorVehicle(String brand, String model, String engine) {
        super(brand, model);
        this.engineType = engine;
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Engine: " + engineType);
    }

    // Implement ONE abstract method
    @Override
    void start() {
        System.out.println("Starting " + engineType + " engine...");
    }

    // Leave accelerate() and brake() still abstract!
    // Subclasses must implement them
}

// Level 3: Concrete class
class Car extends MotorVehicle {
    private int numDoors;

    Car(String brand, String model, int doors) {
        super(brand, model, "V6");
        this.numDoors = doors;
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Doors: " + numDoors);
    }

    // Must implement remaining abstract methods
    @Override
    void accelerate() {
        System.out.println("Car accelerating smoothly...");
    }

    @Override
    void brake() {
        System.out.println("Car braking with ABS...");
    }
}

class Motorcycle extends MotorVehicle {
    private boolean hasSidecar;

    Motorcycle(String brand, String model, boolean sidecar) {
        super(brand, model, "V-Twin");
        this.hasSidecar = sidecar;
    }

    @Override
    void displayInfo() {
        super.displayInfo();
        System.out.println("Sidecar: " + (hasSidecar ? "Yes" : "No"));
    }

    @Override
    void accelerate() {
        System.out.println("Motorcycle accelerating fast!");
    }

    @Override
    void brake() {
        System.out.println("Motorcycle braking...");
    }
}

// Alternative: Extends Vehicle directly
class Bicycle extends Vehicle {

    Bicycle(String brand, String model) {
        super(brand, model);
    }

    // Must implement ALL THREE abstract methods
    @Override
    void start() {
        System.out.println("Getting on the bicycle...");
    }

    @Override
    void accelerate() {
        System.out.println("Pedaling faster!");
    }

    @Override
    void brake() {
        System.out.println("Squeezing hand brakes...");
    }
}

Abstract class can extend another abstract class. Concrete class must implement all.

Template method pattern

Define algorithm structure, let subclasses fill in steps.

TemplateMethod.java
// Template Method Design Pattern

public class TemplateMethod {
    public static void main(String[] args) {
        System.out.println("=== Template Method Pattern ===\n");

        System.out.println("--- Making Coffee ---");
        Beverage coffee = new Coffee();
        coffee.prepare();

        System.out.println("\n--- Making Tea ---");
        Beverage tea = new Tea();
        tea.prepare();

        System.out.println("\n=== Pattern Explanation ===");
        System.out.println("""
        Template Method Pattern:

        1. Abstract class defines the ALGORITHM skeleton
        2. Some steps are concrete (shared)
        3. Some steps are abstract (customized)
        4. Subclasses fill in the blanks

        prepare() is the TEMPLATE:
        - boilWater()     ← Same for all
        - brew()          ← Different for each
        - pourInCup()     ← Same for all
        - addCondiments() ← Different for each
        """);

        System.out.println("=== Another Example: Report Generator ===\n");

        Report pdf = new PDFReport();
        Report html = new HTMLReport();

        System.out.println("--- PDF Report ---");
        pdf.generate();

        System.out.println("\n--- HTML Report ---");
        html.generate();
    }
}

// Template Method in Beverage class
abstract class Beverage {

    // TEMPLATE METHOD - defines the algorithm
    final void prepare() {
        boilWater();       // Concrete - same for all
        brew();            // Abstract - subclass implements
        pourInCup();       // Concrete - same for all
        addCondiments();   // Abstract - subclass implements
    }

    // Concrete methods - shared implementation
    void boilWater() {
        System.out.println("1. Boiling water");
    }

    void pourInCup() {
        System.out.println("3. Pouring into cup");
    }

    // Abstract methods - subclass provides
    abstract void brew();
    abstract void addCondiments();
}

class Coffee extends Beverage {

    @Override
    void brew() {
        System.out.println("2. Dripping coffee through filter");
    }

    @Override
    void addCondiments() {
        System.out.println("4. Adding sugar and milk");
    }
}

class Tea extends Beverage {

    @Override
    void brew() {
        System.out.println("2. Steeping tea bag");
    }

    @Override
    void addCondiments() {
        System.out.println("4. Adding lemon and honey");
    }
}

// Another Template Method example
abstract class Report {

    // Template method
    final void generate() {
        openDocument();
        writeHeader();    // Abstract
        writeContent();   // Abstract
        writeFooter();    // Abstract
        closeDocument();
    }

    void openDocument() {
        System.out.println("Opening document...");
    }

    void closeDocument() {
        System.out.println("Closing document.");
    }

    // Abstract steps
    abstract void writeHeader();
    abstract void writeContent();
    abstract void writeFooter();
}

class PDFReport extends Report {

    @Override
    void writeHeader() {
        System.out.println("Writing PDF header with metadata");
    }

    @Override
    void writeContent() {
        System.out.println("Writing PDF content with images");
    }

    @Override
    void writeFooter() {
        System.out.println("Writing PDF footer with page numbers");
    }
}

class HTMLReport extends Report {

    @Override
    void writeHeader() {
        System.out.println("Writing <head> with CSS");
    }

    @Override
    void writeContent() {
        System.out.println("Writing <body> with HTML tags");
    }

    @Override
    void writeFooter() {
        System.out.println("Writing <footer> with links");
    }
}

Concrete method calls abstract methods - subclasses provide the specifics.

template method Algorithm skeleton in parent. Specific steps implemented by subclasses.

Exercise: Practical.java

Build a document processing system with abstract classes