Правильный дизайн классов, созданных для тестируемости с использованием инжектора конструктора - PullRequest
0 голосов
/ 18 ноября 2011

Скажем, у меня есть эти 3 слоя моего кода:
1. Уровень базы данных (ORM)
2. BusinessLogic
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?

Ответы [ 2 ]

1 голос
/ 18 ноября 2011

Обратите внимание, что для тестируемости, к которой обращается Миско, в идеале вы хотите создать интерфейсы для MyDatabaseLayer, MyBusinessLogic и т. Д., И чтобы конструкторы использовали эти интерфейсы, а не конкретные классы, чтобы при тестировании вы можете легко передать поддельные реализации, которые фактически не используют базу данных и т. д.

С Guice вы привязываете интерфейсы к конкретным классам в Module или Module s. Затем вы создадите Injector с использованием этих Module s и получите некоторый корневой объект (например, объект вашего приложения) из 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;
}

Это, конечно, очень простой пример, и есть гораздо более сложные вещи, которые вы можете сделать. Например, в веб-приложении вы можете использовать инжекцию конструктора в сервлеты с помощью Guice Servlet, а не получать объект непосредственно из Injector после его создания.

1 голос
/ 18 ноября 2011

Часть, которую вам не хватает с инверсией управления, состоит в том, что прикладной уровень не вызывает конструктор напрямую. Он использует фабрику (контейнер IoC) для заполнения параметра конструктора.

Какой бы инструмент вы ни использовали, guice / spring / picocontainer / singleton-factory, код вашего приложения должен выглядеть примерно так:

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

Но в этот момент пикоконтейнер, обтекатель и пружина могут гораздо лучше решить все сложности вышеуказанного СООО.

Кроме того, такие вещи, как spring, использующие аннотации java 6, означают, что вы можете делать что-то кроме внедрения в конструктор, что ОЧЕНЬ полезно, если у вас есть несколько элементов конфигурации одного базового типа данных (например, строки).

...