duanmu6231 2011-05-13 09:11
浏览 69
已采纳

使用依赖注入进行基本初始化的可测试(小)应用程序

Been getting more and more in applying proper methodologies to code in a testable manner and the past 2 weeks have drastically changed my approach when starting a small app from scratch.

Since I very often have to write small php console applications, my current target is to have a minimal application methodology that initialises a small application in a testable manner. Just want the usual classes for config, DB connection with a properly used singleton , error_handling configuration and logging.

I have approached this in quite a few different ways, but as of right now I have no clue if I am at all in the proper direction. Reading up on posts about dependency injection, I have come to try creating objects that have objects as parameters all the while respecting the law of Demeter for easy mocking. I have eliminated global constants that were not truly constants. I have just eliminated most static calls, and for the needed singletons such as app-wide DB connection, trying to apply Mr Hevery's suggestions and contain/wrap it in an object that will be passed along with other needed objects in the collaborating classes.

This is a very simplistic example straight from the top of my head from what I have pieced together so far. The approach I am looking into is to use a class AppFactoryHelper that acts as a very simple factory and creates the objects noted above.

I create a config object that is just a regular array with getters and setters, a DB singleton connexion, a logger object, and the error_handling class all through this factory, ie:

// very sketchy outline of programm flow for initialisation, stripped of error handling 

require_once 'includes/settings.php';
require_once 'AppHelperFactory.php';
require_once 'container.php';

// object to load in container
$appObjects = array = ('log', 'db', 'error_handling');

$appHelperFactory = new AppHelperFactory;

//config object will be needed helper objects
$config = $appHelperFactory->createConfig($settings.php); // 

// create dependency container, sets config object as private property inside,
// container holds only getters for $config.
$container = $appHelperFactory->createContainer($config);
try{      
  foreach($appObject as $className){
     $methodName = 'create' . $className; 
     $container->{$value} = $appHelperFactory->{$methodName}($config);
 }


$app = new ObjectThatWillFinallyGetSomethingDone($container);

$app->doStuff();

This approach has raised questions though. Am I using dependency injection even close to the proper way I should? is the DB singleton better off in a container that way? And the thing that bugs me all the time in testing, how would I test my "main" file?

  • 写回答

2条回答 默认 最新

  • doulv1760 2011-05-14 21:13
    关注

    I've read your question soon after you posted it but I don't have a really solid approach to answersing.

    So i decided on just going trough your post from top to bottom and answering on everything that jumps me.

    DB connection with a properly used singleton

    Singletons have no use in PHP

    If you only need one, only make one. It just makes testing harder and introduces global state if you use a singleton.

    I have just eliminated most static calls, and for the needed singletons such as app-wide DB connection

    Again: "singleton" as in 'i only create one is fine. "Singleton" as in "the Pattern" is not. Just don't, you don't need it.

    contain/wrap it in an object that will be passed along with other needed objects in the collaborating classes.

    So basically a Registry? You put stuff in and your application expects that it can pull stuff out of it. So you are passing around an intermediate object instead of the real objects. It can work but it's usually not the best approach.

    This Google Tech Talk: The Clean Code Talks - Don't Look For Things! does a nice job at explaining why.

    The approach I am looking into is to use a class AppFactoryHelper that acts as a very simple factory and creates the objects noted above.

    You only create the basic objects for you application one in your bootstrap so I'm not all to sure why you want to wrap those into a factory but well. I'll get back to that when it comes to the code.

    Now to the code

    Let me say first that i tried reading those ~30 lines for 5 times until i got the hang of what you are trying to do even so the code is kinda short. Might just be me though ;)

    The requires:

    Nowadays usually an autoloader is used, if you don't like crippling your file structure by tying your classnames to it use something like phpab that always for flexibility.

    The objects:

    You abstract everything your application does with your AppHelperFactory.

    $appHelperFactory = new AppHelperFactory;
    $config = $appHelperFactory->createConfig($settings.php); 
    // dunno what $settings.php means here, i assume you mean "settings.php" or something
    

    I'm just suggesting that

    $config = new Config(); 
    $config->readFromFile("settings.php"); 
    

    would also get the job done and since i assume you don't need to create config objects all over the place you should not build a factory just for the sake of it.

    The same goes for your other objects.

    Your ObjectThatWillFinallyGetSomethingDone depends on a container that is expects to hold at least 4 objects and mocking those for testing will be quite a pain. (Or at least more of a pain than it needs to be).

    Especially when you are creating a web application i don't see the point in having a "application" object (if you use it as your "main method" thats fine i guess) but if you want it really unit testable you'd need to pass in a lot more objects to it than you currently have. Things like a router, some "controllerFactory" (or however you handle the request dispatching to the code that does your business logic) and so on.

    Am I using dependency injection even close to the proper way I should?

    You are using a Registry. Thats something else but related.

    Reads on that topic (pro and con):

    Flaw: Brittle Global State & Singletons (the "Adding or using registries" and "Adding or using service locators" sections)

    Do you need a Dependency Injection Container?

    Dealing with dependencies (just some samples)

    and somewhat related Object lifecycle control


    All in all the code you showed focuses on "build lots of stuff and put it somewhere in case i need it" and doesn't show what you are actually trying to do (usually answering requests).

    So for that.. yeah.. you managed to do that and apart from some minor code complaints

     $container->{$value} = $appHelperFactory->{$methodName}($config); // really? :(
    

    you kinda succeed in doing so. If thats actually helping or any good can't be said from just that piece of bootstrap I'd say.

    Hope that helps.. you can also jump into the PHP chat here on SO for those kinds of discussions if it something you can't get into a question because it's too subjective ;)

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥35 平滑拟合曲线该如何生成
  • ¥100 c语言,请帮蒟蒻写一个题的范例作参考
  • ¥15 名为“Product”的列已属于此 DataTable
  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 自己瞎改改,结果现在又运行不了了
  • ¥15 链式存储应该如何解决
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站