Статьи

Конструктор или сеттер?

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

Когда мы говорим об инициализации объекта, нам часто нужно думать о зависимостях. Как вы будете вводить их? Будете ли вы использовать конструктор или сеттер?

Позвольте мне помочь вам принять правильное решение.

Давным-давно..

… Была необходимость справиться с каким-то событием. Для этого нам нужно было, прежде всего, получить необходимые данные из репозитория и затем передать их триггеру, который отвечал за запуск соответствующего действия на основе заданных данных.

В ходе реализации мы создали следующий класс:

1
2
3
4
5
6
7
8
9
public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }
 
   public void handle(SomeEvent event) {
       // some code
   }
}

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

После изменения наш класс выглядел так:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }
 
   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker) {
       // some code
   }
 
   public void handle(SomeEvent event) {
       // some code
   }
}

Проходит еще один месяц и еще одно требование исходит от нашего клиента. Они хотят иметь возможность включить уведомление сразу после запуска события. Это необходимо для них в случае возникновения каких-то неотложных вопросов. Они хотят иметь более высокую прозрачность.

Хорошо, теперь у нас есть две вещи, которые могут быть включены:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }
 
   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker) {
       // some code
   }
 
   public SomeHandler(Repository repository, Trigger trigger, Notifier notifier) {
       // some code
   }
 
   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker, Notifier notifier) {
       // some code
   }
 
   public void handle(SomeEvent event) {
       // some code
   }
}

Код выглядит хорошо, не так ли? Хорошо, это был риторический вопрос. Давайте сделаем что-нибудь с этим.

Конструктор или нет?

В приведенном выше примере мы получили класс с четырьмя конструкторами. Почему так много? Из-за меняющихся потребностей нашего клиента. И это прекрасно. Приложение должно удовлетворить потребности клиента.

В чем проблема? Проблема с дизайном класса.

Почему у нас так много конструкторов? Поскольку некоторые зависимости являются необязательными, их наличие зависит от внешних условий.

Нужно ли нам так много конструкторов?

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

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

С другой стороны, мы должны помещать в конструктор только необходимые зависимости . Конструктор не место для чего-либо необязательного. Если что-то необязательно, это означает, что нам не нужно создавать действительный объект.

Если мы хотим использовать другие зависимости, которые приятно иметь, мы должны внедрить их по-другому. И здесь вступают в игру сеттеры. Мы не обязаны вызывать метод установки. У нас может быть необходимость, но это не обязательно. Вы должны использовать сеттеры, когда зависимость является опцией .

Итак, нам нужно так много конструкторов? Пусть код будет ответом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }
 
   public void setSnapshotTaker(SnapshotTaker snapshotTaker) {
       // some code
   }
 
   public void setNotifier(Notifier notifier) {
       // some code
   }
 
   public void handle(SomeEvent event) {
       // some code
   }
}

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

Но ждать! Сеттер?!

Я не люблю сеттеров. Почему? Потому что эти методы каким-то образом нарушают инкапсуляцию .

Но что мы можем использовать вместо сеттеров? Что можно использовать вместо этого в данном примере?

Ну, мы не будем избегать этих методов. Или, если быть более точным, нам нужен их функционал. Необходимо разрешить клиенту включить функциональность. В данном примере мутаторы должны остаться, потому что они необходимы. Тем не менее, мы всегда можем сделать код лучше. Больше связанных с доменом. Как? Нам просто нужно показать эту связь с доменом:

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

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

Слишком много вариантов

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

Если вариантов слишком много, это может означать, что обязанностей слишком много, и стоит пересмотреть свое текущее решение.

Будьте очень осторожны при добавлении еще одной необязательной части в код класса. Может быть, этот класс делает слишком много?

Слово в конце

Надеюсь, вы найдете статью полезной.

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

Что дальше?

Пойдем и создадим какой-нибудь объект 🙂

Ссылка: Конструктор или сеттер? от нашего партнера JCG Себастьяна Малаки в блоге « Давайте поговорим о Java» .