Skip to content

บทที่ 08 — Class และ Modifier

← บทที่ 07 | สารบัญ | บทที่ 09: Module + Declaration →

⏱ ใช้เวลา 2–3 ชั่วโมง


บทนี้เรียนอะไร

class มีอยู่ใน JavaScript อยู่แล้ว — TypeScript เพิ่ม "ระบบ type" และ "ตัวกำกับการเข้าถึง" (modifier) เข้าไป ทำให้ class ปลอดภัยและสื่อความหมายมากขึ้น

บทนี้จะสอน:

  • การใส่ type ให้ property และ method ของ class
  • Parameter property — ไวยากรณ์ย่อที่ TypeScript เพิ่มมา
  • Access modifierpublic, private, protected, readonly — และแนวคิด encapsulation (การห่อหุ้มข้อมูล)
  • การสืบทอด (inheritance), abstract class, การ implement interface
  • การใช้ class กับ generic
  • เรื่องที่มืออาชีพถกเถียงกัน — "ควรใช้ class หรือไม่"

💡 บทนี้ถ้าคุณแม่นเรื่อง class ใน JavaScript อยู่แล้ว จะอ่านง่ายมาก — เพราะ TypeScript แค่เพิ่ม type ทับลงไป ถ้ายังไม่แม่น แนะนำทบทวนบท class ใน Javascript ควบคู่ไปด้วย


Part 1: Class พื้นฐานพร้อม type

1.1 หน้าตาของ class ใน TypeScript

typescript
class User {
    name: string;     // ★ ประกาศ property พร้อม type (TypeScript บังคับ)
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    greet(): string {
        return `สวัสดี ฉันชื่อ ${this.name}`;
    }
}

const alice = new User("Alice", 30);
console.log(alice.greet());   // "สวัสดี ฉันชื่อ Alice"

ส่วนที่ TypeScript เพิ่มจาก JavaScript:

  • บรรทัด name: string; และ age: number; — ต้อง "ประกาศ property พร้อม type" ไว้บนสุดของ class (JavaScript ไม่บังคับ แต่ TypeScript บังคับ)
  • type ของพารามิเตอร์ใน constructor
  • return type ของ method (greet(): string)

1.2 ทำไมต้องประกาศ property ไว้บนสุด

ใน JavaScript คุณ assign this.name ได้เลยโดยไม่ต้องประกาศ แต่ TypeScript ต้องรู้ "รูปร่างของ object" ล่วงหน้า — มันจึงต้องการให้เราประกาศ property ทั้งหมดไว้ก่อน เพื่อจะตรวจสอบได้ว่าเราเข้าถึง property ถูกต้อง


Part 2: Parameter Property — ไวยากรณ์ย่อสุดสะดวก

2.1 ปัญหา — โค้ดซ้ำซาก

สังเกตโค้ดใน Part 1 — เราเขียนชื่อ property ถึง 3 ครั้ง สำหรับแต่ละตัว:

typescript
class User {
    name: string;                          // 1. ประกาศ
    constructor(name: string) {            // 2. รับเป็นพารามิเตอร์
        this.name = name;                  // 3. assign
    }
}

ถ้ามี property 10 ตัว โค้ดส่วนนี้จะยาวและน่าเบื่อมาก

2.2 ทางออก — Parameter Property

TypeScript มีไวยากรณ์ย่อพิเศษ — แค่ใส่ access modifier (เช่น public) หน้าพารามิเตอร์ใน constructor มันจะ ประกาศ + รับ + assign ให้อัตโนมัติ:

typescript
class User {
    constructor(
        public name: string,
        public age: number,
    ) {}
    // ไม่ต้องเขียน this.name = name; เลย — TypeScript ทำให้

    greet(): string {
        return `สวัสดี ${this.name}`;
    }
}

const alice = new User("Alice", 30);
console.log(alice.name);   // "Alice" — ใช้ได้ปกติ

โค้ดสั้นลงมาก! public name: string ในตำแหน่งพารามิเตอร์ constructor = "สร้าง property name ที่เป็น public และ assign ค่าให้อัตโนมัติ"

