ปัญหาในการใช้งาน Singleton Pattern
ปัญหาในการใช้งาน Singleton ที่พบได้บ่อยคือ
ปัญหาการทำ unitTest เนื่องจาก ในการสร้าง Test แต่ละหน่วยทดสอบควรมี Instance แยกกัน แต่เนื่องจากการที่ Singleton จะมีเพียง Instance เดียวเท่านั้นในระหว่างการทดสอบ อาจเป็นเหตุให้ได้ผลการทดสอบที่ไม่ถูกต้องได้
ส่วนหนึ่งของโค้ดตัวอย่างด้านบน ที่เป็น Singleton ฟังก์ชั่น inc()
เมื่อเขียน test (ดูจากฟังก์ชั่นที่เขียนนะครับ) testInc() และ testInc2() มีการเขียนการทดสอบเหมือนๆกันดังนั้นผลลัพท์มันควรจะเหมือนกัน แต่เมื่อทำการทดสอบ ผลลัพท์ของ testInc2() จะไม่ผ่าน เนื่องจากมันจะเอาผลการทดสอบจาก testInc() มารวมด้วย
การแก้ไข อาจต้องเพิ่มฟังก์ชั่น reset() ลงใน Singleton อีกสักตัวใช้ทำหน้าที่ในการทำให้ Singleton กลับเป็นค่าเริ่มต้นและเรียกใช้ฟังก์ชั่นก่อนการทดสอบ
และเมื่อเขียน test
ปัญหาที่สองคือเรื่องของ Performance จริงๆมันก็จะแตกต่างจากการเขียนคลาสปกติไม่เยอะหรอกครับแต่ในบางกรณีที่ผมเจอ คือมีการเขียนแบบนี้
จะเห็นว่ามีการเรียกเมธอดภายในคลาส Cfg หลายครั้ง และทุกครั้งเรียกด้วย getInstance() วิธีนี้ทำให้ Performance ตกลงอย่างมากครับ เนื่องจากการเรียก getInstance() ในแต่ละครั้งจะต้องมีการปฏิบัติเพิ่มเติมตามหน้าที่ของฟังก์ชั่น ซึ่งการแก้ไขอาจเรียกแบบนี้ก็ได้
ปัญหาอีกอันของ Singleton เกี่ยวกับ Performance คือ เนื่องจาก Singleton จะต้องเป็น Global ดังนั้นมันจะถูกโหลดเก็บไว้ในหน่วยความจำจนกว่าจะจบโปรแกรม ดังนั้นหากคลาสนี้เป็นคลาสขนาดใหญ่ เราจะสูญเสียหน่วยความจำส่วนหนึ่งไปให้กับคลาส
ปัญหาการทำ unitTest เนื่องจาก ในการสร้าง Test แต่ละหน่วยทดสอบควรมี Instance แยกกัน แต่เนื่องจากการที่ Singleton จะมีเพียง Instance เดียวเท่านั้นในระหว่างการทดสอบ อาจเป็นเหตุให้ได้ผลการทดสอบที่ไม่ถูกต้องได้
class Cfg
{
/**
* property ของคลาส
*
* @var int
*/
public $property = 1;
.........................
/**
* @param int $dec
* @return int
*/
public function inc($dec)
{
$this->property = $this->property + $dec;
return $this->property;
}
}
ส่วนหนึ่งของโค้ดตัวอย่างด้านบน ที่เป็น Singleton ฟังก์ชั่น inc()
/**
* Generated by PHPUnit_SkeletonGenerator on 2015-12-04 at 18:54:14.
*/
class CfgTest extends PHPUnit_Framework_TestCase
{
/**
* @var Cfg
*/
protected $object;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->object = \Cfg::getInstance();
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
}
/**
* @covers Cfg::inc
*/
public function testInc()
{
$this->assertEquals(2, $this->object->inc(1));
}
/**
* @covers Cfg::inc
*/
public function testInc2()
{
$this->assertEquals(2, $this->object->inc(1));
}
}
เมื่อเขียน test (ดูจากฟังก์ชั่นที่เขียนนะครับ) testInc() และ testInc2() มีการเขียนการทดสอบเหมือนๆกันดังนั้นผลลัพท์มันควรจะเหมือนกัน แต่เมื่อทำการทดสอบ ผลลัพท์ของ testInc2() จะไม่ผ่าน เนื่องจากมันจะเอาผลการทดสอบจาก testInc() มารวมด้วย
การแก้ไข อาจต้องเพิ่มฟังก์ชั่น reset() ลงใน Singleton อีกสักตัวใช้ทำหน้าที่ในการทำให้ Singleton กลับเป็นค่าเริ่มต้นและเรียกใช้ฟังก์ชั่นก่อนการทดสอบ
class Cfg
{
.........................
public function reset()
{
$this->property = 1;
}
}
และเมื่อเขียน test
class CfgTest extends PHPUnit_Framework_TestCase
{
......
public function testInc()
{
$this->object->reset();
$this->assertEquals(2, $this->object->inc(1));
}
public function testInc2()
{
$this->object->reset();
$this->assertEquals(2, $this->object->inc(1));
}
}
ปัญหาที่สองคือเรื่องของ Performance จริงๆมันก็จะแตกต่างจากการเขียนคลาสปกติไม่เยอะหรอกครับแต่ในบางกรณีที่ผมเจอ คือมีการเขียนแบบนี้
Cfg::getInstance()->inc(1);
Cfg::getInstance()->inc(1);
Cfg::getInstance()->inc(1);
Cfg::getInstance()->inc(1);
Cfg::getInstance()->inc(1);
จะเห็นว่ามีการเรียกเมธอดภายในคลาส Cfg หลายครั้ง และทุกครั้งเรียกด้วย getInstance() วิธีนี้ทำให้ Performance ตกลงอย่างมากครับ เนื่องจากการเรียก getInstance() ในแต่ละครั้งจะต้องมีการปฏิบัติเพิ่มเติมตามหน้าที่ของฟังก์ชั่น ซึ่งการแก้ไขอาจเรียกแบบนี้ก็ได้
$cfg = Cfg::getInstance();
$cfg->inc(1);
$cfg->inc(1);
$cfg->inc(1);
$cfg->inc(1);
$cfg->inc(1);
ปัญหาอีกอันของ Singleton เกี่ยวกับ Performance คือ เนื่องจาก Singleton จะต้องเป็น Global ดังนั้นมันจะถูกโหลดเก็บไว้ในหน่วยความจำจนกว่าจะจบโปรแกรม ดังนั้นหากคลาสนี้เป็นคลาสขนาดใหญ่ เราจะสูญเสียหน่วยความจำส่วนหนึ่งไปให้กับคลาส