2010-06-27 8 views
21

Tôi đang làm việc trong một khung ứng dụng web và một phần của nó bao gồm một số dịch vụ, tất cả được triển khai dưới dạng đơn. Tất cả họ đều thừa kế một lớp dịch vụ, nơi mà các hành vi singleton được thực hiện, tìm kiếm một cái gì đó như thế này:Mở rộng các tệp đơn trong PHP

class Service { 
    protected static $instance; 

    public function Service() { 
     if (isset(self::$instance)) { 
      throw new Exception('Please use Service::getInstance.'); 
     } 
    } 

    public static function &getInstance() { 
     if (empty(self::$instance)) { 
      self::$instance = new self(); 
     } 
     return self::$instance; 
    } 
} 

Bây giờ, nếu tôi có một lớp được gọi là FileService thực hiện như thế này:

class FileService extends Service { 
    // Lots of neat stuff in here 
} 

... gọi FileService :: getInstance() sẽ không mang lại một cá thể FileService, như tôi muốn nó, nhưng một cá thể Service. Tôi cho rằng vấn đề ở đây là từ khóa "tự" được sử dụng trong constructor dịch vụ.

Có cách nào khác để đạt được những gì tôi muốn ở đây không? Mã singleton chỉ là một vài dòng, nhưng tôi vẫn muốn tránh bất kỳ sự thừa mã nào bất cứ khi nào tôi có thể.

Trả lời

46

Code:

abstract class Singleton 
{ 
    protected function __construct() 
    { 
    } 

    final public static function getInstance() 
    { 
     static $instances = array(); 

     $calledClass = get_called_class(); 

     if (!isset($instances[$calledClass])) 
     { 
      $instances[$calledClass] = new $calledClass(); 
     } 

     return $instances[$calledClass]; 
    } 

    final private function __clone() 
    { 
    } 
} 

class FileService extends Singleton 
{ 
    // Lots of neat stuff in here 
} 

$fs = FileService::getInstance(); 

Nếu bạn sử dụng PHP < 5.3, thêm này quá:

// get_called_class() is only in PHP >= 5.3. 
if (!function_exists('get_called_class')) 
{ 
    function get_called_class() 
    { 
     $bt = debug_backtrace(); 
     $l = 0; 
     do 
     { 
      $l++; 
      $lines = file($bt[$l]['file']); 
      $callerLine = $lines[$bt[$l]['line']-1]; 
      preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l]['function'].'/', $callerLine, $matches); 
     } while ($matches[1] === 'parent' && $matches[1]); 

     return $matches[1]; 
    } 
} 
+0

FYI: Mã này sử dụng ['get_called_class'] (http://us2.php.net/manual/en/function.get-called-class.php), được thêm vào trong PHP 5.3. Làm điều này trong các phiên bản trước đó là một chút khó khăn hơn. – Charles

+0

Cảm ơn Charles, đã cập nhật câu trả lời để khắc phục điều đó. –

+1

Holy yikes, thật đáng sợ. Hãy tưởng tượng gọi 'getInstance' một chục lần, đó là một tá mở và một chục lần đọc của tập tin lớp. – Charles

7

Nếu tôi chú trọng hơn 5,3 lớp, tôi sẽ biết làm thế nào để giải quyết việc này bản thân mình. Sử dụng tính năng kết buộc tĩnh muộn mới của PHP 5.3, tôi tin rằng đề xuất của Coronatus có thể được đơn giản hóa thành điều này:

class Singleton { 
    protected static $instance; 

    protected function __construct() { } 

    final public static function getInstance() { 
     if (!isset(static::$instance)) { 
      static::$instance = new static(); 
     } 

     return static::$instance; 
    } 

    final private function __clone() { } 
} 

Tôi đã thử nó, và nó hoạt động như một sự quyến rũ. Mặc dù vậy, bản 5.3 trước vẫn là một câu chuyện hoàn toàn khác.

+4

Có vẻ như chỉ có một trường duy nhất '$ instance' cho tất cả các lớp con, vì vậy chỉ có singleton của lớp mà' getInstance() 'được gọi là đầu tiên được trả về. –

+0

Đây là cách tiếp cận ngây thơ mà tiếc là không thể làm việc ở tất cả như C-Otto đã đề cập. Downvoted: Đừng làm điều này ở nhà ;-) – Phil

+0

Như họ đã nói ở trên .. nó sai, điều này sẽ cho bạn ví dụ của lớp đầu tiên sử dụng getInstance() – Juan

0

Đây là câu trả lời của Johan. PHP 5.3+

abstract class Singleton 
{ 
    protected function __construct() {} 
    final protected function __clone() {} 

    final public static function getInstance() 
    { 
     static $instance = null; 

     if (null === $instance) 
     { 
      $instance = new static(); 
     } 

     return $instance; 
    } 
}