📌 Parameter property เป็นฟีเจอร์เฉพาะของ TypeScript (JavaScript ไม่มี) — ต้องใส่ modifier (public/private/protected/readonly) นำหน้าถึงจะทำงาน ถ้าไม่ใส่จะเป็นแค่พารามิเตอร์ธรรมดา


Part 3: Access Modifier — ควบคุมการเข้าถึง

3.1 แนวคิด — "Encapsulation" (การห่อหุ้ม)

ก่อนเรียนไวยากรณ์ ต้องเข้าใจ "ทำไม" ก่อน

ลองนึกถึงรีโมททีวี — มันมีปุ่มให้คุณกด (เปิด/ปิด, เปลี่ยนช่อง) แต่วงจรอิเล็กทรอนิกส์ข้างในถูก "ห่อ" ไว้ คุณแตะมันไม่ได้ ทำไม? เพราะถ้าใครก็มาแตะวงจรข้างในได้ มันจะพังง่าย

class ก็เหมือนกัน — บาง property/method ควรเป็น "ปุ่มสาธารณะ" ที่ใครก็ใช้ได้ บางอย่างควรเป็น "วงจรภายใน" ที่ซ่อนไว้ การควบคุมแบบนี้เรียกว่า encapsulation และ access modifier คือเครื่องมือทำมัน

3.2 modifier ทั้ง 4 ตัว

typescript
class BankAccount {
    public owner: string;          // เข้าถึงได้จากทุกที่
    private balance: number;       // เข้าถึงได้เฉพาะภายใน class นี้
    protected accountType: string; // เข้าถึงได้ใน class นี้ + class ลูก
    readonly accountNumber: string;// อ่านได้ทุกที่ แต่แก้ไม่ได้

    constructor(owner: string, accountNumber: string) {
        this.owner = owner;
        this.balance = 0;
        this.accountType = "savings";
        this.accountNumber = accountNumber;
    }
}
modifierเข้าถึงจากไหนได้บ้าง
publicทุกที่ (เป็นค่าเริ่มต้น ถ้าไม่ใส่อะไร)
privateเฉพาะภายใน class เดียวกันเท่านั้น
protectedภายใน class นี้ และ class ที่สืบทอดไป
readonlyอ่านได้ทุกที่ แต่กำหนดค่าได้ครั้งเดียว (ตอนสร้าง)

3.3 ตัวอย่างการใช้ private ปกป้องข้อมูล

typescript
class BankAccount {
    private balance: number = 0;

    deposit(amount: number): void {
        if (amount <= 0) throw new Error("จำนวนเงินต้องมากกว่า 0");
        this.balance += amount;
    }

    getBalance(): number {
        return this.balance;
    }
}

const account = new BankAccount();
account.deposit(1000);
console.log(account.getBalance());   // 1000 ✅

account.balance = 999999;   // ❌ Error! balance เป็น private

เห็นประโยชน์ไหม — เราบังคับให้ทุกคน "ฝากเงินผ่าน deposit()" เท่านั้น ซึ่งมีการตรวจสอบ (amount > 0) ไม่มีใครมาแอบแก้ balance ตรง ๆ ได้ ข้อมูลจึงปลอดภัยและน่าเชื่อถือ

3.4 ⚠️ จุดสำคัญ — private ของ TypeScript ไม่ใช่ความลับจริง

นี่คือเรื่องที่มือใหม่ต้องรู้ — private ของ TypeScript มีผลแค่ตอน compile-time เท่านั้น

ทบทวนคำสองคำนี้ก่อน: compile-time = ตอนคอมไพล์ (ช่วงที่ TypeScript แปลงโค้ดเป็น JavaScript และตรวจ type) ส่วน runtime = ตอนรันโปรแกรมจริง (ช่วงที่โค้ดทำงานจริง) — จำเรื่อง type erasure จากบทที่ 00 ได้ไหม type ทั้งหลายจะถูกลบทิ้งก่อนถึง runtime

typescript
const account = new BankAccount();
account.balance = 999;          // ❌ TypeScript ฟ้อง

(account as any).balance = 999; // 😱 แต่ทำแบบนี้ได้! ตอน runtime ไม่มีการป้องกัน

private ของ TypeScript เป็นเหมือน "ป้ายห้ามเข้า" — มันเตือนเราตอนเขียนโค้ด แต่ตอน runtime จริง ๆ ค่ายังเข้าถึงได้

