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

Singleton

Singleton รับประกันว่า class ถูกสร้าง instance เพียงครั้งเดียวเท่านั้น และเปิดเผย instance เดียวนั้นผ่านจุดเข้าถึงที่แชร์กัน โดยสร้าง instance แบบ lazy ในการใช้งานครั้งแรก

บางสิ่งในโปรแกรมเป็นเอกพจน์อย่างแท้จริง: configuration ที่ parse แล้ว, connection pool, registry ในหน่วยความจำ, หรือ logger หากทุกส่วนของระบบสร้างสำเนาของตัวเองขึ้นมา สำเนาเหล่านั้นก็จะหลุดออกจากกันและสิ้นเปลืองทรัพยากร คุณต้องการ object เดียว สร้างขึ้นในครั้งแรกที่มีใครร้องขอ และนำกลับมาใช้ซ้ำตลอดไป และคุณต้องการให้การรับประกันนั้นถูกบังคับใช้จริง ไม่ใช่อาศัยแค่ข้อตกลงร่วมกัน

ส่วนที่ละเอียดอ่อนคือ ใคร เป็นผู้บังคับใช้กฎนั้น code ทั่วไปสามารถเรียก constructor ได้กี่ครั้งก็ได้ตามใจ Singleton ย้ายการสร้างไปไว้หลัง accessor ที่ควบคุมได้ เพื่อให้ class เองเป็นเจ้าของกฎ ราคาที่ต้องจ่ายคือ instance นั้นกลายเป็น global state พร้อมกับอันตรายด้านการทดสอบและ concurrency ทั้งหมดที่ตามมา นั่นคือเหตุผลว่าทำไมวินัยของการมี หนึ่งเดียว จึงสำคัญกว่าลูกเล่นของการซ่อน constructor

classDiagram
  class Singleton {
    -instance: Singleton$
    -Singleton()
    +getInstance() Singleton$
    +operation()
  }
  Singleton --> Singleton : holds static reference
Singleton ถือ static reference ไปยัง instance เดียวของตัวเอง
  • Singleton — ตัว class เอง ซ่อน constructor ของตัวเอง ถือ private static reference ไปยัง instance เดียวนั้น และเปิดเผย static accessor ที่สร้าง instance ในการเรียกครั้งแรกแล้วคืน reference เดิมกลับมาทุกครั้งหลังจากนั้น
  • Client — ผู้เรียกใด ๆ ไม่เคยสร้าง object โดยตรง แต่ขอ instance ที่แชร์กันจาก accessor

application config แบบอ่านอย่างเดียวขนาดเล็กที่โหลดเพียงครั้งเดียวและถูกแชร์ไปทุกที่ แต่ละภาษาใช้กลไกตามสำนวนของตนเองสำหรับ “สร้างเพียงครั้งเดียวเท่านั้น แบบ lazy และปลอดภัย”

class AppConfig {
private static instance: AppConfig | null = null;
readonly env: string;
readonly maxConnections: number;
private constructor() {
// Pretend this reads from disk or the environment once.
this.env = process.env.APP_ENV ?? 'development';
this.maxConnections = 10;
}
static get(): AppConfig {
if (AppConfig.instance === null) {
AppConfig.instance = new AppConfig();
}
return AppConfig.instance;
}
}
const a = AppConfig.get();
const b = AppConfig.get();
console.log(a === b); // true — same shared instance
console.log(a.env, a.maxConnections);
  • ข้อดี: รับประกัน instance เดียวและจุดเข้าถึงที่ชัดเจนเพียงจุดเดียวสำหรับ state ที่แชร์กันจริง ๆ
  • ข้อดี: instance ถูกสร้างแบบ lazy คุณจึงจ่ายค่าตั้งค่าก็ต่อเมื่อมีการใช้งานจริงเท่านั้น
  • ข้อเสีย: นี่คือ global mutable state ที่อำพรางไว้ dependency ที่ซ่อนอยู่ทำให้ code ทำความเข้าใจได้ยากขึ้น
  • ข้อเสีย: ทำให้การทดสอบยุ่งยาก เทสต์แชร์ instance เดียวกันและไม่สามารถฉีด fake เข้าไปได้ง่าย ๆ เว้นแต่คุณออกแบบเผื่อไว้
  • ข้อเสีย: การทำ lazy initialisation แบบไร้เดียงสาคือ race condition ภายใต้ concurrency คุณต้องป้องกันไว้ (อย่างที่ sync.Once และ OnceLock ทำข้างต้น)
  • Abstract Factory มักถูก implement เป็น Singleton เพื่อให้ factory ตัวเดียวถูกแชร์กัน
  • Prototype เสนอจุดยืนตรงข้าม — สร้างสำเนาจำนวนมากอย่างประหยัดแทนที่จะแชร์ตัวเดียว
SingletonStatic ClassDependency Injection
ทดสอบได้ยาก (global state)ยากมากง่าย (inject mock ได้)
lazy initได้ไม่ได้ขึ้นกับ container
overrideยากไม่ได้ง่าย
เมื่อใช้ต้องการ instance เดียวจริง ๆutility function ล้วน ๆระบบที่ test ได้สำคัญ
Singleton รับประกันอะไร?
ทำไม lazy initialisation แบบไร้เดียงสาจึงต้องระวังเป็นพิเศษภายใต้ concurrency?
กลไกใดของ Go ทำให้การสร้าง singleton รันได้มากที่สุดเพียงครั้งเดียว?
ข้อเสียหลักด้านความสามารถในการทดสอบของ Singleton คืออะไร?