Skip to content

บทที่ 6.3 — Interface & Composition

← บทที่ 6.2 | สารบัญ | บทที่ 7: Collections →

📚 บทที่ 6 (OOP ขั้นต่อ) แบ่งเป็น 3 ตอน — ตอนนี้คือ 6.3: 6.1 Inheritance · 6.2 Polymorphism & Abstract · 6.3 Interface & Composition

ปิดท้ายบทที่ 6 ด้วย interface (สัญญาที่ implement ได้หลายอัน), หลักการ composition over inheritance และ sealed class


1. Interface — "สัญญา" ไม่ใช่ class

interface = "ระบุว่า class ที่ implement ต้องมี method อะไรบ้าง" — ไม่มี instance state (instance state = field ที่แต่ละ object เก็บค่าต่างกันได้ เหมือน field ปกติใน class ทั่วไป — interface ห้ามมีสิ่งนี้ มีได้แค่ค่าคงที่ public static final) และ method ส่วนใหญ่ไม่มี body — แต่ตั้งแต่ Java 8 interface มี default method และ static method ที่มี body ได้ (รายละเอียดใน section 1.3–1.5)

เริ่มจากตัวอย่างง่ายสุด — 1 interface, 1 class:

java
interface Swimmable {
    void swim();    // implicit (โดยปริยาย): public abstract  → Java เติม "public abstract" ให้อัตโนมัติ ไม่ต้องเขียนเอง
}

class Fish implements Swimmable {
    @Override
    public void swim() {
        System.out.println("Fish swimming");
    }
}

implements = "สัญญาว่าจะทำตาม interface นี้" (เหมือน extends แต่ใช้กับ interface)

ขยายต่อ — class เดียว implement หลาย interface ได้:

java
interface Flyable {
    void fly();
}

class Duck implements Swimmable, Flyable {
    @Override
    public void swim() {
        System.out.println("Duck swimming");
    }
    
    @Override
    public void fly() {
        System.out.println("Duck flying");
    }
}

1.1 ทำไม interface

  • Java ห้าม extends หลาย class (multiple inheritance ของ class — การสืบทอด class แม่หลายตัวพร้อมกัน ซึ่ง Java ไม่รองรับเพื่อหลีกเลี่ยงความสับสน)
  • แต่ implement หลาย interface ได้ — Duck เป็นทั้ง Swimmable และ Flyable

1.2 Interface vs Abstract class

InterfaceAbstract class
Fieldconstants เท่านั้น — ทุก field เป็น public static final อัตโนมัติ ห้ามมี instance field หรือ mutable static fieldfield ปกติได้
Methodabstract / default / static / private (Java 9+)abstract + concrete
Constructor
Multi-inherit✅ implement หลายอัน❌ extends ได้แค่ตัวเดียว
State
ใช้เมื่อ"ทำอะไรได้" (capability)"เป็นอะไร" (is-a) แบ่งปัน code

📝 Interface field: เขียนแค่ int x = 5; Java เติม public static final ให้อัตโนมัติ — ไม่สามารถมี instance field หรือ mutable field ได้เลย
📝 Private method (Java 9+): interface มี private/private static method ได้เพื่อแชร์ logic ภายใน default/static method — ดูตัวอย่างใน section 1.5

1.3 Default method (Java 8+)

Interface มี method ที่มี body ได้ (สำหรับ backward compatibility — ความเข้ากันได้กับโค้ดเดิม — เพิ่ม method ใหม่ใน interface โดยไม่ทำลาย class ที่ implement ไปแล้ว):

java
interface Greeter {
    String greet();
    
    default void greetLoudly() {
        System.out.println(greet().toUpperCase() + "!!!");
    }
}

1.4 Static method ใน interface

java
interface MathOps {
    static int square(int n) {
        return n * n;
    }
}

int result = MathOps.square(5);

⚠️ Pitfall — เรียก static method ของ interface ผ่าน instance ไม่ได้

java
MathOps ops = new SomeMathOps();
ops.square(5);         // ❌ compile error — static interface method เรียกผ่าน instance ไม่ได้
MathOps.square(5);     // ✅ ต้องเรียกผ่านชื่อ interface เสมอ

static method ของ interface ไม่ถูก inherit ไปยัง class ที่ implement (ต่างจาก static method ของ class ที่ subclass inherit ได้)

1.5 Private method ใน interface (Java 9+) — share code ระหว่าง default method

ก่อน Java 9: ถ้า default method 2 ตัวมี logic ซ้ำกัน → copy-paste, ไม่งั้นต้องเป็น public default ที่คนเรียกได้

Java 9+ มี private (และ private static) ใน interface — internal helper:

