Polymorphism ยืดหยุ่นต่อการเปลี่ยน Type

บทนำ: ปัญหาของการจัดการ Multiple Types

เมื่อเรามี objects หลายประเภท (subclasses) ที่ต่างกัน บ่อยครั้งเราต้องจัดการกับทั้งหมดอย่างเดียวกัน แต่แต่ละตัวมี behavior ที่แตกต่างกัน

java// ❌ ปัญหา: ต้องเขียนแยกสำหรับแต่ละประเภท
public class AnimalHandler {
    public static void makeAllAnimalsSound(Dog dog) {
        dog.makeSound();
    }
    
    public static void makeAllAnimalsSound(Cat cat) {
        cat.makeSound();
    }
    
    public static void makeAllAnimalsSound(Bird bird) {
        bird.makeSound();
    }
    // ... ต้องสร้าง method ใหม่ทุกครั้งที่มี animal type ใหม่!
}

// ใช้งาน
Dog dog = new Dog();
Cat cat = new Cat();
Bird bird = new Bird();

AnimalHandler.makeAllAnimalsSound(dog);
AnimalHandler.makeAllAnimalsSound(cat);
AnimalHandler.makeAllAnimalsSound(bird);

ปัญหา:

  • ❌ Code ซ้ำซ้อน
  • ❌ ต้องเขียน method ใหม่สำหรับแต่ละ type
  • ❌ ไม่ยืดหยุ่นเมื่อมี type ใหม่เพิ่มเข้ามา
  • ❌ โปรแกรม “scale” ไม่ได้

Polymorphism คืออะไร?

Polymorphism (poly = หลาย, morph = รูป) = ความสามารถที่ object สามารถมีหลายรูป และ ใช้ได้ในหลาย context ด้วย method เดียว

textก่อน Polymorphism:
┌──────────┐ ┌──────────┐ ┌──────────┐
│   Dog    │ │   Cat    │ │   Bird   │
└──────────┘ └──────────┘ └──────────┘
   แยก        แยก        แยก

หลัง Polymorphism:
┌──────────────────────────────────┐
│        Animal (Reference)         │ ← ใช้เดียว!
│  ├─ Dog object                   │
│  ├─ Cat object                   │
│  └─ Bird object                  │
└──────────────────────────────────┘

Polymorphism ผ่าน Reference

Upcasting: Assign Subclass ให้ Superclass Reference

java// ✓ Polymorphism: ใช้ parent reference ชี้ไป child object
Animal animal = new Dog();      // Dog IS-A Animal
animal = new Cat();              // Cat IS-A Animal
animal = new Bird();             // Bird IS-A Animal

// ทั้งหมด "ดู" เหมือน Animal
// แต่ทำ behavior ของตัวเอง (ผ่าน overriding)

ตัวอย่างที่ 1: Polymorphism – Loop ผ่านหลาย Types

java// SUPERCLASS
public class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void makeSound() {
        System.out.println("Animal sound");
    }
    
    public void sleep() {
        System.out.println(name + " is sleeping");
    }
}

// SUBCLASS 1
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Woof!");
    }
}

// SUBCLASS 2
public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Meow!");
    }
}

// SUBCLASS 3
public class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Tweet!");
    }
}

// ใช้งาน - POLYMORPHIC
public class Main {
    public static void main(String[] args) {
        // ✓ สร้าง array ของ Animal (superclass reference)
        Animal[] animals = new Animal[3];
        
        // ✓ Assign subclass objects
        animals[0] = new Dog("Rex");
        animals[1] = new Cat("Whiskers");
        animals[2] = new Bird("Tweety");
        
        // ✓ Loop ผ่านหมด - polymorphic!
        System.out.println("=== Making Sounds ===");
        for (Animal animal : animals) {
            animal.makeSound();  // ← Method เดียว แต่ behavior ต่างกัน!
        }
        
        System.out.println("\n=== Sleeping ===");
        for (Animal animal : animals) {
            animal.sleep();  // ← ใช้ parent method
        }
    }
}

Output:

text=== Making Sounds ===
Rex says: Woof!
Whiskers says: Meow!
Tweety says: Tweet!

=== Sleeping ===
Rex is sleeping
Whiskers is sleeping
Tweety is sleeping

คำอธิบาย:

  • Animal[] animals – array ของ superclass reference
  • แต่ละ element ชี้ไปยัง ต่างกัน subclass object
  • animal.makeSound() – loop เดียว แต่ output ต่างกัน ตามประเภท object
  • นี่คือ Polymorphism!

