首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在PHP中实现注册表模式的可伸缩方式?

在PHP中实现注册表模式的可伸缩方式?
EN

Stack Overflow用户
提问于 2014-07-14 17:34:34
回答 1查看 3.4K关注 0票数 3

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

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

因此,我想出了一个想法,即书记官处更适合这种情况下的需要。

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

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

让我们来看看基本注册表(非常简单的实现,只是从一本书中摘取的一个例子):

代码语言:javascript
复制
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]);
    } 
  }
 }

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

代码语言:javascript
复制
$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时,类内(从书中摘录):

代码语言:javascript
复制
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()和Registry::getInstance()方法中的set(),以适应这些更改,或者有更明智的方法吗?

你们中有人遇到这个问题了吗?他们找到了一种简单的方法来交换不同的注册中心,这取决于应用程序的类型和复杂性等等。

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

谢谢大家的关注!希望能得到帮助!

EN

回答 1

Stack Overflow用户

发布于 2014-07-15 08:53:00

首先。很高兴你自己发现了你的方法的问题。通过使用注册表,您可以将类紧密耦合到从中提取依赖项的注册表。不仅如此,如果您的类必须关心它们如何存储在注册表中并从注册表中被抓取(在您的例子中,每个类也实现了一个单例),那么您也违反了单一责任原则

记住:从类内从任何存储中全局访问对象将导致类和存储之间的紧密耦合。

让我们看看马丁·福勒对这个话题有什么看法?

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

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

所以你看,这取决于你在建造什么。如果您有一个小应用程序,依赖程度很低,那就去死吧,继续使用注册表(但是您绝对应该放弃一个类行为来将自己存储到注册表中,或者从注册表中抓取)。如果不是这样的话,并且您正在构建复杂的服务,您需要一个干净而直观的API,通过使用Type提示和Constructor注入显式地定义依赖项。

代码语言:javascript
复制
<?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-容器的内容。Fabien Potencier写了以下文章

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

服务定位器和DI容器之间的界限似乎很模糊,但我喜欢这样想:服务定位器隐藏类的依赖关系,而DI容器不隐藏依赖项(这与简单单元测试的好处一样)。

所以你看,没有最终的答案,这取决于你在建设什么。我可以建议更深入地研究这个主题,因为如何管理依赖关系是每个应用程序的核心关注点。

进一步阅读

  • 为什么注册表模式是反模式。还有什么是替代方案。
  • 服务定位器是一种反模式。
  • 您需要依赖注入容器吗?
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24742383

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档