我的代码位于: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
}
答案 0 :(得分:7)
我认为你有一种静态不好的感觉。我发布的内容似乎相当疯狂,因为它是一个巨大的变化。至少希望它对世界有不同的看法。
MiškoHevery写了static methods are a death to testability。
我喜欢测试,因此我不使用它们。那么,我们还能如何解决这个问题呢?我喜欢用我认为是一种依赖注入来解决它。马丁福勒有一篇很好但复杂的文章here。
对于构造中的每个对象,我传递它们操作所需的对象。从你的代码我将使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语句中的默认构造)。因此,通常您不需要通过设置。但是,当您正在测试或想要不同的功能时,您可以传入模拟或不同的类(源自正确的基类)。您也可以使用接口作为选项。
编辑更纯粹的依赖注入形式涉及进一步的变化。它要求您传递始终传递所需的对象,而不是在未传递对象时让类默认为一。我在+ 20K LOC的代码库中经历了类似的变化。实施之后,我看到了全程实现的诸多好处。对象封装大大改进。它让你感觉自己拥有真实的物体,而不是依赖于其他东西的每一段代码。
如果不注入所有依赖项,则抛出异常会导致您快速修复问题。通过在某些引导代码中使用set_exception_handler设置良好的系统范围异常处理程序,您可以轻松查看异常并快速修复每个异常。然后代码在AppController中变得更简单,构造函数中的检查变为:
if (!$setup['App'] instanceof App)
{
throw new InvalidArgumentException('Not an App.');
}
对于每个类,然后编写所有对象将在初始化时构建。此外,对于对象的每个构造,您将传递所需的依赖项(或者让您提供的默认依赖项)实例化。 (当您忘记执行此操作时,您会注意到,因为在测试之前必须重写代码以取出依赖项。)
看起来很多工作,但课程更接近现实世界,测试变得轻而易举。您还可以在构造函数中轻松查看代码中的依赖项。
答案 1 :(得分:3)
好吧,如果是我,我的最终目标是将App
依赖项注入需要它的任何类(或类树)。这样在测试或重用代码时,你可以注入任何你想要的东西。
注意我说在那里重用。那是因为很难重复使用静态调用的代码。那是因为它与全局状态联系在一起,所以你无法真正“改变”子请求的状态(或者你想做的任何事情)。
现在,关于手头的问题。您似乎拥有遗留代码库,这会使事情变得复杂。我接近它的方式如下:
创建app类的非静态版本(现在将其命名为不同的版本)除了代理对真实应用类的get / set调用之外什么也不做。所以,例如:
class AppProxy {
public function set($value) {
return App::set($value);
}
}
目前,它所要做的只是代理。一旦我们完成所有代码与代理而不是静态应用程序交谈,我们将使它实际上起作用。但在此之前,这将使应用程序继续运行。这样你就可以花时间实现这些步骤而不需要一次性完成所有这些步骤。
选择一个主要类(对应用程序有很多功能,或者很重要),可以轻松控制实例化。最好只在一个地方实例化(在引导程序中最简单)。通过构造函数更改该类以使用依赖注入来获取“appproxy”。
一个。测试一下!
根据您认为最重要和最简单的内容,选择另一个类树进行处理。
一个。测试!!!
如果您有更多来电App::
,请转到#3
将现有的App
类更改为非静态类。
一个。测试!!!!!!!!!!
删除AppProxy并在依赖注入器中替换为App。如果你做得对,你应该只有一个地方可以改变以进行这种转换。
拍拍自己背上去喝酒,因为你已经完成了。
我将其分割出来的原因是,一旦完成一步(任何步骤),您仍然可以发送工作软件。因此,这种转换可能需要几个月的时间(取决于您的代码库的大小)而不会像往常一样中断业务......
现在,一旦完成,您将获得一些显着的好处:
易于测试,因为您可以创建一个新的App对象来注入(或根据需要进行模拟)。
副作用更容易看到,因为无论何处可以更改,都需要App对象。
以这种方式对库进行组件化更容易,因为它们的副作用是本地化的
如果注入核心应用程序类,那么覆盖(多态)核心应用程序类比它是静态的更容易。
我可以继续,但我认为很容易找到有关静力学一般不好的资源。这就是我用来从静态类迁移到实例的方法......
答案 2 :(得分:2)
如果您不想拥有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();
}