在PHP中实现Registry模式的可扩展方式?

时间:2014-07-14 17:34:35

标签: php design-patterns registry

我想知道是否有一种在PHP中实现注册表模式的好方法,让我更清楚一点:

我知道当你需要跟踪你实例化的对象以便重用它们而不是从脚本到脚本再次重新实例化时,就会使用注册表。我有一个数据库类,我只想实例化一次,然后用于我的所有脚本,我不想一次又一次地重新实例化它。另一个示例可以是表示当前登录用户的实例的User类。在这种情况下我无法使用Singleton,例如我需要另一个用户实例,例如当我想要检索当前登录用户的朋友等时。

所以我提出了这样的想法,即在这种情况下,注册表更适合这种需求。

我也知道有两种方法可以实现它,或者更好的两种方式来访问存储的实例:

  • 明确或外部,意味着每次需要恢复脚本中的实例或者需要在其中放置实例时都应调用注册表;
  • 隐式或内部,意味着您使用getInstance()方法创建一个抽象类,该方法返回具有get_called_class()后期静态绑定功能的实例,将其添加到注册表,然后从注册表本身返回该实例注意如果将$ label参数传递给getInstance()方法,那么将返回注册表中的特定实例。这种方法对消费者来说是透明的,在我看来更清洁,更整洁(不过我会展示这两种实现方式)。

让我们采用一个基本的注册表(非常简单的实现,只是一本书的例子):

class Registry {

static private $_store = array();

static public function set($object, $name = null)
{
  // Use the class name if no name given, simulates singleton
  $name = (!is_null($name)) ? $name: get_class($object);
  $name = strtolower($name);
  $return = null;
  if (isset(self::$_store[$name])) {
    // Store the old object for returning
    $return = self::$_store[$name];
  }
    self::$_store[$name]= $object;
    return $return;
}

  static public function get($name)
  {
    if (!self::contains($name)) {
      throw new Exception("Object does not exist in registry");
    }
    return self::$_store[$name];
  }

  static public function contains($name)
  {
    if (!isset(self::$_store[$name])) {
      return false;
    }
    return true;
  }

  static public function remove($name)
  {
    if (self::contains($name)) {
      unset(self::$_store[$name]);
    } 
  }
 }

我知道,注册表可能是一个单身人士,所以你从来没有同时拥有两个注册表(谁需要有人可以思考,但谁知道)。 无论如何,存储/访问实例的外部方式如下:

$read = new DBReadConnection;
Registry::set($read);
$write = new DBWriteConnection;
Registry::set($write);
// To get the instances, anywhere in the code:
$read = Registry::get('DbReadConnection');
$write = Registry::get('DbWriteConnection');

在内部,在调用getInstance时在类中(取自本书):

abstract class DBConnection extends PDO {
  static public function getInstance($name = null)
  {
    // Get the late-static-binding version of __CLASS__
    $class = get_called_class();
    // Allow passing in a name to get multiple instances
    // If you do not pass a name, it functions as a singleton
    $name = (!is_null($name)) ?: $class;
    if (!Registry::contains($name)) {
      $instance = new $class();
      Registry::set($instance, $name);
    }
    return Registry::get($name);
  }
}
class DBWriteConnection extends DBConnection {
  public function __construct()
  {
parent::__construct(APP_DB_WRITE_DSN, APP_DB_WRITE_USER, APP_DB_WRITE_PASSWORD);
} }
class DBReadConnection extends DBConnection {
  public function __construct()
  {
parent::__construct(APP_DB_READ_DSN, APP_DB_READ_USER,APP_DB_READ_PASSWORD);
  }
}

显然间接地提到注册表(第二种情况)对我来说似乎更具可扩展性,但是如果有一天我需要更改注册表并使用另一个实现,我需要将这些调用更改为Registry :: get()并且在getInstance()方法中使用Registry :: set()以适应变化或是否有更聪明的方法?

您是否有人遇到过这个问题并找到了一种简单的方法来根据复杂性等应用程序的类型交换不同的注册表?

应该是配置类的解决方案吗?或者,如果有可能,是否有更智能的方法来实现可扩展的注册表模式?

感谢您的关注!希望得到一些帮助!

1 个答案:

答案 0 :(得分:6)

首先。你自己发现自己的方法问题真是太棒了。通过使用注册表,您可以将类紧密耦合到从中提取依赖项的注册表。不仅如此,如果你的类必须关心它们如何存储在注册表中并从中获取(在你的情况下每个类也会实现一个单例),你也违反了Single-Responsibility-Principle

根据经验,请记住:从类中从任何存储中全局访问对象将导致类与存储之间的紧密耦合。

让我们看看what Martin Fowler has to say about this topic

  
    

关键区别在于,使用服务定位器,服务的每个用户都对定位器具有依赖性。定位器可以隐藏其他实现的依赖关系,但您确实需要查看定位器。因此,定位器和注入器之间的决定取决于该依赖性是否是一个问题。

  

  
    

使用服务定位器,您必须在源代码中搜索定位器的调用。具有查找引用功能的现代IDE使这更容易,但它仍然不像查看构造函数或设置方法那么容易。

  

所以你看,这取决于你正在建设什么。如果你有一个具有少量依赖关系的小应用程序,那么继续使用它,继续使用注册表(但你绝对应该删除一个类行为来存储自己或从注册表中获取)。如果情况并非如此,并且您正在构建复杂的服务,并希望通过使用类型提示和构造函数注入来明确定义依赖关系。

<?php

class DbConsumer {

    protected $dbReadConnection;
    protected $dbWriteConnection;

    public function __construct(DBReadConnection $dbReadConnection, DBWriteConnection $dbWriteConnection)
    {
        $this->dbReadConnection  = $dbReadConnection;
        $this->dbWriteConnection = $dbWriteConnection;
    }

}

// You can still use service location for example to grab instances
// but you will not pollute your classes itself by making use of it
// directly. Instead we'll grab instances from it and pass them into
// the consuming class

// [...]

$read   = $registry->get('dbReadConnection'); 
$write  = $registry->get('dbWriteConnection'); 

$dbConsumer = new DbConsumer($read, $write);
  
    

应该是配置类的解决方案吗?或者,如果有可能,是否有更智能的方法来实现可扩展的注册表模式?

  

经常遇到这种方法,你可能听说过DI-Container。 Fabien Potencier writes the following

  
    

依赖注入容器是一个知道如何实例化和配置对象的对象。为了能够完成它的工作,它需要知道构造函数参数和对象之间的关系。

  

服务定位器和DI-Container之间的界限似乎相当模糊,但我喜欢这样的概念:服务定位器隐藏类的依赖关系而DI-Container没有(来自以及简单的单元测试的好处)。

所以你看,没有最终答案,这取决于你正在建设什么。我可以建议进一步深入研究这个主题,因为依赖关系的管理方式是每个应用程序的核心问题

进一步阅读