gpt4 book ai didi

php - 在 PHP 中实现注册表模式的可扩展方式?

转载 作者:可可西里 更新时间:2023-11-01 00:32:14 24 4
gpt4 key购买 nike

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

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

所以我想到了注册表在这种情况下更适合这种需求。

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

  • 显式或外部,这意味着每次您需要在脚本中恢复实例或需要将实例放入其中时,都应调用注册表;
  • 隐式或内部,这意味着您使用 getInstance() 方法创建一个抽象类,该方法返回一个具有 get_called_class() 后期静态绑定(bind)功能的实例,将其添加到注册表,然后从注册表本身返回该实例请注意,如果将 $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]);
}
}
}

我知道,Registry 可能是一个单例,所以您永远不会同时拥有两个 Registry(谁需要它们有人会想到,但谁知道)。无论如何,存储/访问实例的外部方式是这样的:

$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() 的调用和 Registry::set() 在 getInstance() 方法中以适应变化,还是有更聪明的方法?

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

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

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

最佳答案

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

请牢记一条经验法则:从任何存储中的类中全局访问对象将导致类和存储之间的紧密耦合。

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

The key difference is that with a Service Locator every user of a service has a dependency to the locator. The locator can hide dependencies to other implementations, but you do need to see the locator. So the decision between locator and injector depends on whether that dependency is a problem.

With the service locator you have to search the source code for calls to the locator. Modern IDEs with a find references feature make this easier, but it's still not as easy as looking at the constructor or setting methods.

因此您看到这取决于您正在构建的内容。如果你有一个依赖性低的小应用程序,管它呢,继续使用注册表(但是你绝对应该放弃一个类行为来将自己存储到注册表中或从注册表中获取)。如果不是这种情况,并且您正在构建复杂的服务并想要一个干净直接的 API,请使用类型提示和构造函数注入(inject)显式定义您的依赖项。

<?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);

Should be a configuration class the solution? Or is there a smarter way to achieve a scalable registry pattern if it is possible?

这种方法经常遇到,您可能听说过 DI 容器。 Fabien Potencier writes the following :

A Dependency Injection Container is an object that knows how to instantiate and configure objects. And to be able to do its job, it needs to knows about the constructor arguments and the relationships between the objects.

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

所以你看,没有最终答案,这取决于你正在构建什么。我建议深入探讨该主题,因为如何管理依赖项是每个应用程序的核心问题

进一步阅读

关于php - 在 PHP 中实现注册表模式的可扩展方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24742383/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com