ข้ามไปยังเนื้อหาหลัก
คู่มือระบบ เนื้อหาผ่านการตรวจสอบแล้ว

การใช้ Design Patterns: Singleton Pattern

บทความนี้แนะนำ Singleton Pattern ซึ่งเป็น Design Pattern ที่ใช้ควบคุมการสร้าง Object ให้มีเพียง Instance เดียวและสามารถเข้าถึงได้จากทุกส่วนของโปรแกรม โดยมีทั้งข...

การใช้ Design Patterns: Singleton Pattern

ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Interfaces และการนำไปใช้เพื่อสร้างโค้ดที่ยืดหยุ่นและลดการพึ่งพากัน ในบทนี้ เราจะมาทำความรู้จักกับ Design Pattern ที่ชื่อว่า Singleton Pattern ซึ่งเป็น Pattern ที่มีประโยชน์ในการควบคุมการสร้าง Object ให้มีเพียง Instance เดียวเท่านั้น

Singleton Pattern คืออะไร

Singleton Pattern เป็น Design Pattern ประเภท Creational (เกี่ยวกับการสร้าง Object) ที่มีจุดประสงค์เพื่อให้มั่นใจว่าคลาสจะมีเพียง Instance เดียวเท่านั้น และมีจุดเข้าถึง (Global Access Point) ให้ Instance นั้นได้จากทุกที่ในโปรแกรม

ลองนึกภาพว่าคุณมี Configuration Class ที่เก็บการตั้งค่าต่างๆ ของ Application ของคุณ คุณต้องการให้ทุกส่วนของ Application สามารถเข้าถึง Configuration นี้ได้ แต่คุณไม่ต้องการให้มี Configuration หลายชุดที่อาจทำให้เกิดความขัดแย้งกัน Singleton Pattern ช่วยให้คุณมั่นใจได้ว่าจะมี Configuration Instance เดียวเท่านั้นที่ถูกใช้งาน

การ Implement Singleton Pattern

การ Implement Singleton Pattern ใน PHP มีขั้นตอนดังนี้:

  1. กำหนด Constructor ให้เป็น Private: เพื่อป้องกันการสร้าง Instance จากภายนอกคลาสโดยตรง
  2. สร้าง Static Private Variable: เพื่อเก็บ Instance ของคลาส
  3. สร้าง Static Public Method: เพื่อให้เข้าถึง Instance ของคลาส (Lazy Initialization)

ตัวอย่างโค้ด:

<?php

class DatabaseConnection {
  private static ?DatabaseConnection $instance = null;
  private string $connectionString;

  // Constructor เป็น private ป้องกันการสร้าง instance จากภายนอก
  private function __construct(string $connectionString) {
    $this->connectionString = $connectionString;
    echo "สร้าง Database Connection: " . $connectionString . "<br>";
  }

  // ป้องกันการ Clone
  private function __clone() {}

  // ป้องกันการ Unserialize
  public function __wakeup()
  {
    throw new \Exception("Cannot unserialize singleton");
  }

  // Method สำหรับเข้าถึง Instance (Lazy Initialization)
  public static function getInstance(string $connectionString): DatabaseConnection {
    if (self::$instance === null) {
      self::$instance = new DatabaseConnection($connectionString);
    }

    return self::$instance;
  }

  public function getConnectionInfo(): string {
    return $this->connectionString;
  }
}

// การใช้งาน
$db1 = DatabaseConnection::getInstance("mysql://user:password@host:port/database");
echo "Connection Info (db1): " . $db1->getConnectionInfo() . "<br>";

$db2 = DatabaseConnection::getInstance("mysql://user:password@host:port/database");
echo "Connection Info (db2): " . $db2->getConnectionInfo() . "<br>";

// ตรวจสอบว่า $db1 และ $db2 เป็น Instance เดียวกัน
if ($db1 === $db2) {
  echo "db1 และ db2 เป็น Instance เดียวกัน<br>";
}

// พยายามสร้าง Instance โดยตรง (จะเกิด Error)
// $db3 = new DatabaseConnection(); // Error: Call to private DatabaseConnection::__construct()

?>

คำอธิบาย:

  • private static ?DatabaseConnection $instance = null;: ประกาศ Static Variable ชื่อ $instance เพื่อเก็บ Instance ของ DatabaseConnection (Nullable Type Hint ? หมายความว่าสามารถเป็น null ได้)
  • private function __construct(string $connectionString) { ... }: Constructor ถูกกำหนดให้เป็น private เพื่อป้องกันการสร้าง Instance โดยตรงจากภายนอก การทำเช่นนี้บังคับให้ใช้ getInstance() เท่านั้น
  • public static function getInstance(string $connectionString): DatabaseConnection { ... }: getInstance() เป็น Static Method ที่ใช้ในการเข้าถึง Instance ของ DatabaseConnection ถ้ายังไม่มี Instance จะสร้าง Instance ใหม่ (Lazy Initialization) แล้วเก็บไว้ใน $instance ถ้ามี Instance อยู่แล้วก็จะ Return Instance เดิม
  • private function __clone() {}: ป้องกันการ Clone Instance โดยการกำหนด Method __clone() ให้เป็น private
  • public function __wakeup() { throw new \Exception("Cannot unserialize singleton"); }: ป้องกันการ Unserialize Instance โดยการ Throw Exception ใน Method __wakeup()