java
interface Logger {
    default void info(String msg)  { log("INFO",  msg); }
    default void warn(String msg)  { log("WARN",  msg); }
    default void error(String msg) { log("ERROR", msg); }

    private void log(String level, String msg) {              // ✅ Java 9+
        System.out.println("[" + level + "] " + msg);
    }
}

⚠️ private method ใน interface เรียกได้แค่ใน default/static method ภายใน interface เดียวกัน — class ที่ implement เห็นไม่ได้

1.6 ปัญหา diamond — เมื่อ default method ของ 2 interface ชนกัน (Default method conflict)

(เรียกว่า diamond เพราะ diagram การสืบทอดเป็นรูปเพชร — A อยู่บน, B และ C รับมาจาก A, D รับมาจาก B และ C)

ถ้า class implement 2 interface ที่มี default method ชื่อเดียวกัน → compile error ต้อง override:

java
interface A { default String name() { return "A"; } }
interface B { default String name() { return "B"; } }

class C implements A, B {                                     // ❌ compile error
}

class D implements A, B {                                     // ✅ ต้อง override
    @Override
    public String name() {
        return A.super.name() + B.super.name();               // เรียก default ของ A/B ได้
    }
}

กฎ resolution (ใช้เมื่อ method ชนกัน):

  1. class wins — method ใน superclass ชนะ default method

  2. specific interface wins — sub-interface ชนะ super-interface

    ตัวอย่าง: ถ้า B extends A (ทั้งคู่เป็น interface) แล้วทั้ง 2 มี default name()B.name() ชนะเพราะเฉพาะเจาะจงกว่า (B extends A — B อยู่ใกล้ class ที่ implement มากกว่า)

  3. ถ้ายังคลุมเครือ → ต้อง override เอง (แล้วเลือกผ่าน A.super.name() ตามตัวอย่างข้างบน)

📖 ทำไม A.super.name() ไม่ใช่ A.name(): A.name() = "เรียก method ของ class A" → แต่ A เป็น interface ไม่มี instance ของตัวเอง A.super.name() = "ในบริบทของ class นี้ที่ implement A ให้เรียก default method ที่มาจาก A" — syntax พิเศษเพื่อบอกเฉพาะเจาะจง

1.7 Functional interface — interface ที่มี method เดียว

java
@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

ใช้กับ lambda — เดี๋ยวบทที่ 9


2. Composition over Inheritance (การประกอบกันแทนการสืบทอด)

หลักการสำคัญ: อย่า inheritance พร่ำเพรื่อ — บางทีใช้ "composition" (มี object ของ class อื่นเป็น field) ดีกว่า

Inheritance (is-a)

"Dog is an Animal" — ใช้ extends ดี

Composition (has-a)

"Car has an Engine" — ใช้ field ดีกว่า

java
// ❌ inheritance ผิด
class Car extends Engine { ... }    // Car ไม่ใช่ Engine!

// ✅ composition ถูก
class Car {
    private Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
    
    public void start() {
        engine.start();
    }
}

ข้อดี composition:

  • ยืดหยุ่น — เปลี่ยน Engine ได้
  • ไม่ผูกแน่นกับ parent
  • test ง่าย — mock Engine ได้

💡 Rule of thumb: ถาม "is-a" หรือ "has-a" — ถ้า "has-a" ใช้ composition


3. Sealed class/interface — ควบคุมว่าใครสืบทอดได้ (Java 17+)

ก่อน Java 17 — class มี 2 mode: เปิด (ใครก็ extend ได้) หรือ final (ห้าม)
Java 17 มี mode กลาง: sealed — extend ได้แค่ class ที่ระบุ

java
public sealed interface Shape permits Circle, Square, Triangle {}

public final class Circle implements Shape { /* ... */ }
public final class Square implements Shape { /* ... */ }
public non-sealed class Triangle implements Shape { /* sub ของ Triangle ไม่ถูก seal */ }

permitted subclass (class ที่ได้รับอนุญาตให้สืบทอด) ต้องเป็น final, sealed (ล็อกต่อ — ระบุชื่อ class ที่ extend ได้ในระดับถัดไป), หรือ non-sealed (เปิดล็อก — ใครก็ extend ได้ต่อ ไม่ถูก seal อีก)

ทำไมต้องมี:

  • Exhaustive pattern matching (การ match ครบทุกกรณีโดย compiler ตรวจสอบให้) — compiler รู้ว่ามี subclass ครบแล้ว ไม่ต้องเขียน default ใน switch (บทที่ 10)
  • API safety — library author ควบคุมว่าใคร extend ได้
  • เป็นทางใช้ Java แสดง "type นี้เป็นได้แค่ A หรือ B หรือ C ไม่มีอย่างอื่น" (concept เดียวกับ enum แต่ละ case มี field ของตัวเอง)