ตัวอย่างที่ 2: Polymorphic Methods

java// SUPERCLASS
public class Shape {
    protected String name;
    
    public Shape(String name) {
        this.name = name;
    }
    
    public double getArea() {
        return 0;
    }
    
    public void display() {
        System.out.printf("%s: Area = %.2f\n", name, getArea());
    }
}

// SUBCLASS 1
public class Circle extends Shape {
    private double radius;
    
    public Circle(String name, double radius) {
        super(name);
        this.radius = radius;
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

// SUBCLASS 2
public class Rectangle extends Shape {
    private double width, height;
    
    public Rectangle(String name, double w, double h) {
        super(name);
        this.width = w;
        this.height = h;
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
}

// SUBCLASS 3
public class Triangle extends Shape {
    private double base, height;
    
    public Triangle(String name, double b, double h) {
        super(name);
        this.base = b;
        this.height = h;
    }
    
    @Override
    public double getArea() {
        return (base * height) / 2;
    }
}

// ✓ Polymorphic method - ทำงานกับทุก shapes
public class ShapeCalculator {
    public static void printShapeInfo(Shape shape) {
        shape.display();  // ← Polymorphic call
    }
    
    public static double getTotalArea(Shape[] shapes) {
        double total = 0;
        for (Shape shape : shapes) {
            total += shape.getArea();  // ← Polymorphic call
        }
        return total;
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        // ✓ สร้าง shapes
        Shape circle = new Circle("Circle", 5);
        Shape rectangle = new Rectangle("Rectangle", 4, 6);
        Shape triangle = new Triangle("Triangle", 3, 4);
        
        // ✓ ใช้ polymorphic method
        System.out.println("=== Individual Shapes ===");
        ShapeCalculator.printShapeInfo(circle);
        ShapeCalculator.printShapeInfo(rectangle);
        ShapeCalculator.printShapeInfo(triangle);
        
        // ✓ ใช้ polymorphic method กับ array
        Shape[] shapes = {circle, rectangle, triangle};
        System.out.println("\n=== Total Area ===");
        double total = ShapeCalculator.getTotalArea(shapes);
        System.out.printf("Total Area: %.2f\n", total);
    }
}

Output:

text=== Individual Shapes ===
Circle: Area = 78.54
Rectangle: Area = 24.00
Triangle: Area = 6.00

=== Total Area ===
Total Area: 108.54

คำอธิบาย:

  • ShapeCalculator.printShapeInfo(Shape shape) – ยอมรับ superclass reference
  • สามารถ pass ได้ว่า Circle, Rectangle, หรือ Triangle
  • shape.getArea() ทำงานต่างกัน ตามประเภท object จริง
  • Method เดียว แต่ยืดหยุ่นกับทุก shapes!

Benefits ของ Polymorphism

ยืดหยุ่นต่อการเปลี่ยน

text✓ เพิ่ม type ใหม่ ไม่ต้องแก้ code เก่า
✓ Method เดียว ทำงานกับทุก types
✓ Code ประหยัด
✓ ง่ายต่อการดูแล

ตัวอย่างที่ 3: เพิ่ม Type ใหม่ไม่ต้องแก้ Code เก่า

java// เดิม: Circle, Rectangle, Triangle

// เพิ่ม type ใหม่
public class Pentagon extends Shape {
    private double side;
    
    public Pentagon(String name, double side) {
        super(name);
        this.side = side;
    }
    
    @Override
    public double getArea() {
        return (side * side * Math.sqrt(25 + 10 * Math.sqrt(5))) / 4;
    }
}

// ใช้งาน - ShapeCalculator.java ไม่ต้องแก้เลย!
public class Main {
    public static void main(String[] args) {
        Shape[] shapes = {
            new Circle("Circle", 5),
            new Rectangle("Rectangle", 4, 6),
            new Triangle("Triangle", 3, 4),
            new Pentagon("Pentagon", 5)  // ← Type ใหม่!
        };
        
        // ✓ Code เดิมทำงาน ด้วย type ใหม่!
        double total = ShapeCalculator.getTotalArea(shapes);
        System.out.printf("Total Area: %.2f\n", total);
    }
}

ข้อดี:

  • ✓ ไม่แก้ ShapeCalculator class
  • ✓ ไม่แก้ loop code
  • ✓ Polymorphism ทำให้ extensible!

Polymorphism ผ่าน Collections

List ด้วย Superclass Reference

javaimport java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // ✓ List ของ superclass reference
        List<Animal> animals = new ArrayList<>();
        
        // ✓ Add ต่างกัน subclass objects
        animals.add(new Dog("Rex"));
        animals.add(new Cat("Whiskers"));
        animals.add(new Dog("Buddy"));
        animals.add(new Bird("Tweety"));
        animals.add(new Cat("Fluffy"));
        
        // ✓ Loop - polymorphic
        System.out.println("=== All Animals ===");
        for (Animal animal : animals) {
            animal.makeSound();
        }
    }
}

Output:

text=== All Animals ===
Rex says: Woof!
Whiskers says: Meow!
Buddy says: Woof!
Tweety says: Tweet!
Fluffy says: Meow!

Type Checking & Casting ใน Polymorphism

❓ ถ้าต้องเข้าถึง Subclass-specific Methods?

javaAnimal animal = new Dog("Rex");

// ❌ ไม่ได้ - Dog-specific method
// animal.fetchBall();  // ERROR!

// ✓ ต้อง cast กลับเป็น Dog
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
    dog.fetchBall();
}