จากตัวอย่าง จะเห็นว่าไม่ว่าเราจะเรียก DatabaseConnection::getInstance() กี่ครั้ง ก็จะได้ Instance เดียวกันเสมอ นั่นคือ $db1 และ $db2 ชี้ไปยัง Object เดียวกันใน Memory

ข้อดีและข้อเสียของ Singleton Pattern

ข้อดี:

  • ควบคุมการสร้าง Instance: มั่นใจได้ว่าจะมี Instance เดียวเท่านั้น
  • Global Access Point: สามารถเข้าถึง Instance ได้จากทุกที่ในโปรแกรม
  • Lazy Initialization: สร้าง Instance เมื่อต้องการใช้งานจริงๆ เท่านั้น ช่วยประหยัด Resource

ข้อเสีย:

  • Global State: Singleton Instance เป็น Global State ซึ่งอาจทำให้ Code เข้าใจยากและทดสอบยาก
  • Tight Coupling: อาจทำให้ Code พึ่งพา Singleton Class มากเกินไป ทำให้เปลี่ยนแปลงยาก
  • Difficult to Test: เนื่องจาก Singleton เป็น Global State การ Mock หรือ Stub Singleton เพื่อ Test อาจทำได้ยาก

เหตุผลเชิงออกแบบ, Trade-offs และแนวปฏิบัติที่ดี

เหตุผลเชิงออกแบบ: Singleton Pattern ถูกใช้เมื่อต้องการควบคุมการสร้าง Object ให้มีเพียง Instance เดียวเท่านั้น และต้องการให้ Instance นั้นสามารถเข้าถึงได้จากทุกที่ในโปรแกรม เหมาะสำหรับกรณีที่ต้องการจัดการ Resource ที่มีจำกัด เช่น Database Connection, Configuration Settings หรือ Logging Service

Trade-offs: ข้อเสียหลักของ Singleton คือการสร้าง Global State ซึ่งอาจทำให้ Code เข้าใจยากและทดสอบยาก นอกจากนี้ยังอาจทำให้ Code พึ่งพา Singleton Class มากเกินไป ทำให้เปลี่ยนแปลงยาก

แนวปฏิบัติที่ดี:

  • ใช้ Singleton อย่างระมัดระวัง: พิจารณาให้ดีว่าจำเป็นต้องใช้ Singleton จริงๆ หรือไม่ บางครั้ง Dependency Injection อาจเป็นทางเลือกที่ดีกว่า
  • พยายามลดการพึ่งพา Singleton: พยายาม Inject Singleton Instance เข้าไปใน Class ที่ต้องการใช้งาน แทนที่จะเรียกใช้ getInstance() โดยตรง
  • ออกแบบ Singleton ให้ Testable: พยายามทำให้ Singleton สามารถ Mock หรือ Stub ได้ง่าย เพื่อให้สามารถ Test Code ที่ใช้งาน Singleton ได้
  • พิจารณาการใช้ Factory Pattern: ในบางกรณี Factory Pattern อาจเป็นทางเลือกที่ดีกว่า Singleton โดยเฉพาะอย่างยิ่งถ้าต้องการความยืดหยุ่นในการสร้าง Instance ของ Class ที่แตกต่างกัน

สรุป: Singleton Pattern เป็น Design Pattern ที่มีประโยชน์ในการควบคุมการสร้าง Object ให้มีเพียง Instance เดียวเท่านั้น แต่ควรใช้งานอย่างระมัดระวังเนื่องจากมีข้อเสียบางประการ การทำความเข้าใจ Trade-offs และปฏิบัติตามแนวปฏิบัติที่ดีจะช่วยให้คุณใช้ Singleton ได้อย่างมีประสิทธิภาพ

ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับ Design Pattern อีกประเภทหนึ่งที่ชื่อว่า Factory Pattern ซึ่งเป็น Pattern ที่มีประโยชน์ในการสร้าง Object ที่ซับซ้อน

Office.in.th Custom Service

ต้องการพัฒนาระบบหรือปรับแต่งจากโครงสร้างนี้?

Office.in.th รับพัฒนาและออกแบบ Enterprise Web Application, API Integration และระบบบริหารงานภาครัฐ/เอกชน พร้อมการสนับสนุนระยะยาว

ดูบริการรับพัฒนาซอฟต์แวร์