ข้ามไปยังเนื้อหา

Mixins & Composition

single inheritance บังคับให้ทุกความสามารถอยู่ใน chain เดียวที่แข็งทื่อ ถ้า Timestamped, Serializable และ Comparable เป็น behavior ที่คุณอยากแชร์ข้าม class ที่ไม่เกี่ยวกัน hierarchy แบบเส้นตรงแสดงออกไม่ได้ — คุณต้องเลือก parent ตัวเดียวแล้ว duplicate ที่เหลือ

mixin แก้ปัญหานี้ด้วยการทำให้ behavior เป็น function ที่เพิ่ม feature ให้ base class ใดก็ได้ ดังนั้น class หนึ่งจึง compose ได้หลายอัน

mixin คือ function ที่รับ base class (constructor) แล้ว return class ใหม่ที่ extends base นั้นพร้อม behavior เพิ่ม

type Constructor = new (...args: any[]) => {};
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
};
}
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false;
activate() { this.isActive = true; }
};
}
class User {
constructor(public name: string) {}
}
// Compose several behaviors onto User:
const SmartUser = Activatable(Timestamped(User));
const u = new SmartUser("Ada");
u.createdAt; // from Timestamped
u.activate(); // from Activatable
u.name; // from User

constraint แบบ generic TBase extends Constructor คือสิ่งที่ทำให้ mixin type-safe: constraint นี้บอกว่า “class ไหนก็ตามที่คุณส่งเข้ามา ฉัน return class ที่มี member ของ class นั้นทั้งหมดบวกตัวใหม่” TypeScript track shape ที่สะสมผ่านแต่ละชั้นการ wrap

flowchart LR
  base["class User"] --> m1["Timestamped(...)
+ createdAt"]
  m1 --> m2["Activatable(...)
+ activate()"]
  m2 --> result["SmartUser
has all three"]
inheritance เป็น chain ส่วน mixin compose

บางที mixin ต้องการให้ base class มี member บางอย่างอยู่แล้ว constrain base ให้เป็น constructor ที่ produce shape ที่ต้องการ:

type Positioned = new (...args: any[]) => { x: number; y: number };
function Movable<TBase extends Positioned>(Base: TBase) {
return class extends Base {
move(dx: number, dy: number) {
this.x += dx; // ✅ allowed — the constraint guarantees x exists
this.y += dy;
}
};
}

ตอนนี้ Movable apply ได้แค่กับ base ที่ instance มี x และ y และภายใน mixin นั้น member เหล่านี้ถือว่ามีอยู่แน่นอน

mixin เป็นรูปแบบหนึ่งของหลักการที่กว้างกว่า: เลือกประกอบ behavior จากชิ้นเล็ก ๆ ที่อิสระต่อกัน แทนการ derive ผ่าน hierarchy ที่แข็งทื่อ บ่อยครั้งคุณไม่ต้องใช้ mixin ด้วยซ้ำ — plain object composition (ถือ collaborator เป็น field แล้ว delegate งานให้) ง่ายกว่าและเลี่ยงกลไก class-factory ไปเลย ใช้ mixin เมื่อคุณต้องการให้ผลลัพธ์ที่ compose แล้ว เป็น class ที่มีทุก behavior merge อยู่บน instance ของตัวเองจริง ๆ

mixin ใน TypeScript คืออะไร?
ทำไม mixin ใช้ generic constraint แบบ `TBase extends new (...args: any[]) => {}`?
mixin แก้ปัญหาอะไรที่ single inheritance แก้ไม่ได้?
เมื่อไร plain object composition ดีกว่า mixin?