Natcha Luang - Aroonchai

Trust me I'm Petdo

Bangkok, Thailand

[Blog] ลองมาดูหลักการของ singleton กันหน่อยดีไหม?

วันนี้เราจะมาคุยกันเรื่องของทฤษฎีสักหน่อย ที่ผ่านมาผมมักจะเน้นเรื่องของการลงมือทำเสียเป็นส่วนใหญ่ จนบางครั้งก็ลืมหลักการทางทฤษฎีไป ผมไม่สามารถปฏิเสธได้ว่าการลงมือทำเป็นเรื่องที่ดี แต่การเรียนรู้จากทฤษฎีก็ทำให้โปรแกรมของเราทำงานได้ดียิ่งกว่า คงจะดีไม่น้อย ถ้าเราสามารถเขียนโปรแกรมอยู่บนพื้นฐานของความเข้าใจไม่ใช่การจำจากเอกสาร

และสำหรับเรื่องราวในบทความวันนี้เป็นเรื่องของการทำงานกับหลักการที่ชื่อว่า "singleton" ผมเชื่อว่าหลายคนน่าจะเคยได้ยินคำนี้ หรืออย่างน้อยก็น่าจะได้ใช้งานกันมาบ้างแล้วถ้าจะยกตัวอย่างโค๊ดง่าย ๆ ที่ผมเจอมาก็จะอยู่ใน framework ชื่อดัง ๆ อย่าง Laravel หน้าตามันก็จะประมาณนี้ครับ

<?php

// Encrypting a value
$encrypted = Crypt::encrypt('secret');

โดยลักษณะของ syntax ภาษาคือหมายถึงการเรียกใช้งานเมดธอด encrypt ที่อยู่ในคลาส Crypt โดยไม่จำเป็นต้องสร้าง object จากคลาส Crypt ซึ่งตัว interface ของคลาสและเมดธอดก็น่าจะออกมาคล้าย ๆ แบบนี้

<?php

class Crypt {

  private function __construct() {
    ...
  }

  public static function encrypt($plaintext) {
    ...
  }

}

ซึ่งจะเห็นว่าเมดธอด encrypt จะถูกประกาศอยู่ในรูปแบบ static ทำให้สามารถเข้าถึงเมดธอดจากคลาสได้ แม้จะยังไม่ได้สร้าง object ก็ตาม

หลักการมันคืออะไรล่ะ?

ลองใช้งานกันมาก็บ่อยแล้ว ทีนี้ลองถามกลับไปว่าแล้วรู้หรือปล่าวหลักการของ singleton มันคืออะไร? ทำไมมันจึงเรียกว่า singleton? เหตุผลที่มันถูกสร้างขึ้นมาคืออะไร ผมเชื่อหลายคนรวมทั้งผมเองก็ตอบมันไม่ได้ รู้อย่างเดียวว่ามันก็ทำให้ใช้งานได้ง่ายดี ไม่ต้องมาสร้าง object เวลาจะใช้งาน เรียกเป็น global ได้เลยสะดวกดีแบบนี้ แล้วหลักการที่มันถูกสร้างขึ้นมาจริง ๆ ล่ะ เพื่ออะไรกันแน่?

ผมลองค้นหาจาก Google ด้วยคีย์เวิร์ดง่าย ๆ ว่า "singleton theory" ก็ได้ผลลัพธ์ออกมาเยอะมากเลยทีเดียว ผมเจอผลการค้นหาที่น่าสนใจอยู่คือถ้าเป็นคำว่า "singleton" เฉย ๆ เราจะพบกับลิงค์ของ Wikipedia ที่แสดงผลลัพธ์ของ singleton ในเชิงของ software engineering แต่ถ้าเติมคำว่า theory เข้าไปด้วยก็จะได้ผลลัพธ์เป็นอีกลิงค์หนึ่งที่อยู่ในเทอมของคณิตศาสตร์ น่าสนใจจริง ๆ

สำหรับคำจำกัดความในลิงค์ของฝั่ง software engineering ได้กล่าวไว้ว่า

In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.

แปลเป็นไทยได้ว่า ในทาง software engineering นั้น singleton pattern เป็นการออกแบบที่จำกัดการสร้าง instance ไว้เพียงแค่หนึ่ง object เท่านั้น จะประโยชน์กับงานที่ต้องใช้ object เดียวในการทำงานร่วมกันทั้งระบบ (เช่น object สำหรับเก็บค่า config เป็นต้น) โดยคอนเซ็ปต์นี้บอกไว้อีกว่าบางครั้งการที่มี object เพียงแค่ตัวเดียวในการทำงานกลับทำให้ประสิทธิภาพของโปรแกรมดีกว่าการที่มี object แบบเดียวกันนี้หลาย ๆ ตัว (เข้าใจว่าต้องการ consistency ของระบบด้วย) และในประโยคสุดท้ายมีบอกอีกว่าหลักการนี้เนี่ยมาจากคอนเซ็ปต์ของ mathematical งั้นเราลองไปดูหลักการของ mathematical กันด้วยดีกว่า

In mathematics, a singleton, also known as a unit set,[1] is a set with exactly one element. For example, the set {0} is a singleton.

The term is also used for a 1-tuple (a sequence with one element).

หลายคนอาจจะงง ๆ กับภาษาและเครื่องหมายของคณิตศาสตร์กันสักเล็กน้อยแต่ไม่เป็นไรผมจะอธิบายคร่าว ๆ คือในเทอมของคณิตฯ เนี่ยกล่าวไว้ว่า singleton เป็นที่รู้จักกันใน set ที่มีสมาชิกเพียงแค่ตัวเดียว (a set with exactly one element) อย่างในตัวอย่างก็แสดงให้เห็นว่าถ้ามี set ที่มีสมาชิกคือ 0 ({0} หรือจะเป็น set ที่มีสมาชิกอื่นที่ไม่ใช่เลข 0 ก็ได้เช่น {1} หรือ {3} แบบนี้) อยู่เพียงตัวเดียวแบบนี้ก็คือ singleton

