Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/356.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 使用构造函数注入正确设计为可测试性而构建的类_Java_Inversion Of Control_Class Design_Guice_Testability - Fatal编程技术网

Java 使用构造函数注入正确设计为可测试性而构建的类

Java 使用构造函数注入正确设计为可测试性而构建的类,java,inversion-of-control,class-design,guice,testability,Java,Inversion Of Control,Class Design,Guice,Testability,假设我有这3层代码: 1.数据库层(ORM) 2.业务逻辑 3.应用 现在,我编写代码如下: 数据库层: 这主要是通过数据库执行CURD操作 class MyDatabaseLayer { public int findValue(int k) { // find v } public void insertValue(int k, int v) { // Insert v } } 业务逻辑: 这包含了调用数据库层和执行任务的

假设我有这3层代码:
1.数据库层(ORM)
2.业务逻辑
3.应用

现在,我编写代码如下:

  • 数据库层:
    这主要是通过数据库执行CURD操作

    class MyDatabaseLayer {
        public int findValue(int k) {
            // find v
        }
    
        public void insertValue(int k, int v) {
            // Insert v
        }
    }
    
  • 业务逻辑:
    这包含了调用数据库层和执行任务的实际逻辑

    class MyBusinessLogic {
        private MyDatabaseLayer dbLayer;
        public MyBusinessLogic(MyDatabaseLayer dbLayer) {
            this.dbLayer  = dbLayer;
        }
    
        public int manipulateValue(int k) {
            dbLayer.findValue(k);
            //do stuff with value
        }
    }
    
  • 应用层:
    这将调用业务逻辑并显示数据

    MyBusinessLogic logic = new MyBusinessLogic(new MyDatabaseLayer ()); //The problem
    logic.manipulateValue(5);
    
  • 现在,如果您看到应用程序层,它知道数据库层,这是错误的。它知道的太多了。


    Misko Hevery:构造函数注入很好。但如果我遵循这一点,我将如何实现抽象?Google Guice如何帮助我呢?

    控制反转缺少的部分是应用层不直接调用构造函数。。它使用工厂(IoC容器)来填充构造函数参数

    无论您使用什么工具,guice/spring/picocontainer/singleton Factorys,您的应用程序代码应该如下所示:

    @Controller
    class MyController {
      @Resource // Some container knows about this annotation and wires you in
      MyBusinessLogic myBusinessLogic;
    
      @RequestMethod("/foo/bar.*")
      public MyWebResponse doService(Response resp, long id, String val) {
         boolean worked = myBusinessLogic.manipulatevalue(id, val);
         return new MyWebResponse(worked);
      }
    }
    
    注意,myBusinessLogic可以通过几种方式注册—java的@Resource、MyBusinessLogicFactory.getMyBusinessLogic()、guice.get(myBusinessLogic.class)等

    穷人的解决办法是:

    package foo;
    class MyBusinessLogicFactory {
    
       static volatile MyBusinessLogic instance; // package-scoped so unit tests can override
       public static MyBusinessLogic getInstance() {
           if (instance == null) {
               synchronized(MyBusinessLogicFactory.class) {
                  instance = new MyBusinessLogic(MyDatabaseLayerFactory.getInstance());
               }
           }
           return instance;
       }
    }
    
    // repeat with MyDatabaseLayerFactory
    
    注意,上面的单例模型非常不推荐,因为它没有作用域。您可以将上面的内容封装在上下文中,例如

    class Context {
       Map<Class,Object> class2Instance = new ConcurrentHashMap<>();
       public <T> T getInstance(Class<T> clazz) {
          Object o = class2Instance.get(clazz);
          if (o == null) { 
            synchronized(this) {
              o = class2Instance.get(clazz);
              if (o != null) return (T)o;
              o = transitivelyLoadInstance(clazz); // details not shown
              for (Class c : loadClassTree(clazz)) { // details not shown
                class2Instance.put(c, o);
              }
            }
          }
          return (T)o;
       } 
       ...
    }
    
    类上下文{
    Map class2Instance=新的ConcurrentHashMap();
    公共T getInstance(类clazz){
    对象o=class2Instance.get(clazz);
    如果(o==null){
    已同步(此){
    o=class2实例get(clazz);
    如果(o!=null)返回(T)o;
    o=过渡状态(clazz);//未显示详细信息
    对于(c类:loadClassTree(clazz)){//未显示详细信息
    第2类惯性摆(c,o);
    }
    }
    }
    返回(T)o;
    } 
    ...
    }
    

    但在这一点上,picocontainer、guice和spring可以更好地解决上述问题的复杂性


    此外,spring等支持java 6注释的东西意味着您可以执行构造函数注入以外的操作,如果您有相同基本数据类型(例如字符串)的多个配置项,则构造函数注入非常有用。

    请注意,对于Misko提到的可测试性,理想情况下您希望为
    MyDatabaseLayer
    创建接口,
    MyBusinessLogic
    等,并让构造函数使用这些接口而不是具体的类,以便在测试中,您可以轻松地传递实际上不使用数据库的伪实现,等等

    使用Guice,可以将接口绑定到
    模块
    模块
    s中的具体类。然后,您将使用这些
    模块创建
    注入器
    ,并从
    注入器中获取一些根对象(例如,您的应用程序对象)

    Injector injector = Guice.createInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(MyDatabaseLayer.class).to(MyDatabaseLayerImplementation.class);
        // etc.
    });
    MyApplicationLayer applicationLayer = injector.getInstance(MyApplicationLayer.class);
    
    MyApplicationLayer
    中,您将注入业务逻辑:

    @Inject
    public MyApplicationLayer(MyBusinessLogic logic) {
      this.logic = logic;
    }
    

    这当然是一个非常简单的例子,你可以做很多更复杂的事情。例如,在web应用程序中,您可以使用Guice Servlet在Servlet上使用构造函数注入,而不是在创建对象后直接从
    Injector
    获取对象。

    是的,我有接口。我只是用这个例子来简化事情。你能给我指出一些复杂示例的参考资料吗?我会看看GUI,包括绑定、作用域、扩展等。顺便说一句,我正在处理一些遗留代码,我无法为
    MyDatabaseLayer
    提供接口。从Guice的角度来看,这会是一个问题吗?在扩展AbstractModule的类中的一个绑定中,我需要:
    bind(MyDatabaseLayerImplementation.class).to(MyDatabaseLayerImplementation.class)因为我没有接口。这是允许的吗?您可能无法更改数据库类以实现接口,但能否创建一个接口和一个委托给实际数据库类的实现?对于第二个问题,如果
    MyDatabaseLayerImplementation
    是一个具体的类,那么您根本不需要绑定它。。。不过,如果您愿意,您可以通过执行
    bind(MyDatabaseLayerImplementation.class)
    “但是在这一点上,picocontainer、guice和spring可以更好地解决上述问题的复杂性。”。你能详细说明那部分吗?