反对的点global
变量的优点是它们与代码的耦合非常紧密。你的整个代码库取决于 a) 变量name $config
b) 该变量的存在。如果您想重命名变量(无论出于何种原因),您必须这样做到处整个代码库。您也不能再使用任何独立于该变量的代码段。
示例为global
多变的:
require 'SomeClass.php';
$class = new SomeClass;
$class->doSomething();
在上面几行的任何地方,您都可能会收到错误,因为类或某些代码SomeClass.php
隐式依赖于全局变量$config
。尽管仅查看班级,但没有任何迹象表明这一点。为了解决这个问题,你必须这样做:
$config = array(...);
require 'SomeClass.php';
$class = new SomeClass;
$class->doSomething();
这段代码可能still如果你没有在里面设置正确的键,就会在某个地方失败$config
。因为配置数组的哪些部分并不明显SomeClass
需要或不需要,以及当需要它们时,很难重新创建正确的环境以使其正确运行。如果您碰巧已经有一个变量,它也会产生冲突$config
用于其他任何您想使用的地方SomeClass
.
因此,与其创建隐式的、不可见的依赖关系,inject所有依赖项:
require 'SomeClass.php';
$arbitraryConfigVariableName = array(...);
$class = new SomeClass($arbitraryConfigVariableName);
$class->doSomething();
通过显式传递配置数组作为参数,上述所有问题都得到解决。就这么简单传递所需的信息在您的应用程序内。它还使应用程序的结构和流程以及对话内容变得更加清晰。如果您的应用程序目前是一个大泥球,要达到这种状态可能需要进行一些重组。
你的代码库越大,你需要做的事情就越多decouple各个部分彼此分离。如果每个部分都依赖于代码库中的每个其他部分,那么您根本无法单独测试、使用或重用它的任何部分。这只会陷入混乱。要将各个部分彼此分开,请将它们编码为类或函数,将所有必需的数据作为参数。这会在代码的不同部分之间创建干净的接缝(接口)。
尝试将您的问题结合到一个示例中:
require_once 'Database.php';
require_once 'ConfigManager.php';
require_once 'Log.php';
require_once 'Foo.php';
// establishes a database connection
$db = new Database('localhost', 'user', 'pass');
// loads the configuration from the database,
// the dependency on the database is explicit without `global`
$configManager = new ConfigManager;
$config = $configManager->loadConfigurationFromDatabase($db);
// creates a new logger which logs to the database,
// note that it reuses the same $db as earlier
$log = new Log($db);
// creates a new Foo instance with explicit configuration passed,
// which was loaded from the database (or anywhere else) earlier
$foo = new Foo($config);
// executes the conversion function, which has access to the configuration
// passed at instantiation time, and also the logger which we created earlier
$foo->conversion('foo', array('bar', 'baz'), $log);
我将把各个类的实现作为读者的练习。当您尝试实现它们时,您会发现它们的实现非常简单明了,并且不需要任何操作global
。每个函数和类都获取以函数参数形式传递的所有必需数据。显然,上述组件可以以任何其他组合插入在一起,或者依赖项可以轻松地替换为其他组件。例如,配置根本不需要来自数据库,或者记录器可以记录到文件而不是数据库而无需Foo::conversion
必须了解所有这些。
示例实现ConfigManager
:
class ConfigManager {
public function loadConfigurationFromDatabase(Database $db) {
$result = $db->query('SELECT ...');
$config = array();
while ($row = $result->fetchRow()) {
$config[$row['name']] = $row['value'];
}
return $config;
}
}
这是一段非常简单的代码,甚至没有做太多事情。您可能会问为什么要将其作为面向对象的代码。关键是,这使得使用该代码变得非常灵活,因为它将它与其他所有代码完美地隔离开来。你给出一个数据库连接,你就会得到一个具有特定语法的数组。输入→输出。清晰的接缝、清晰的接口、最小的、明确的职责。您可以使用一个简单的函数执行相同的操作。
对象的额外优点是它甚至可以进一步解耦调用的代码loadConfigurationFromDatabase
来自该函数的任何特定实现。如果你只是使用全局function loadConfigurationFromDatabase()
,您基本上又遇到了同样的问题:当您尝试调用该函数时需要定义该函数,并且如果您想用其他东西替换它,则会出现命名冲突。通过使用对象,代码的关键部分移至此处:
$config = $configManager->loadConfigurationFromDatabase($db);
你可以替代$configManager
这里是为了任何其他物体也有一个方法loadConfigurationFromDatabase
。这就是“鸭子打字”。你不在乎具体是什么$configManager
是的,只要有方法loadConfigurationFromDatabase
。如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子。或者更确切地说,如果它有一个loadConfigurationFromDatabase
方法并返回一个有效的配置数组,它是某种 ConfigManager。您已将代码与一个特定变量解耦$config
,从一个特定的loadConfigurationFromDatabase
函数,甚至从一个特定的函数ConfigManager
。所有部分都可以从任何地方动态更改、换出、替换和加载,因为代码不依赖于任何特定的其他部分。
The loadConfigurationFromDatabase
方法本身也不依赖于任何一个特定的数据库连接,只要它可以调用query
并获取结果。这$db
传递给它的对象可能完全是假的,并且可以从 XML 文件或其他任何地方读取其数据,只要它是界面行为仍然相同。