กลับมาดูกันที่ฝั่งของ software engineering อีกนิดดีกว่า โดยปกติแล้วเราจะสร้าง singleton ด้วยการป้องกันการสร้าง object โดยประกาศ private หรือ protected ไว้ที่ constructor และสร้างเมดธอดสำหรับสร้าง object ขึ้นมาอีกอัน​โดยเข้าเมดธอดนี้จะต้องควบคุมไม่ให้การสร้าง object จากคลาสมีมากกว่าหนึ่งตัว

<?php

class Foo {

  private static $instance;

  private function __construct() {
  }

  public static function bar() {
    if (is_null(static::$instance)) {
      static::$instance = new Foo;
    }

    return static::$instance;
  }

}

Foo::bar();

แต่ทีนี้เกิดเราต้องการสร้าง singleton หลาย ๆ คลาสการมานั่งจัดการสร้างและตรวจสอบจำนวน object คงไม่ใช่เรื่องสะดวกเท่าไร จึงเกิดเทคนึคนึงขึ้นมาสำหรับเข้ามาช่วยจัดการเรื่องของ singleton ให้โโยอัตโนมัติเพียงแต่ register คลาสที่ต้องการทำ singleton ลงไปคลาสนั้น ๆ ก็จะมีสถานะภาพเป็น singleton ได้ทันที เรียกเทคนิคนี้ว่า IoC (Inversion of Control) หลักการ IoC ไม่ได้เกิดมาเพื่อใช้งานกับ singleton เพียงแต่มันเป็นเทคนิคการจัดการกับ dependency ที่อยู่ในคลาสโดยโยน object ของคลาสที่เราต้องยุ่งเกี่ยวเข้ามาใน parameter แทนที่จะไปสร้างอีกครั้ง ตัวอย่างโค๊ดจากเดิมเราเขียนกันแบบนี้


class Foo {

  public function bar($config) {
    $this->container = new Container($config);
  }

}

แบบเดินเราต้องการใช้คลาส Container ในการทำงานร่วมกับคลาส Foo ทีนี้เราก็เคยชินกับการส่งค่าคอนฟิกเข้ามาที่ parameter จากนั้นก็สร้าง object เพื่อใช้งาน ซึ่งถ้าเกิดเราต้องการ mock คลาส Container เพื่อทำ unit test ก็จะไม่สามารถทำได้ หลักการ IoC จึงเกิดขึ้นมา


class Foo {

  public function bar(Container $container) {
  }

}

$foo = new Foo();
$foo->bar(new Container($config));

พอเราต้องการสร้าง mock ก็สามารถทำได้อย่างง่ายดาย ซึ่งในการทำงานจริง ๆ เราจะมี framework สำหรับช่วยจัดการเรื่องการทำ IoC ให้อยู่แล้ว ยกตัวอย่างโค๊ดจาก Laravel เป็นการใช้งาน repository pattern ใน controller แทนการเรียกใช้งาน model


// UserRepository.php
//
class UserRepository implements RepositoryInterface {
  ...
}

// UserController.php
//

use App\Repositories\UserRepository as User;

class UserController extends Controller {

  public function __construct(User $user) {
    $this->user = $user;
  }

}

จะเห็นได้ว่าเราไม่ต้องสร้าง object จากคลาส UserRepository เลยเพราะ Laravel จะเป็นคนอ่าน parameter แล้วจัดการสร้าง object และ assign เข้ามาตอนที่ initialize คลาสให้เอง

ซึ่งหลักการของ IoC นี้ก็จะทำให้เราสามารถสร้างคลาสปกติและส่งเข้ามาเป็น parameter ที่คลาสสำหรับควบคุมการสร้าง singleton ทำให้เราสามารถสร้างคลาส singleton จากคลาสธรรมดา ๆ ได้ง่าย ๆ ด้วยเทคนิค IoC นี้เองครับ


ในเว็บบอร์ดต่างประเทศเคยมีการถกเรื่องของประสิทธิภาพของ singleton กันฝ่ายนึงก็พูดว่าถึงแม้โค๊ดจะดูสวยและใช้งานง่าย แต่ก็อาจจะส่งผลถึงประสิทธิภาพของโปรแกรมได้เช่นกัน ในตัวอย่างคือเป็นการเชื่อมต่อฐานข้อมูลด้วย singleton ทำให้ object ที่เชื่อมต่อเข้าไปจำเป็นต้องเปิด connection ค้างเอาไว้เนื่องจากต้องการเชื่อมต่อด้วย object ตัวเดิมตลอดทั้งโปรแกรม แต่กลับกันถ้าเราเชื่อมต่อด้วยหลักการเดิม เราไม่จำเป็นต้องใช้ object เดิมในการเชื่อมต่อตัว GC ที่ฉลาดมากพอก็จะทำการคืนค่าหน่วยความจำได้ง่ายกว่า

ซึ่งก็มีทั้งข้อดีและข้อเสียถ้าเราเลือกใช้งานไม่ถูกต้องจากเทคนิคดี ๆ ก็อาจจะส่งผลเสียต่อโปรแกรมมากกว่าก็ได้ครับ ดังนั้นก็ข้อให้นึกคิดดี ๆ ก่อนว่าเราจะสร้าง singleton ไปเพื่ออะไร


อ้างอิง

Comments