Статьи

@ Автосоединение всех вещей!

Недавно я написал, что аннотация @Autowired делает нашу жизнь проще , поскольку позволяет писать меньше кода. Однако его использование часто может усложнить ваш дизайн. Особенно, когда мы говорим об использовании его в свойствах класса. Это облегчает нарушение
Принцип единой ответственности . Так легче не замечать этого.

Можно использовать @Autowired со свойствами, но я думаю, что присутствие конструктора визуализирует проблему со слишком большим количеством зависимостей, когда она возникает. Даже не проявляя дополнительной заботы с нашей стороны.

Код, который растет

Давайте использовать известный пример из статьи о конструкторах и установщиках , но мы его немного изменим.

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

На основе требований мы создали следующий фрагмент кода:

1
2
3
4
5
6
7
8
public class SomeHandler {
   @Autowired private final Repository repository;
   @Autowired private final Trigger trigger;
 
   public void handle(SomeEvent event) {
       // some code
   }
}

Но изменения — это единственное, что точно. У клиента появились новые требования, и нам пришлось расширить функциональность. Клиент хотел сохранить всю информацию, прежде чем принять соответствующие меры. Они также хотели получать уведомления в случае возникновения чрезвычайных ситуаций.

После всех изменений мы получили что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
public class SomeHandler {
   @Autowired private final Repository repository;
   @Autowired private final Trigger trigger;
   @Autowired private final SnapshotTaker snapshotTaker;
   @Autowired private final Notifier notifier;
 
   public void handle(SomeEvent event) {
       // some code
   }
}

Это хорошо написанный код с точки зрения дизайна класса? Ну, я считаю, что три-четыре зависимости определенно должны дать нам повод задуматься о небольшом редизайне.

И четыре, к сожалению, не самое большое количество зависимостей, которые я когда-либо видел в коде одного класса…

Где в нем @Autowired?

Хорошо, но что с этим делать @Autowired? Решение проблемы состоит в том, чтобы реорганизовать и перепроектировать код, когда это необходимо, я прав? Я верю, что некоторые из вас согласятся со мной. Однако, прежде чем принять решение о каких-либо изменениях, мы должны были определить проблему.

Хорошо, еще раз — что @Autowired должен делать с этим? Разве не очевидно, что количество зависимостей является проблематичным? Ну, использование @Autowired на поле делает его немного размытым. Код выше не выглядит огромной болью, когда вы смотрите на него. Это всего четыре строки кода, короткие строки кода. Мы можем утверждать, что любой хороший разработчик должен знать, когда их слишком много, но почему мы должны предполагать, когда мы можем написать код, который может выразить саму проблему?

Если все эти зависимости необходимы, мы можем использовать конструктор для их внедрения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public class SomeHandler {
   private final Repository repository;
   private final Trigger trigger;
   private final SnapshotTaker snapshotTaker;
   private final Notifier notifier;
    
   @Autowired
   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker, Notifier notifier) {
       this.repository = repository;
       this.trigger = trigger;
       this.snapshotTaker = snapshotTaker;
       this.notifier = notifier;
   }
 
   public void handle(SomeEvent event) {
       // some code
   }
}

Теперь код «говорит» нам что-то. Это длинное объявление конструктора выглядит не очень хорошо. Нам не нужно думать, много ли четыре или нет. Мы видим, что.

И в примере мы используем короткие имена обоих классов и методов, но в реальном приложении эти имена иногда намного длиннее, чтобы быть как можно более описательными:

1
2
3
4
5
6
7
@Autowired
public SomeHandler(EventRepository eventRepository, EventActionTrigger eventActionTrigger, EventSnapshotTaker eventSnapshotTaker, EmergencyIssueNotifier emergencyIssueNotifier) {
   this.repository = eventRepository;
   this.trigger = eventActionTrigger;
   this.snapshotTaker = eventSnapshotTaker;
   this.notifier = emergencyIssueNotifier;
}

Теперь проблема более заметна, не так ли?

А добавление еще одной зависимости просто повредит нашим глазам (если это еще не произошло):

1
2
3
4
5
6
7
8
@Autowired
public SomeHandler(EventRepository eventRepository, EventActionTrigger eventActionTrigger, EventSnapshotTaker eventSnapshotTaker, EmergencyIssueNotifier emergencyIssueNotifier, SomeAnotherDependency someAnotherDependency) {
   this.eventRepository = eventRepository;
   this.eventActionTrigger = eventActionTrigger;
   this.eventSnapshotTaker = eventSnapshotTaker;
   this.emergencyIssueNotifier = emergencyIssueNotifier;
   this.someAnotherDependency = someAnotherDependency;
}

Нам нужно идти глубже!

Но не останавливайся в этот момент. Давайте посмотрим на код, когда мы расширяем класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public interface Handler {
 void handle(Event event);
}
 
public abstract class BasicEventHandler {
   @Autowired private final EventRepository eventRepository;
   @Autowired private final EventActionTrigger eventActionTrigger;
 
   // some code
}
 
public class SomeHandler extends BasicEventHandler implements Handler {
   @Autowired private final EventSnapshotTaker eventSnapshotTaker;
   @Autowired private final EmergencyIssueNotifier emergencyIssueNotifier;
   @Autowired private final SomeAnotherDependency someAnotherDependency;
    
   public void handle(SomeEvent event) {
       // some code
   }
}

В первом примере мы можем поспорить, достаточно ли велико количество строк кода, объявляющих зависимости объекта, чтобы заставить нас волноваться. Когда мы расширяем класс, это число в определенном классе может быть в порядке.

Однако проблема с этим кодом еще больше, чем в предыдущем примере.

Автор, как правило, уверен, что это хорошее решение. Так мы, авторы, относимся к тому, что создаем.

Но на этот раз даже рецензент кода может не заметить проблему. К сожалению, не все будут смотреть на родительский класс. Когда код находится в обзоре, это требует дополнительных усилий. Даже не проверяя ветку, мы все равно должны пойти в нашу IDE и открыть класс. Может быть, это не так много, но поверьте мне, что для некоторых из нас это слишком много.

Не было бы проблемы заметить проблему, если бы мы договорились, что конструктор является обязательной частью наших классов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class SomeHandler extends BasicEventHandler implements Handler {
   private final EventSnapshotTaker eventSnapshotTaker;
   private final EmergencyIssueNotifier emergencyIssueNotifier;
   private final SomeAnotherDependency someAnotherDependency;
 
   @Autowired
   public SomeHandler(EventRepository eventRepository, EventActionTrigger eventActionTrigger, EventSnapshotTaker eventSnapshotTaker, EmergencyIssueNotifier emergencyIssueNotifier, SomeAnotherDependency someAnotherDependency) {
      super(eventRepository, eventActionTrigger);
      this.eventSnapshotTaker = eventSnapshotTaker;
      this.emergencyIssueNotifier = emergencyIssueNotifier;
      this.someAnotherDependency = someAnotherDependency;
   }
   
   public void handle(SomeEvent event) {
       // some code
   }
}

Сделай это очевидным!

Мы можем думать о соглашениях с точки зрения того, сколько их слишком много . Мы можем попытаться следовать нашим собственным правилам. Тем не менее, всегда есть кто-то, кто бросит вызов этому. Кто напишет код и постарается доказать, что все сходится и должно быть вместе, и это еще не «слишком много».

Не спорьте, пусть код говорит сам за себя!

Ссылка: @ Автосоединение всех вещей! от нашего партнера JCG Себастьяна Малаки в блоге « Давайте поговорим о Java» .