Конструктор против инъекции сеттера - PullRequest
3 голосов
/ 21 апреля 2010

В настоящее время я разрабатываю API, в котором я хочу разрешить настройку различными способами. Один метод - через схему конфигурации XML, а другой - через API, с которым я бы хотел хорошо поиграть в Spring.

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

Кажется, что многие предпочитают javabean-тип PoJo с конструкторами по умолчанию с нулевым параметром, а затем с установкой. Проблема, которую я пытаюсь решить, состоит в том, что некоторые реализации методов установки зависят от других методов установки, вызываемых перед ними последовательно.

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

Единственное решение, которое я могу придумать, - это забыть о 'beans' и применить необходимые параметры с помощью инжектора конструктора.

Примером этого является установка идентификатора компонента по умолчанию на основе идентификатора родительских компонентов.

Мой интерфейс

public interface IMyIdentityInterface {
    public String getId();
    /* A null value should create a unique meaningful default */
    public void setId(String id);
    public IMyIdentityInterface getParent();
    public void setParent(IMyIdentityInterface parent);
}

Базовая реализация интерфейса:

public abstract class MyIdentityBaseClass implements IMyIdentityInterface {
   private String _id;
   private IMyIdentityInterface _parent;

   public MyIdentityBaseClass () {}

   @Override
   public String getId() {
      return _id;
   }

   /**
    * If the id is null, then use the id of the parent component
    * appended with a lower-cased simple name of the current impl
    * class along with a counter suffix to enforce uniqueness
    */
   @Override
   public void setId(String id) {
      if (id == null) {
          IMyIdentityInterface parent = getParent();
          if (parent == null) {
              // this may be the top level component or it may be that
              // the user called setId() before setParent(..)
          } else {
              _id = Helpers.makeIdFromParent(parent,getClass());
          }
      } else {
          _id = id;
      }
   }

   @Override
   public IMyIdentityInterface getParent() {
      return _parent;
   }

   @Override
   public void setParent(IMyIdentityInterface parent) {
      _parent = parent;
   }

}

У каждого компонента в платформе будет родитель, кроме компонента верхнего уровня. При использовании метода ввода метода установки метод настройки будет отличаться в зависимости от порядка вызова метода установки.

В этом случае, согласитесь ли вы, что конструктор, принимающий ссылку на родителя, лучше и полностью удаляющий метод родительского сеттера из интерфейса? Считается ли это плохой практикой, если я хочу настроить эти компоненты с помощью контейнера IoC?

Chris

Ответы [ 4 ]

2 голосов
/ 21 апреля 2010

Я на самом деле не думаю, что проблема заключается в инъекции сеттера или конструктора.Проблема в том, что вы используете null для обозначения слишком многих вещей ...

Что касается: /* A null value should create a unique meaningful default */

Использование его в качестве "магического" значения в методе setId()это тоже что-то вроде плохого запаха кода.Если вы никогда не вызовете setId(), то getId() вернет null, но если вы позвоните setId(null), тогда getId() вернет какое-то сгенерированное значение?

Что-то подобное может показаться более логичным:

public abstract class MyIdentityBaseClass implements IMyIdentityInterface {
    private String _id;
    private IMyIdentityInterface _parent;

    public MyIdentityBaseClass () {}

    @Override
    public String getId() {
        return _id;
    }

    @Override
    public void setId(String id) {
        _id = id;
    }

    @Override
    public IMyIdentityInterface getParent() {
        return _parent;
    }

    @Override
    public void setParent(IMyIdentityInterface parent) {
        _parent = parent;
        if (_id == null) {
            // if id isn't already set, set it to the generated id.
            _id = Helpers.makeIdFromParent(parent, getClass());
        }
    }

}

Если вашей модели действительно требуется, чтобы родитель не стеснялся использовать инъекцию конструктора, это то, для чего она нужна.Но поймите, что вам все равно придется предоставить конструктор по умолчанию для элементов верхнего уровня ... и что мешает кому-то использовать конструктор по умолчанию для элемента, который не является верхним уровнем, а затем вызвать setParent() для него позже.Я бы посмотрел на создание некоторой Контейнерной / Компонентной архитектуры и сделал бы специальный класс (или классы) для элементов верхнего уровня.(Возможно, у вас это уже есть, поскольку вы дали только один примерный класс и при этом абстрактный).

Кстати - также соглашения IInterface и _name не являются соглашениями об именах Java.Если это открытый API, который должны использовать другие люди, ожидайте каких-либо жалоб.

2 голосов
/ 21 апреля 2010

Не плохо использовать контейнер IOC для этой цели. Выбор использования внедрения contrucxtor или setter обусловлен тем фактом, что любые экземпляры этих классов должны когда-либо создаваться без родителя. Так как в вашем случае всегда нужно устанавливать родительский элемент (кроме верхнего узла), внедрение инжектора имеет большой смысл

1 голос
/ 21 апреля 2010

Похоже, проблема в ID по умолчанию. Это сделало бы вашу жизнь намного проще, если бин сам не определился со своим идентификатором. Я бы сказал, что за идентификатор отвечает клиентский код, который создает или использует компонент.

Конечно, я не знаю точного контекста, но я предполагаю, что идентификатор должен быть несколько уникальным (в собственном контексте бина). Ваше текущее решение указывает, что идентификатор установлен в двух разных местах: либо в методе setId(), либо в коде клиента.

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

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

1 голос
/ 21 апреля 2010

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

Специфическое решение Spring для этого состоит в том, чтобы объявить компонент как реализующий InitializingBean, а затем реализовать необходимую проверку и заполнение значений по умолчанию в методе afterPropertiesSet() компонента. Вы можете даже подключить это так, чтобы у программистов был выбор использования конструкторов с аргументами или установщиками, и (возможно) иметь конструкторы с полным аргументом вызовом afterPropertiesSet().

Единственные ошибки:

  • ваша кодовая база становится зависимой от Spring благодаря реализации InitializingBean и

  • если разработчик настраивает компоненты вручную, используя установщики, которые он должен запомнить, чтобы вызвать afterPropertiesSet(), когда они будут готовы.

EDIT

Метод afterPropertiesSet() также дает удобное решение для вашего примера с вычислением идентификатора. Проблема с тем, как вы сейчас пишете, заключается в том, что оно неявно ограничивает вас для построения дерева сверху вниз (корнем). Например, предположим, что у вас есть многоуровневое дерево, и вы устанавливаете родительский атрибут конечного узла до , вы устанавливаете родительский атрибут родительского узла. Это вызовет алгоритм установки id в конечном узле, который будет ошибочно думать, что родительский узел является корневым узлом.

В Spring вы можете организовать установку атрибутов parent в реализации соответствующих методов setChild / setChildren родительских узлов. Настройка атрибутов id будет затем выполнена методом afterPropertiesSet(). Поскольку это вызывается после , когда все явные и автоматические настройки свойств были применены, отношения родитель / потомок для дерева стабилизируются, и поэтому вычисления id будут правильными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...