ตัวอย่างที่ 4: Polymorphism ด้วย instanceof

java// SUPERCLASS
public class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void makeSound() {
        System.out.println("Animal sound");
    }
}

// SUBCLASS 1
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Woof!");
    }
    
    public void fetchBall() {
        System.out.println(name + " is fetching the ball");
    }
}

// SUBCLASS 2
public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Meow!");
    }
    
    public void scratchPost() {
        System.out.println(name + " is scratching the post");
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        Animal[] animals = {
            new Dog("Rex"),
            new Cat("Whiskers"),
            new Dog("Buddy")
        };
        
        System.out.println("=== Polymorphic Calls ===");
        for (Animal animal : animals) {
            animal.makeSound();  // ← Polymorphic
        }
        
        System.out.println("\n=== Type-Specific Calls ===");
        for (Animal animal : animals) {
            // ✓ Check type และ cast
            if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.fetchBall();
            } else if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.scratchPost();
            }
        }
    }
}

Output:

text=== Polymorphic Calls ===
Rex says: Woof!
Whiskers says: Meow!
Buddy says: Woof!

=== Type-Specific Calls ===
Rex is fetching the ball
Whiskers is scratching the post
Buddy is fetching the ball

คำอธิบาย:

  • instanceof ตรวจสอบประเภท object จริง
  • Casting ให้ access subclass-specific methods
  • Polymorphism ทำให้ handle ทั้งหมดในลูปเดียว

Real-World Example: Payment System

java// SUPERCLASS
public abstract class PaymentMethod {
    protected String name;
    protected double amount;
    
    public PaymentMethod(String name, double amount) {
        this.name = name;
        this.amount = amount;
    }
    
    public abstract void process();
    public abstract void refund();
    
    public void displayInfo() {
        System.out.printf("Payment: %s | Amount: %.2f\n", name, amount);
    }
}

// SUBCLASS 1
public class CreditCard extends PaymentMethod {
    private String cardNumber;
    
    public CreditCard(String number, double amount) {
        super("Credit Card", amount);
        this.cardNumber = number;
    }
    
    @Override
    public void process() {
        System.out.printf("Processing credit card ending in %s\n", 
                         cardNumber.substring(cardNumber.length() - 4));
        System.out.printf("Charged: %.2f\n", amount);
    }
    
    @Override
    public void refund() {
        System.out.println("Refunding to credit card");
    }
}

// SUBCLASS 2
public class BankTransfer extends PaymentMethod {
    private String accountNumber;
    
    public BankTransfer(String account, double amount) {
        super("Bank Transfer", amount);
        this.accountNumber = account;
    }
    
    @Override
    public void process() {
        System.out.printf("Transferring to account %s\n", accountNumber);
        System.out.printf("Amount: %.2f\n", amount);
    }
    
    @Override
    public void refund() {
        System.out.println("Refunding to bank account");
    }
}

// SUBCLASS 3
public class Wallet extends PaymentMethod {
    private String walletID;
    
    public Wallet(String id, double amount) {
        super("Wallet", amount);
        this.walletID = id;
    }
    
    @Override
    public void process() {
        System.out.printf("Processing wallet %s\n", walletID);
        System.out.printf("Deducted: %.2f\n", amount);
    }
    
