โหมดมืด
บทที่ 6.3 — Interface & Composition
📚 บทที่ 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
| Interface | Abstract class | |
|---|---|---|
| Field | constants เท่านั้น — ทุก field เป็น public static final อัตโนมัติ ห้ามมี instance field หรือ mutable static field | field ปกติได้ |
| Method | abstract / 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 staticmethod ได้เพื่อแชร์ 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 ไม่ได้
javaMathOps 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);
}
}⚠️
privatemethod ใน 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 ชนกัน):
class wins — method ใน superclass ชนะ default method
specific interface wins — sub-interface ชนะ super-interface
ตัวอย่าง: ถ้า
B extends A(ทั้งคู่เป็น interface) แล้วทั้ง 2 มี defaultname()—B.name()ชนะเพราะเฉพาะเจาะจงกว่า (B extends A — B อยู่ใกล้ class ที่ implement มากกว่า)ถ้ายังคลุมเครือ → ต้อง 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 ที่ใช้ทุกแนวคิดร่วมกัน