3.5 ความลับจริง ๆ — ใช้ # (private ของ JavaScript)

ถ้าต้องการ "ความลับจริง" ที่ป้องกันได้ทั้งตอน compile และ runtime ให้ใช้ # ซึ่งเป็นฟีเจอร์ของ JavaScript เอง:

typescript
class BankAccount {
    #balance: number = 0;   // ★ ใช้ # นำหน้า = private จริงระดับ runtime

    deposit(amount: number): void {
        this.#balance += amount;
    }

    getBalance(): number {
        return this.#balance;
    }
}

const account = new BankAccount();

// ❌ บรรทัดข้างล่างนี้ "compile ไม่ผ่าน" — เป็น SyntaxError ตั้งแต่ตอน parse:
// (account as any).#balance;
//
// เหตุผล: `#balance` เป็น private field จริงของ JavaScript — ไวยากรณ์ `#ชื่อ`
// อ้างถึงได้เฉพาะ "ภายใน class body ที่ประกาศมัน" เท่านั้น
// การ cast เป็น any ไม่ช่วยอะไรเลย เพราะ `#` ถูกบล็อกที่ระดับไวยากรณ์ (syntactic) ไม่ใช่ระดับ type
//
// ถ้าจะ "พยายาม" เข้าถึง runtime จริง ๆ ก็ทำไม่ได้เช่นกัน:
console.log((account as any).balance);     // undefined — property ชื่อ "balance" (ไม่มี #) ไม่มีอยู่
console.log(Object.keys(account));         // [] — # field ไม่โผล่ใน Object.keys

🎯 คำแนะนำปี 2026: สำหรับ property ที่ต้องการความเป็นส่วนตัวจริง ๆ ใช้ # แทน private เพราะมันป้องกันได้จริงทั้งสองช่วงเวลา ส่วน private/protected/public ของ TypeScript ยังมีประโยชน์ในแง่ "สื่อเจตนา" และใช้กับ parameter property ได้


Part 4: Static Member — สมาชิกของ "class" ไม่ใช่ของ "instance"

ปกติ property/method เป็นของ "แต่ละ instance" (แต่ละ object ที่ new ออกมา) แต่ static member เป็นของ "ตัว class เอง":

typescript
class MathUtils {
    static readonly PI = 3.14159;

    static square(n: number): number {
        return n * n;
    }
}

// เรียกผ่านชื่อ class ตรง ๆ — ไม่ต้อง new
console.log(MathUtils.PI);          // 3.14159
console.log(MathUtils.square(5));   // 25

ใช้ static กับสิ่งที่ "ไม่ผูกกับ instance ใด instance หนึ่ง" เช่น ค่าคงที่ หรือฟังก์ชันช่วยเหลือ


Part 5: Getter และ Setter — property ที่มีลอจิกซ่อน

get และ set ให้เรา "ทำให้ method ใช้งานเหมือน property":

typescript
class Temperature {
    #celsius: number;

    constructor(celsius: number) {
        this.#celsius = celsius;
    }

    // getter — เรียกใช้เหมือนอ่าน property
    get celsius(): number {
        return this.#celsius;
    }

    // setter — เรียกใช้เหมือนเขียน property แต่มีการตรวจสอบ
    set celsius(value: number) {
        if (value < -273.15) {
            throw new Error("ต่ำกว่าศูนย์สัมบูรณ์ไม่ได้");
        }
        this.#celsius = value;
    }

    // getter ที่ "คำนวณ" ค่าใหม่ทุกครั้ง
    get fahrenheit(): number {
        return this.#celsius * 9 / 5 + 32;
    }
}

const temp = new Temperature(25);
console.log(temp.celsius);      // 25     ← เรียก getter (ดูเหมือนอ่าน property)
temp.celsius = 30;              //        ← เรียก setter (ดูเหมือนเขียน property)
console.log(temp.fahrenheit);   // 86     ← getter ที่คำนวณ
temp.celsius = -300;            // ❌ Error! setter ตรวจสอบแล้วโยน error

ข้อดี: คนใช้เขียน temp.celsius = 30 เหมือนเขียน property ธรรมดา แต่จริง ๆ มีการตรวจสอบซ่อนอยู่


Part 6: Inheritance — การสืบทอด

