ปัญหาในการใช้งาน Singleton Pattern
ปัญหาการทำ 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 ดังนั้นมันจะถูกโหลดเก็บไว้ในหน่วยความจำจนกว่าจะจบโปรแกรม ดังนั้นหากคลาสนี้เป็นคลาสขนาดใหญ่ เราจะสูญเสียหน่วยความจำส่วนหนึ่งไปให้กับคลาส