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 — ตัว 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 instanceconsole.log(a.env, a.maxConnections);class AppConfig: _instance = None
def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) # Initialise the single instance exactly once. instance = cls._instance instance.env = "development" instance.max_connections = 10 return cls._instance
a = AppConfig()b = AppConfig()print(a is b) # True — __new__ returns the same objectprint(a.env, a.max_connections)package main
import ( "fmt" "sync")
type AppConfig struct { Env string MaxConnections int}
var ( instance *AppConfig once sync.Once)
// Get returns the single AppConfig, building it on first call.func Get() *AppConfig { once.Do(func() { instance = &AppConfig{Env: "development", MaxConnections: 10} }) return instance}
func main() { a := Get() b := Get() fmt.Println(a == b) // true — sync.Once guards construction fmt.Println(a.Env, a.MaxConnections)}use std::sync::OnceLock;
struct AppConfig { env: String, max_connections: u32,}
fn config() -> &'static AppConfig { static INSTANCE: OnceLock<AppConfig> = OnceLock::new(); INSTANCE.get_or_init(|| AppConfig { env: "development".to_string(), max_connections: 10, })}
fn main() { let a = config(); let b = config(); // Both references point at the same static instance. println!("{}", std::ptr::eq(a, b)); // true println!("{} {}", a.env, a.max_connections);}ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”- ข้อดี: รับประกัน instance เดียวและจุดเข้าถึงที่ชัดเจนเพียงจุดเดียวสำหรับ state ที่แชร์กันจริง ๆ
- ข้อดี: instance ถูกสร้างแบบ lazy คุณจึงจ่ายค่าตั้งค่าก็ต่อเมื่อมีการใช้งานจริงเท่านั้น
- ข้อเสีย: นี่คือ global mutable state ที่อำพรางไว้ dependency ที่ซ่อนอยู่ทำให้ code ทำความเข้าใจได้ยากขึ้น
- ข้อเสีย: ทำให้การทดสอบยุ่งยาก เทสต์แชร์ instance เดียวกันและไม่สามารถฉีด fake เข้าไปได้ง่าย ๆ เว้นแต่คุณออกแบบเผื่อไว้
- ข้อเสีย: การทำ lazy initialisation แบบไร้เดียงสาคือ race condition ภายใต้ concurrency คุณต้องป้องกันไว้ (อย่างที่
sync.OnceและOnceLockทำข้างต้น)
pattern ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “pattern ที่เกี่ยวข้อง”- Abstract Factory มักถูก implement เป็น Singleton เพื่อให้ factory ตัวเดียวถูกแชร์กัน
- Prototype เสนอจุดยืนตรงข้าม — สร้างสำเนาจำนวนมากอย่างประหยัดแทนที่จะแชร์ตัวเดียว
| Singleton | Static Class | Dependency Injection | |
|---|---|---|---|
| ทดสอบได้ | ยาก (global state) | ยากมาก | ง่าย (inject mock ได้) |
| lazy init | ได้ | ไม่ได้ | ขึ้นกับ container |
| override | ยาก | ไม่ได้ | ง่าย |
| เมื่อใช้ | ต้องการ instance เดียวจริง ๆ | utility function ล้วน ๆ | ระบบที่ test ได้สำคัญ |