我应该从我的代码中删除静态函数吗?


Should I remove static function from my code?

我的代码位于这里:https://github.com/maniator/SmallFry

我是否应该这样做,以便App类不必使用静态函数,但同时能够从任何地方设置和设置应用程序的变量?

或者我应该保持它现在是如何与App::getApp::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依赖项注入到需要它的任何类(或类树)中。这样,在测试或重用代码时,你就可以注入任何你想要的东西。

注意我在这里说了重用。这是因为很难重用包含静态调用的代码。这是因为它被绑定到全局状态,所以你不能真正"改变"子请求的状态(或任何你想做的)。

现在,回到手头的问题。看起来您有一个遗留代码库,这将使事情复杂化。我的处理方法如下:

  1. 创建一个app类的非静态版本(现在给它起个不同的名字),它什么都不做,只是把它的get/set调用代理给真正的app类。例如:

    class AppProxy {
        public function set($value) {
            return App::set($value);
        }
    }
    

    现在,它所要做的就是代理。一旦我们完成了所有与代理(而不是静态应用程序)对话的代码,我们将使它实际起作用。但在此之前,这将保持应用程序运行。这样你就可以慢慢地执行这些步骤,而不需要一次完成所有的步骤。

  2. 选择一个易于控制实例化的主类(对应用程序有很多作用的类,或者很重要的类)。最好只在一个地方实例化(在bootstrap中是最简单的)。将这个类修改为通过构造函数使用依赖注入来获取"appproxy"。

    。测试这个!

  3. 选择另一个类树来工作,基于你认为最重要和最容易的。

    。测试! !

  4. 如果你有更多的呼叫App::,转到#3

  5. 将现有的App类更改为非静态。

    。测试 !!!!!!!!!!

  6. 删除AppProxy,在依赖注入器中替换为App。如果你做对了,你应该只需要改变一个地方来完成这个切换。

  7. 拍拍自己的背,去喝一杯,因为你已经完成了。

我把它分割成这样的原因是,一旦完成了一个步骤(任何步骤),您仍然可以发布工作软件。因此,这种转换可能需要几个月的时间(取决于代码库的大小),而不会中断业务。

现在,一旦你完成了,你确实得到了一些显著的好处:

  1. 易于测试,因为你可以创建一个新的应用对象来注入(或根据需要模拟它)。

  2. 副作用更容易看到,因为App对象在任何可以改变的地方都是必需的。

  3. 这样更容易组件化库,因为它们的副作用是局部化的/

  4. 如果应用程序的核心类是注入的,那么它比静态的更容易重写(多态性)。

我可以继续,但我认为很容易找到关于为什么静态通常不好的资源。所以这就是我将使用的方法,从静态类迁移到实例…

如果你不想有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();
    }