6.1 extends และ super

class สืบทอดจาก class อื่นได้ด้วย extends — class ลูกจะได้ทุกอย่างของ class แม่:

typescript
class Animal {
    constructor(public name: string) {}

    eat(): string {
        return `${this.name} กำลังกิน`;
    }
}

class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name);   // ★ เรียก constructor ของ class แม่ — ต้องเรียกก่อนใช้ this
    }

    bark(): string {
        return `${this.name} เห่า`;   // ใช้ name ที่สืบทอดมาจาก Animal ได้
    }
}

const rex = new Dog("เรกซ์", "ลาบราดอร์");
console.log(rex.eat());   // "เรกซ์ กำลังกิน"  ← method จาก Animal
console.log(rex.bark());  // "เรกซ์ เห่า"      ← method ของ Dog เอง

⚠️ กฎสำคัญ: ใน constructor ของ class ลูก ต้องเรียก super(...) ก่อน ใช้ this เสมอ

6.2 override — เขียน method ทับของ class แม่

class ลูก "เขียนทับ" method ของ class แม่ได้ ควรใส่คำว่า override กำกับให้ชัด (คำกำกับ override ต้องใช้ TypeScript 4.3 ขึ้นไป — ใน TS 5.7 ที่เราใช้ในเล่มนี้รองรับเต็มที่):

typescript
class Dog extends Animal {
    constructor(name: string) {
        super(name);
    }

    override eat(): string {
        // เรียก method เดิมของ class แม่ด้วย super
        return `${super.eat()} อย่างเอร็ดอร่อย`;
    }
}

ทำไมต้องใส่ override? — ในบทที่ 01 เราเปิด noImplicitOverride: true ไว้ (ตัวเลือก tsconfig ที่ "บังคับใช้คำกำกับ override" เมื่อเขียนทับ method) ประโยชน์: ถ้าวันหนึ่งมีคนเปลี่ยนชื่อ method ใน class แม่ (เช่นจาก eat เป็น consume) — method eat ใน class ลูกที่มี override กำกับจะ error ทันที (เพราะไม่มี eat ใน class แม่ให้ override แล้ว) ช่วยให้ refactor ปลอดภัย


Part 7: class แม่แบบ (Abstract Class) — class ที่ "ยังไม่สมบูรณ์"

class แม่แบบ (Abstract Class) คือ class ที่ออกแบบมาให้ "เป็นแม่แบบ" — มันสร้าง instance ตรง ๆ ไม่ได้ ต้องให้ class ลูกมาสืบทอดและเติมส่วนที่ขาด

typescript
abstract class Shape {
    // abstract method — ประกาศแต่ไม่มี body — class ลูก "ต้อง" มา implement
    abstract getArea(): number;

    // method ปกติ — class ลูกใช้ได้เลย
    describe(): string {
        return `รูปทรงนี้มีพื้นที่ ${this.getArea()} ตารางหน่วย`;
    }
}

class Circle extends Shape {
    constructor(private radius: number) {
        super();
    }

    // ต้อง implement getArea ไม่งั้น error
    getArea(): number {
        return Math.PI * this.radius ** 2;
    }
}

const circle = new Circle(5);
console.log(circle.describe());   // ใช้ describe จาก Shape ได้

new Shape();   // ❌ Error! สร้าง instance ของ abstract class ไม่ได้

ประโยชน์: Shape รับประกันว่า "ทุกรูปทรงต้องมี getArea()" — class ลูกที่ลืม implement จะ error ทันที


Part 8: implements (สัญญาว่าจะมีตามรูปแบบ) — สัญญากับ Interface

📎 คำศัพท์: implements = "สัญญาว่าจะมีตามรูปแบบ" ของ interface ส่วน extends = "สืบทอด" จาก class แม่ (รายละเอียดของความต่างอยู่ท้าย Part นี้)

class ... implements Interface คือการบอกว่า "class นี้สัญญาว่าจะมีทุกอย่างตามที่ interface ระบุ":

typescript
interface Drawable {
    draw(): void;
}

interface Resizable {
    resize(scale: number): void;
}

// class หนึ่งตัว implement ได้หลาย interface
class Rectangle implements Drawable, Resizable {
    constructor(private width: number, private height: number) {}

