зависимость от состояния и безопасность потоков - PullRequest
0 голосов
/ 16 декабря 2011

У меня есть класс действий Struts 1 (действия представляют собой отдельные элементы по конструкции в стойках 1), которым необходимо собрать некоторые данные и затем объединить их все в один ответ.Я хотел бы извлечь всю логику генерации ответов в отдельный класс с именем

ResponseBuilder

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

class JsonResponseBuilder implements ResponseBuilder {
    public void addElement(String key, Object value) {
        ...
    }

    public String buildResponse() {
        // build response from data collected
    }
}

При такой реализации я не могу сделать это из-за проблем с безопасностью потоков.

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

ResponseBuilderFactory

используется как зависимость и называется так:

ResponseBuilder builder = factory.getBuilder();
builder.addElement(...);
...
String response = builder.build();

нормально с точки зрения дизайна и тестируемости?Если все в порядке.Как написать тестовый код для этого?Издеваться над заводом?Макет строителя?

Ответы [ 4 ]

2 голосов
/ 16 декабря 2011

Просто создайте экземпляр ResponseBuilder в вашем методе службы действий.Таким образом, это будет локальная переменная (вместо одноэлементного члена класса), и у каждого потока будет отдельный свежий экземпляр ResponseBuilder.

Вы можете выполнить модульное тестирование ResponseBuilder отдельно, а также можете нормально протестировать свое действие, как и любое другое действие (при необходимости высмеивая ResponseBuilder).

Использование фабричного шаблона для построения ResponseBuilder - это другой вопрос, не связанный с вашей проблемой, imo.Если создание экземпляров ResponseBuilders стоит дорого и их можно использовать повторно, то вы можете использовать пул ResponseBuilders.

2 голосов
/ 16 декабря 2011

Фабрика будет работать. По сути, вы делаете инъекцию зависимостей на фабрике. Когда вы создаете экземпляр или инициализируете свое действие, вы устанавливаете фабрику:

public void setResponseFactory(ResponseBuilderFactory factory) {
    this.responseFactory = factory;
}

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

Что касается тестирования, становится легко заменить фабрику фиктивной фабрикой, которая возвращает фиктивный ResponseBuilder. Для этого существует множество библиотек, например Mockito или JMock . Все они хорошо работают с JUnit и TestNG.

Edit:

Вам потребуется интерфейс ResponseBuilderFactory, например:

public interface ResponseBuilderFactory {
    public ResponseBuilder getResponseBuilder();
}

Когда вы проводите тестирование, просто создайте класс, который возвращает макет вашего ResponseBuilder:

@Test
public void testMyAction() throws Exception {
    ResponseBuilderFactory mockFactory = new ResponseBuilderFactory() {
        public ResponseBuilder getResponseBuilder() {
            ResponseBuilder builder = context.mock(ResponseBuilder.class);
            // set up mock behaviour
            return builder;
        }
    }
}

То есть, вы не вводите фиктивную фабрику, а просто фабрику, которая возвращает ложную информацию.

Также см. Инъекция зависимости от заводской модели .

Редактировать 2:

Если вам удастся каким-либо образом перекодировать ваш JsonResponseBuilder, чтобы он не поддерживал состояние, тогда вы могли бы потенциально избежать всей заводской путаницы и просто использовать свой оригинальный подход. Объекты, которые не поддерживают состояние, по своей сути поточно-ориентированы.

1 голос
/ 16 декабря 2011

Вы можете сохранить построитель ответов в сеансе. Сделайте так, чтобы он отображался как «свойство» вашего класса Action, имея для него getter / setter, но не определяйте фактическое поле. Вместо этого сделайте что-то вроде:

protected ResponseBuilder getResponseBuilder() {
    ResponseBuilder builder = (ResponseBuilder) session.getAttribute("ATTR_RESPONSE_BUILDER");
    if(builder == null) {
        builder = new ResponseBuilder();
        session.setAttribute("ATTR_RESPONSE_BUILDER", builder);
    }

    return builder;
}

protected void setResponseBuilder(ResponseBuilder builder) {
    session.setAttribute("ATTR_RESPONSE_BUILDER", builder);
}

Вам нужно будет очистить компоновщик между вызовами класса Action.

1 голос
/ 16 декабря 2011

В ту минуту, когда вы делаете свой ResponseBuilder переменной-членом в действии, он распределяется по всем запросам. Это означает, что вам придется синхронизировать его, чтобы гарантировать, что его изменяемое состояние, если оно есть, является потокобезопасным.

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

...