ดูเพิ่มเติม บทที่ 10 — Modern Java


4. Checkpoint

🛠️ Checkpoint 6.3.1 — interface Comparable

📖 <Book> คืออะไร? — นี่คือ generics (เรียนละเอียดบทที่ 7) ตอนนี้ให้มองว่า Comparable<Book> = "เทียบกับ Book ด้วยกันได้" วงเล็บแหลม <...> บอก type ที่จะใช้ทำงานด้วย — ถ้ายังไม่คุ้น ให้ copy รูปแบบ implements Comparable<Book> และ compareTo(Book other) ตามที่กำหนดไว้ได้เลย บทที่ 7 จะอธิบายว่า <Book> หมายถึงอะไร

Java มี interface Comparable<T> พร้อม method int compareTo(T other) — return ติดลบถ้า this < other, 0 ถ้าเท่า, บวกถ้ามากกว่า

ทำให้ class Book (จาก checkpoint 5.4) compareTo เทียบโดย title

java
class Book implements Comparable<Book> {
    String title;
    ...
    
    @Override
    public int compareTo(Book other) {
        return ???;
    }
}
📖 เฉลย
java
class Book implements Comparable<Book> {
    private String title;
    private String author;
    
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }
    
    @Override
    public int compareTo(Book other) {
        return this.title.compareTo(other.title);    // String มี compareTo อยู่แล้ว
    }
    
    @Override
    public String toString() {
        return title + " by " + author;
    }
}

// ใช้ (เรียงด้วย bubble sort เพื่อให้ run ได้โดยไม่ต้องรู้ Collections จากบทที่ 7)
// วางโค้ดส่วนนี้ใน class Main และ main method เพื่อ compile ได้
public class Main {
    public static void main(String[] args) {
        Book[] books = {
            new Book("Brave New World", "Huxley"),
            new Book("1984", "Orwell"),
            new Book("Animal Farm", "Orwell")
        };

        // bubble sort อย่างง่าย — ใช้ compareTo ที่เพิ่งเขียน
        for (int i = 0; i < books.length - 1; i++) {
            for (int j = 0; j < books.length - 1 - i; j++) {
                if (books[j].compareTo(books[j + 1]) > 0) {
                    Book temp = books[j];
                    books[j] = books[j + 1];
                    books[j + 1] = temp;
                }
            }
        }
        for (Book b : books) System.out.println(b);

        // หมายเหตุ: บทที่ 7 จะสอน Collections.sort(list) ซึ่งเรียก compareTo เหมือนกัน แต่สั้นกว่า
    }
}

🛠️ Checkpoint 6.3.2 — interface multiple

สร้าง interface Printable (method print()) และ Saveable (method save()) สร้าง class Document ที่ implement ทั้งสอง

📖 เฉลย
java
interface Printable {
    void print();
}

interface Saveable {
    void save();
}

class Document implements Printable, Saveable {
    private String content;
    
    public Document(String content) {
        this.content = content;
    }
    
    @Override
    public void print() {
        System.out.println("Printing: " + content);
    }
    
    @Override
    public void save() {
        System.out.println("Saving to disk");
    }
}

5. สรุปบท 6.3 (interface)

✅ Interface = "สัญญา" — abstract method + ค่า constants (public static final implicit) + default/static/private method (Java 8+/9+) ✅ class implements I1, I2, ... — ใช้หลาย interface ได้ (ต่างจาก extends ที่ได้แค่ 1 class) ✅ Default method (Java 8+) — เพิ่ม method ใหม่ใน interface เดิมโดยไม่ทำให้ class ที่ implement ไปแล้วพัง (break existing implementer) (เหตุที่ Java 8 เพิ่ม Collection.stream() ได้) ✅ Static method (Java 8+) — utility ใน interface, เรียกผ่าน InterfaceName.method() เท่านั้น ไม่ inherit — ทั้ง class ที่ implement และ sub-interface ไม่สามารถ inherit static method ของ interface ได้ (ต่างจาก class ที่ subclass inherit static method ได้) ✅ Private method (Java 9+) — helper ภายใน interface ใช้ได้แค่ใน default/static method ของ interface เดียวกัน ไม่ถูก inherit ✅ Diamond problem Java แก้ด้วย rule: class > interface, ถ้าชนกันต้อง override + ใช้ A.super.method()Sealed interface (Java 17+) — จำกัด permits + คู่กับ pattern matching ✅ Functional interface (1 abstract method) — ใช้ lambda ได้

📚 สรุปรวมของ Inheritance + Polymorphism + Interface ทั้ง 3 บท: → ดู checkpoint บทที่ 7 ที่ใช้ทุกแนวคิดร่วมกัน

→ ไปบทที่ 7: Collections + Generics