    draw(): void {
        console.log(`วาดสี่เหลี่ยม ${this.width}x${this.height}`);
    }

    resize(scale: number): void {
        this.width *= scale;
        this.height *= scale;
    }
}

ถ้า Rectangle ลืม implement method ใด method หนึ่งที่ interface ระบุ — TypeScript จะ error

extends vs implements ต่างกันยังไง?

  • extendsสืบทอดของจริง จาก class แม่ (ได้ทั้ง type และโค้ด implementation) สืบทอดได้แค่ class เดียว
  • implements — แค่ สัญญาว่าจะมีตามรูปแบบ ของ interface (ไม่ได้โค้ดอะไรมา class ต้องเขียนเอง) แต่ implement ได้หลาย interface

Part 9: Generic Class

จำบทที่ 05 ได้ไหม — class ใช้ generic ได้ ทำให้เป็น "โครงที่ทำงานกับ type อะไรก็ได้":

typescript
class Box<T> {
    #content: T;

    constructor(content: T) {
        this.#content = content;
    }

    getContent(): T {
        return this.#content;
    }

    setContent(content: T): void {
        this.#content = content;
    }
}

const numberBox = new Box<number>(42);
const textBox = new Box<string>("สวัสดี");

numberBox.setContent(100);    // ✅
numberBox.setContent("ผิด");  // ❌ Error! box นี้รับแค่ number

ใส่ constraint ให้ generic ของ class ก็ได้ (เหมือนบทที่ 05):

typescript
class Repository<T extends { id: number }> {
    #items: T[] = [];

    add(item: T): void {
        this.#items.push(item);
    }

    findById(id: number): T | undefined {
        return this.#items.find(item => item.id === id);
    }
}

T extends { id: number } บังคับว่า "ของที่เก็บใน Repository ต้องมี id" — เพราะ findById ต้องใช้ item.id


Part 10: เรื่องที่ต้องระวัง — Property Initialization

ในบทที่ 01 เราเปิด strict ซึ่งรวม strictPropertyInitialization — มันบังคับว่า "ทุก property ต้องถูกกำหนดค่า":

typescript
class User {
    name: string;   // ❌ Error! Property 'name' has no initializer
}

วิธีจัดการ มี 3 ทาง:

typescript
class User {
    name: string = "ไม่มีชื่อ";   // วิธีที่ 1 — ใส่ค่าเริ่มต้น
    email?: string;               // วิธีที่ 2 — ทำให้ optional (มีหรือไม่มีก็ได้)
    id!: number;                  // วิธีที่ 3 — ใส่ ! บอก "เชื่อฉัน เดี๋ยวมีค่าแน่"

    constructor(name: string) {
        this.name = name;
    }
}

วิธีที่ดีที่สุดคือกำหนดค่าใน constructor (เหมือน Part 1) หรือใช้ parameter property (Part 2) — ส่วน ! (definite assignment assertion = "ยืนยันว่าจะมีค่าแน่") ใช้เฉพาะเมื่อมั่นใจจริง ๆ ว่า property จะถูกกำหนดค่าด้วยวิธีอื่น (เช่น framework กำหนดให้) — ใช้บ่อยจะกลายเป็นการ "ปิดปาก" TypeScript โดยไม่จำเป็น

💡 Decorators (มัณฑนากร) — ฟีเจอร์เพิ่มเติมที่ใช้ใน framework อย่าง NestJS และ MobX มาตรฐาน TC39 (คณะกรรมการกำหนดมาตรฐาน JavaScript) stage 3 (ขั้นที่ 3 จาก 4 — ใกล้จะเป็นมาตรฐานแล้ว) ณ ปี 2026 — ถ้าไม่ได้ใช้ NestJS หรือ MobX ไม่ต้องสนใจตอนนี้


Part 11: Class หรือไม่ใช้ Class — มุมมองปี 2026

มือใหม่หลายคนเข้าใจว่า "เขียนโปรแกรมต้องใช้ class" — ความจริงในโลก JavaScript/TypeScript ไม่ใช่แบบนั้น

ลองดูสองวิธีทำสิ่งเดียวกัน — ตัวนับเลข:

