Сколько работы должно быть выполнено в конструкторе? Представляется целесообразным выполнить некоторые вычисления внутри конструктора, а затем инкапсулировать результаты. Таким образом, когда результаты требуются объектными методами, они будут готовы. Похоже, хороший подход? Нет, это не так. Это плохая идея по одной причине: она предотвращает сложение объектов и делает их нерасширяемыми.
Допустим, мы создаем интерфейс, который будет представлять имя человека:
| 1 2 3 | interfaceName {  String first();} | 
Довольно легко, правда? Теперь давайте попробуем реализовать это:
| 01 02 03 04 05 06 07 08 09 10 | publicfinalclassEnglishName implementsName {  privatefinalString name;  publicEnglishName(finalCharSequence text) {    this.parts = text.toString().split(" ", 2)[0];  }  @Override  publicString first() {    returnthis.name;  }} | 
 Что с этим не так?  Это быстрее, верно?  Он разбивает имя на части только один раз и инкапсулирует их.  Затем, независимо от того, сколько раз мы вызываем метод first() , он вернет одно и то же значение и не будет нуждаться в повторном разбиении.  Однако это ошибочное мышление!  Позвольте мне показать вам правильный путь и объяснить: 
| 01 02 03 04 05 06 07 08 09 10 | publicfinalclassEnglishName implementsName {  privatefinalCharSequence text;  publicEnglishName(finalCharSequence txt) {    this.text = txt;  }  @Override  publicString first() {    returnthis.text.toString().split("", 2)[0];  }} | 
Это правильный дизайн. Я вижу, как ты улыбаешься, поэтому позволь мне доказать свою точку зрения.
Прежде чем я начну доказывать, позвольте мне попросить вас прочитать эту статью: Композиционные декораторы против методов императивных утилит . Это объясняет разницу между статическим методом и составными декораторами. Первый приведенный выше фрагмент кода очень похож на метод императивной утилиты, даже если он выглядит как объект. Второй пример — это настоящий объект.
  В первом примере мы злоупотребляем new оператором и превращаем его в статический метод, который выполняет все вычисления для нас прямо здесь и сейчас .  Вот что такое императивное программирование.  В императивном программировании мы делаем все вычисления прямо сейчас и возвращаем полностью готовые результаты.  В декларативном программировании мы вместо этого пытаемся отложить вычисления как можно дольше. 
  Давайте попробуем использовать наш класс EnglishName : 
| 01 02 03 04 05 06 07 08 09 10 11 | finalName name = newEnglishName(  newNameInPostgreSQL(/*...*/));if(/* something goes wrong */) {  thrownewIllegalStateException(    String.format(      "Hi, %s, we can't proceed with your application",      name.first()    )  );} | 
  В первой строке этого фрагмента мы просто создаем экземпляр объекта и помечаем его name .  Мы пока не хотим идти в базу данных и извлекать полное имя оттуда, разбивать его на части и инкапсулировать их в name .  Мы просто хотим создать экземпляр объекта.  Такое поведение анализа будет побочным эффектом для нас и в этом случае замедлит работу приложения.  Как видите, нам может понадобиться только name.first() если что-то пойдет не так, и нам нужно создать объект исключения. 
Я хочу сказать, что любые вычисления внутри конструктора — плохая практика, и их следует избегать, поскольку они являются побочными эффектами и не запрашиваются владельцем объекта.
  Что касается производительности во время повторного использования name , вы можете спросить.  Если мы name.first() экземпляр EnglishName и затем вызовем name.first() пять раз, мы получим пять вызовов метода String.split() . 
Чтобы решить эту проблему, мы создаем другой класс, составной декоратор , который поможет нам решить эту проблему «повторного использования»:
| 01 02 03 04 05 06 07 08 09 10 11 | publicfinalclassCachedName implementsName {  privatefinalName origin;  publicCachedName(finalName name) {    this.origin = name;  }  @Override  @Cacheable(forever = true)  publicString first() {    returnthis.origin.first();  }} | 
  Я использую аннотацию Cacheable из jcabi-aspect , но вы можете использовать любые другие инструменты кэширования, доступные в Java (или других языках), такие как Guava Cache : 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | publicfinalclassCachedName implementsName {  privatefinalCache<Long, String> cache =    CacheBuilder.newBuilder().build();  privatefinalName origin;  publicCachedName(finalName name) {    this.origin = name;  }  @Override  publicString first() {    returnthis.cache.get(      1L,      newCallable<String>() {        @Override        publicString call() {          returnCachedName.this.origin.first();        }      }    );  }} | 
  Но, пожалуйста, не делайте CachedName изменчивым и лениво загружаемым — это анти-паттерн, который я обсуждал ранее в « Объектах должны быть неизменяемыми» . 
Вот как теперь будет выглядеть наш код:
| 1 2 3 4 5 | finalName name = newCachedName(  newEnglishName(    newNameInPostgreSQL(/*...*/)  )); | 
Это очень примитивный пример, но я надеюсь, что вы поняли идею.
В этом проекте мы в основном разделяем объект на две части. Первый знает, как получить имя из английского имени. Второй знает, как кэшировать результаты этого расчета в памяти. И теперь я, как пользователь этих классов, решил, как именно их использовать. Я решу, нужно ли мне кэшировать или нет. Вот что такое объектная композиция.
Позвольте мне повторить, что единственным допустимым оператором внутри конструктора является присваивание. Если вам нужно добавить что-то еще, подумайте о рефакторинге — ваш класс определенно нуждается в редизайне.
| Ссылка: | Конструкторы должны быть свободны от кода от нашего партнера JCG Егора Бугаенко в блоге About Programming . | 