    @Override
    public void refund() {
        System.out.println("Refunding to wallet");
    }
}

// ✓ Polymorphic Service
public class PaymentProcessor {
    public static void processPayments(PaymentMethod[] payments) {
        System.out.println("=== Processing Payments ===");
        for (PaymentMethod payment : payments) {
            payment.displayInfo();
            payment.process();
            System.out.println();
        }
    }
    
    public static void refundPayments(PaymentMethod[] payments) {
        System.out.println("=== Refunding Payments ===");
        for (PaymentMethod payment : payments) {
            payment.displayInfo();
            payment.refund();
            System.out.println();
        }
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) {
        PaymentMethod[] payments = {
            new CreditCard("1234-5678-9012-3456", 1000),
            new BankTransfer("123456789", 500),
            new Wallet("WALLET001", 250),
            new CreditCard("9876-5432-1098-7654", 750)
        };
        
        PaymentProcessor.processPayments(payments);
        PaymentProcessor.refundPayments(payments);
    }
}

Output:

text=== Processing Payments ===
Payment: Credit Card | Amount: 1000.00
Processing credit card ending in 3456
Charged: 1000.00

Payment: Bank Transfer | Amount: 500.00
Transferring to account 123456789
Amount: 500.00

Payment: Wallet | Amount: 250.00
Processing wallet WALLET001
Deducted: 250.00

Payment: Credit Card | Amount: 750.00
Processing credit card ending in 7654
Charged: 750.00

=== Refunding Payments ===
Payment: Credit Card | Amount: 1000.00
Refunding to credit card
...

คำอธิบาย:

  • PaymentMethod – abstract superclass
  • CreditCardBankTransferWallet – subclasses ต่างๆ
  • processPayments() – ใช้ polymorphism สำหรับทุก payment types
  • เพิ่ม payment type ใหม่ ไม่ต้องแก้ PaymentProcessor

Polymorphism vs Type Casting

อัน ชี้ลักษณะทั่วไป

textPolymorphism:
└─ ใช้ superclass reference
└─ สามารถชี้ไป subclass objects
└─ Flexible + Safe

Type Casting:
└─ Convert type เมื่อต้องการ
└─ ต้องรู้ประเภท object จริง
└─ Use with instanceof check

Best Practices: Polymorphism

1. ใช้ Superclass Reference ที่เหมาะสม

java// ✓ ดี
List<Animal> animals = new ArrayList<>();

// ❌ ไม่ดี
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
List<Bird> birds = new ArrayList<>();

2. ออกแบบ Superclass ให้ General

java// ✓ ดี: Superclass มีพื้นฐานทั่วไป
public abstract class Vehicle {
    public abstract void start();
    public abstract void stop();
}

// ❌ ไม่ดี: Superclass เฉพาะเจาะจง
public class Car {
    public void startCar() {}
    public void stopCar() {}
}

3. ใช้ instanceof เมื่อต้อง Type-Specific Logic

java// ✓ OK: ต้องการ subclass-specific behavior
if (animal instanceof Dog) {
    ((Dog) animal).fetchBall();
}

// ⚠️ ปัญหา: Loop ต่างกันสำหรับแต่ละ type
for (Dog dog : dogs) {
    dog.fetchBall();
}
for (Cat cat : cats) {
    cat.scratchPost();
}

สรุป

Polymorphism เป็นแนวคิดหนึ่งในสามปรัชญาหลักของ Object-Oriented Programming ที่ให้โปรแกรมของเรา ยืดหยุ่นและขยายได้ง่าย ด้วยการใช้ superclass reference ชี้ไปยัง subclass objects เราสามารถเขียน code ที่ สามารถจัดการหลาย types ได้พร้อมกัน ผ่านวิธีการเดียว

ความสวยงามของ polymorphism อยู่ที่ว่า เมื่อเราเพิ่ม subclass ใหม่ เราไม่ต้องแก้ code เดิมเลย สิ่งที่เปลี่ยนแปลงคือ behavior ของ methods ที่ถูก override ในขณะที่ interface (วิธีการเรียกใช้) ยังคงเหมือนเดิม

การใช้ polymorphism อย่างถูกต้องจะทำให้ code ของเรา maintainablescalable, และ professional เมื่อรวมกับ inheritance และ method overriding polymorphism กลายเป็นพื้นฐานของการเขียน OOP code ที่ดี