我的代码位于这里:https://github.com/maniator/SmallFry
我是否应该这样做,以便App
类不必使用静态函数,但同时能够从任何地方设置和设置应用程序的变量?
或者我应该保持它现在是如何与App::get
和App::set
方法?
两者的优点和缺点是什么?
如果我要承担第一项任务,我将如何完成它?
示例代码:
//DEFAULT TEMPLATE
App::set('APP_NAME', 'SmallVC');
//END DEFAULT TEMPLAT
//
//DEFAULT TEMPLATE
App::set('DEFAULT_TEMPLATE', 'default');
//END DEFAULT TEMPLATE
//DEFAULT TITLE
App::set('DEFAULT_TITLE', 'Small-VC');
//END DEFAULT TITLE
//LOGIN SEED
App::set('LOGIN_SEED', "lijfg98u5;jfd7hyf");
//END LOGIN SEED
App::set('DEFAULT_CONTROLLER', 'AppController');
if(App::get('view')){
$template_file = $cwd.'/../view/'.App::get('view').'/'.App::get('method').'.stp';
if(is_file($template_file)){
include $template_file;
}
else {
include $cwd.'/../view/missingview.stp'; //no such view error
}
}
else {
App::set('template', 'blank');
include $cwd.'/../view/missingfunction.stp'; //no such function error
}
我想你有一种感觉,静态是不好的。我所发布的内容可能看起来相当疯狂,因为这是一个巨大的变化。至少希望它能呈现出一种不同的世界观。
Miško Hevery写的静态方法是可测试性的死亡。
我喜欢测试,因此我不使用它们。那么,我们还能怎么解决这个问题呢?我喜欢用一种我认为是依赖注入的方式来解决这个问题。Martin Fowler在这里有一篇很好的但是很复杂的文章。
对于构造时的每个对象,我传递了它们运行所需的对象。从你的代码中,我将使AppController变成:
class AppController
{
protected $setup;
public function __construct(array $setup = array())
{
$setup += array('App' => NULL, 'Database' => NULL);
if (!$setup['App'] instanceof App)
{
if (NULL !== $setup['App'])
{
throw new InvalidArgumentException('Not an App.');
}
$setup['App'] = new App();
}
// Same for Database.
// Avoid doing any more in the constructor if possible.
$this->setup = $setup;
}
public function otherFunction()
{
echo $this->setup['App']->get('view');
}
}
依赖项默认为最可能的值(if语句中的默认结构)。所以,通常你不需要传递设置。但是,当您在测试或需要不同的功能时,您可以传入模拟或不同的类(从正确的基类派生)。你也可以使用接口作为一个选项。
Edit更纯粹的依赖注入形式涉及进一步的更改。它要求总是传递必需的对象,而不是在没有传递对象时让类默认传递对象。我在+20K LOC的代码库中经历了类似的变化。在实施了它之后,我看到了全程的很多好处。对象封装得到了极大的改进。它让你觉得你拥有了真正的对象,而不是每一点代码都依赖于其他东西。
当你没有注入所有的依赖时抛出异常会让你快速修复问题。在一些引导代码中使用set_exception_handler设置一个良好的系统范围异常处理程序,您将很容易看到异常,并可以快速修复每个异常。然后,代码在AppController中变得更简单,构造函数中的检查变成:
if (!$setup['App'] instanceof App)
{
throw new InvalidArgumentException('Not an App.');
}
对于你编写的每个类,所有对象都将在初始化时构造。此外,对于对象的每个构造,您将传递所需的依赖项(或让您提供的默认依赖项)进行实例化。(当你忘记这样做的时候,你会注意到,因为在你测试它之前,你必须重写你的代码来去掉依赖项。)
看起来工作量很大,但是这些类更接近真实世界,测试变得轻而易举。您还可以在构造函数中轻松地查看代码中的依赖项。
嗯,如果是我,我的最终目标是将App
依赖项注入到需要它的任何类(或类树)中。这样,在测试或重用代码时,你就可以注入任何你想要的东西。
注意我在这里说了重用。这是因为很难重用包含静态调用的代码。这是因为它被绑定到全局状态,所以你不能真正"改变"子请求的状态(或任何你想做的)。
现在,回到手头的问题。看起来您有一个遗留代码库,这将使事情复杂化。我的处理方法如下:
-
创建一个app类的非静态版本(现在给它起个不同的名字),它什么都不做,只是把它的get/set调用代理给真正的app类。例如:
class AppProxy { public function set($value) { return App::set($value); } }
现在,它所要做的就是代理。一旦我们完成了所有与代理(而不是静态应用程序)对话的代码,我们将使它实际起作用。但在此之前,这将保持应用程序运行。这样你就可以慢慢地执行这些步骤,而不需要一次完成所有的步骤。
-
选择一个易于控制实例化的主类(对应用程序有很多作用的类,或者很重要的类)。最好只在一个地方实例化(在bootstrap中是最简单的)。将这个类修改为通过构造函数使用依赖注入来获取"appproxy"。
。测试这个!
-
选择另一个类树来工作,基于你认为最重要和最容易的。
。测试! !
-
如果你有更多的呼叫
App::
,转到#3 -
将现有的
App
类更改为非静态。。测试 !!!!!!!!!!
-
删除AppProxy,在依赖注入器中替换为App。如果你做对了,你应该只需要改变一个地方来完成这个切换。
-
拍拍自己的背,去喝一杯,因为你已经完成了。
我把它分割成这样的原因是,一旦完成了一个步骤(任何步骤),您仍然可以发布工作软件。因此,这种转换可能需要几个月的时间(取决于代码库的大小),而不会中断业务。
现在,一旦你完成了,你确实得到了一些显著的好处:
-
易于测试,因为你可以创建一个新的应用对象来注入(或根据需要模拟它)。
-
副作用更容易看到,因为App对象在任何可以改变的地方都是必需的。
-
这样更容易组件化库,因为它们的副作用是局部化的/
-
如果应用程序的核心类是注入的,那么它比静态的更容易重写(多态性)。
我可以继续,但我认为很容易找到关于为什么静态通常不好的资源。所以这就是我将使用的方法,从静态类迁移到实例…
如果你不想有static
函数,但global
访问从任何地方没有传递对象到实际需要的地方,那么你几乎只能使用一件事:
A global variable
所以你做得并不好。但这是我能想到的唯一能满足你要求的东西。
如果您的App
对象类似于应用程序配置,那么第一步可能是将它传递给需要它的对象:
class Login {
public function __construct() {
$this->_login_seed = App::get('LOGIN_SEED');
self::$_ms = Database::getConnection();
}
更改为:
class Login {
public function __construct(App $app) {
$this->_login_seed = $app->get('LOGIN_SEED');
self::$_ms = Database::getConnection();
}