0

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

现在,我编写代码如下:

  1. 数据库层:
    这主要对数据库进行 CURD 操作。

    class MyDatabaseLayer {
        public int findValue(int k) {
            // find v
        }
    
        public void insertValue(int k, int v) {
            // Insert v
        }
    }
    
  2. BusinessLogic:
    这包含调用数据库层和执行操作的实际逻辑。

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

    MyBusinessLogic logic = new MyBusinessLogic(new MyDatabaseLayer ()); //The problem
    logic.manipulateValue(5);
    

现在如果你在应用层看到,它知道数据库层,这是错误的。它知道的太多了。

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

4

2 回答 2

1

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

无论您使用什么工具,guice / spring / picocontainer / singleton-factories,您的应用程序代码应该类似于:

@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;
   } 
   ...
}

但是到那时,picocontainer、guice 和 spring 可以更好地解决上述 SOOO 的复杂性。

此外,像 spring 这样尊重 java 6 注释的东西意味着你可以做除了构造函数注入之外的事情,如果你有多个相同基本数据类型的配置项(例如字符串),这非常有用。

于 2011-11-18T01:13:21.213 回答
1

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

使用 Guice,您可以将接口绑定到 aModuleModules 中的具体类。然后,您将Injector使用这些Modules 创建一个并从Injector.

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在创建对象后直接从对象中取出。

于 2011-11-18T13:17:52.537 回答