typescript
// วิธีที่ 1 — ใช้ class
class Counter {
    #count = 0;
    increment(): number { return ++this.#count; }
    getValue(): number { return this.#count; }
}

// วิธีที่ 2 — ใช้ฟังก์ชัน + closure (ไม่มี class)
// (ดู gloss closure ใต้บล็อกโค้ดนี้)
function createCounter() {
    let count = 0;
    return {
        increment: () => ++count,
        getValue: () => count,
    };
}

const counter = createCounter();
counter.increment();
console.log(counter.getValue());   // 1

📎 คำศัพท์: closure (โคลเชอร์) = ฟังก์ชันที่ "จำ" ตัวแปรในขอบเขตด้านนอกที่มันถูกสร้างขึ้นได้ แม้จะถูกเรียกทีหลัง — ในตัวอย่างนี้คือ count ที่ฟังก์ชัน increment/getValue ยังเข้าถึงได้ทั้งที่อยู่ "ในตัวแปร local ของ createCounter" (ถ้ายังไม่แม่น ทบทวนได้ในเล่ม Javascript)

ทั้งสองวิธีทำงานเหมือนกัน วิธีที่ 2 (closure) มีข้อดีคือ ไม่ต้องปวดหัวกับ this ซึ่งใน JavaScript มีพฤติกรรมซับซ้อน

เมื่อไหร่ควรใช้ class:

  • งานที่ต้องสืบทอด (inheritance) จริง ๆ
  • framework บางตัวออกแบบมารอบ class (เช่น Angular, ORM อย่าง TypeORM)
  • Domain entity ในสถาปัตยกรรมแบบ DDD

📎 DDD (Domain-Driven Design) คือแนวทางออกแบบซอฟต์แวร์ที่เน้นให้โครงสร้างโค้ดสะท้อนตรรกะของธุรกิจ เช่น class Order, Customer, Invoice ตรงกับ "ของจริง" ในธุรกิจ ตรงนี้เป็นเรื่องขั้นสูงมาก มือใหม่ข้ามได้เลย ไม่จำเป็นต้องรู้สำหรับ TypeScript ทั่วไป เดี๋ยวค่อยไปเจอตอนทำงานองค์กรใหญ่

เมื่อไหร่ใช้ฟังก์ชัน/object ก็พอ:

  • งานทั่วไปส่วนใหญ่ — โดยเฉพาะใน React สมัยใหม่ที่ใช้ function component + hooks

🎯 ข้อสรุป: class เป็นเครื่องมือหนึ่ง ไม่ใช่เครื่องมือเดียว — รู้จักมันไว้ เข้าใจมันให้ดี แต่อย่าคิดว่าทุกอย่างต้องเป็น class เลือกใช้ตามความเหมาะสม


Part 12: Lab — ลงมือทำ

Lab 1 — class พร้อม encapsulation

ฝึกออกแบบ class ที่ซ่อนข้อมูลภายใน (private #items) เปิดให้แตะผ่าน method/getter เท่านั้น พร้อม validation — เห็นภาพ encapsulation ที่ดี (ภายนอกแก้ข้อมูลตรง ๆ ไม่ได้):

typescript
class ShoppingCart {
    #items: { name: string; price: number }[] = [];

    addItem(name: string, price: number): void {
        if (price < 0) throw new Error("ราคาติดลบไม่ได้");
        this.#items.push({ name, price });
    }

    get total(): number {
        return this.#items.reduce((sum, item) => sum + item.price, 0);
    }

    get count(): number {
        return this.#items.length;
    }
}

const cart = new ShoppingCart();
cart.addItem("หนังสือ", 350);
cart.addItem("ปากกา", 25);
console.log(`มี ${cart.count} ชิ้น รวม ${cart.total} บาท`);

Lab 2 — Inheritance + abstract

ฝึกใช้ abstract class — กำหนด "แม่แบบ" ที่บังคับให้ class ลูก implement method บางตัว (abstract) แต่แชร์ logic ที่เหมือนกันได้ เห็นภาพการออกแบบ inheritance ที่บังคับสัญญา:

typescript
abstract class Employee {
    constructor(public name: string) {}

    abstract calculateSalary(): number;

    describe(): string {
        return `${this.name} — เงินเดือน ${this.calculateSalary()} บาท`;
    }
}

class FullTimeEmployee extends Employee {
    constructor(name: string, private monthlySalary: number) {
        super(name);
    }

    override calculateSalary(): number {
        return this.monthlySalary;
    }
}

class HourlyEmployee extends Employee {
    constructor(name: string, private hourlyRate: number, private hours: number) {
        super(name);
    }

    override calculateSalary(): number {
        return this.hourlyRate * this.hours;
    }
}

const staff: Employee[] = [
    new FullTimeEmployee("Alice", 30000),
    new HourlyEmployee("Bob", 200, 160),
];

staff.forEach(e => console.log(e.describe()));

Lab 3 — Generic class

typescript
class TypedStorage<T> {
    #data = new Map<string, T>();

    set(key: string, value: T): void {
        this.#data.set(key, value);
    }

    get(key: string): T | undefined {
        return this.#data.get(key);
    }

    has(key: string): boolean {
        return this.#data.has(key);
    }
}

const userStore = new TypedStorage<{ name: string; age: number }>();
userStore.set("u1", { name: "Alice", age: 30 });
console.log(userStore.get("u1")?.name);   // "Alice"

Part 13: Checkpoint

1. Parameter property ทำอะไร?

เป็นไวยากรณ์ย่อของ TypeScript — ใส่ access modifier (public/private/...) หน้าพารามิเตอร์ใน constructor แล้วมันจะประกาศ property + assign ค่าให้อัตโนมัติ ไม่ต้องเขียน this.x = x เอง

2. Access modifier 4 ตัวต่างกันยังไง?

public เข้าถึงได้ทุกที่ private เฉพาะใน class นั้น protected ใน class นั้น + class ลูก readonly อ่านได้ทุกที่แต่แก้ไม่ได้

3. private ของ TypeScript ต่างจาก # ยังไง?

private ของ TypeScript มีผลแค่ compile-time (runtime ยังเข้าถึงได้ผ่าน as any) ส่วน # เป็น private จริงของ JavaScript ป้องกันได้ทั้ง compile และ runtime — ปี 2026 แนะนำใช้ #

4. Abstract class คืออะไร?

class ที่เป็น "แม่แบบ" — สร้าง instance ตรง ๆ ไม่ได้ มี abstract method ที่ประกาศไว้แต่ไม่มี body บังคับให้ class ลูกมา implement

5. extends กับ implements ต่างกันยังไง?

extends = สืบทอดของจริงจาก class แม่ (ได้ทั้ง type และโค้ด, ได้แค่ class เดียว) implements = สัญญาว่าจะมีตามรูปแบบ interface (ไม่ได้โค้ด ต้องเขียนเอง, ได้หลาย interface)

6. ทำไมควรใส่คำว่า override?

เมื่อเปิด noImplicitOverride การใส่ override ทำให้ — ถ้ามีคนเปลี่ยนชื่อ method ใน class แม่ method ที่ override ใน class ลูกจะ error ทันที ช่วยให้ refactor ปลอดภัย

7. ปี 2026 ต้องใช้ class กับทุกอย่างไหม?

ไม่ — class เป็นเครื่องมือหนึ่ง งานทั่วไปจำนวนมากใช้ฟังก์ชัน + object/closure ก็พอ (และไม่ต้องปวดหัวกับ this) ใช้ class เมื่อต้องสืบทอดจริง ๆ หรือ framework บังคับ


Part 14: สรุปบทนี้

  • TypeScript เพิ่ม type และ modifier ให้ class
  • Parameter property — ใส่ modifier หน้าพารามิเตอร์ constructor = ประกาศ + assign อัตโนมัติ
  • Access modifierpublic/private/protected/readonly ใช้ทำ encapsulation
  • private ของ TS = compile-time เท่านั้น, # = private จริง (แนะนำใช้ #)
  • Inheritanceextends, super, override
  • Abstract class — แม่แบบที่บังคับ class ลูก implement
  • implements — สัญญากับ interface (ได้หลายตัว)
  • Generic class — โครงที่ทำงานกับ type อะไรก็ได้
  • class ไม่ใช่คำตอบของทุกอย่าง — เลือกใช้ตามความเหมาะสม

บทถัดไป — Module และ Declaration File


← บทที่ 07 | สารบัญ | บทที่ 09: Module + Declaration →