反对
global变量的重点是它们非常紧密地耦合代码。您的 整个代码库 取决于a)变量 名称
$config和b)该变量的存在。如果要重命名变量(无论出于何种原因),则必须 在 整个代码库中的 任何地方
进行重命名。您也不能再使用任何依赖于变量的代码了。
带有
global变量的示例:
require 'SomeClass.php';$class = new SomeClass;$class->doSomething();
在上述各行中的任何地方,您都可能会出错,因为类或某些代码
SomeClass.php隐式依赖于全局变量
$config。尽管只看了课,却没有任何迹象表明。要解决此问题,您必须执行以下操作:
$config = array(...);require 'SomeClass.php';$class = new SomeClass;$class->doSomething();
如果未在其中设置正确的键,则此代码可能 仍会
失败
$config。由于不清楚配置数组的哪些部分
SomeClass需要或不需要以及何时需要它们,因此很难为其创建正确的环境以使其正常运行。如果您碰巧已经有一个变量
$config要用在其他任何地方,它也会产生冲突
SomeClass。
因此,不要创建隐式,不可见的依赖关系,而是 注入 所有依赖关系:
require 'SomeClass.php';$arbitraryConfigVariableName = array(...);$class = new SomeClass($arbitraryConfigVariableName);$class->doSomething();
通过显式传递config数组作为参数,可以解决上述所有问题。这就像在应用程序内部 分发所需的信息
一样简单。它还使应用程序的结构和流程以及讨论的内容变得更加清晰。要达到这种状态(如果您的应用程序当前是一个大问题),可能需要进行一些重组。
您的代码库越大,则您越需要 将 各个部分彼此 分离
。如果每个部分都依赖于代码库中的每个其他部分,则您根本无法单独测试,使用或重用它的任何部分。那简直变成混乱。为了将各个部分彼此分开,请将它们编码为类或函数,这些类或函数将其所有必需数据作为参数。这就在代码的不同部分之间创建了清晰的接缝(接口)。
尝试将您的问题归纳为一个示例:
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 $dbasearlier$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与该函数的任何特定实现分离。如果仅使用global
functionloadConfigurationFromDatabase(),则基本上还会遇到相同的问题:尝试调用该函数时需要定义该函数,如果要用其他函数替换它,则存在命名冲突。通过使用对象,代码的关键部分移至此处:
$config = $configManager->loadConfigurationFromDatabase($db);
您可以
$configManager在这里替换同样具有method的
任何其他对象
loadConfigurationFromDatabase。那就是“鸭子打字”。
$configManager只要它有一个方法,您都不关心它到底是什么
loadConfigurationFromDatabase。如果它走路像鸭子,而嘎嘎叫鸭子,那它就是鸭子。或者说,如果它有一个
loadConfigurationFromDatabase方法并返回有效的配置数组,则它是某种ConfigManager。您已将代码与一个特定变量
$config,一个特定
loadConfigurationFromDatabase函数甚至一个特定变量解耦
ConfigManager。可以从任何地方动态更改和替换所有零件,并进行替换和加载,因为代码不依赖于任何其他特定零件。
该
loadConfigurationFromDatabase方法本身也不依赖于任何一个特定的数据库连接,只要它可以调用
query它并获取结果即可。该
$db对象被传递到它可能是完全伪造的,并从XML文件中读取其数据或其他地方代替,只要它的
界面 仍然表现相同。



