Альтернативы обновлениям списка по ссылкам в Java

#java #design-patterns

#java #шаблоны проектирования

Вопрос:

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

  1. Обновление напрямую с помощью pass-by-reference
  2. Использование обратных вызовов, чтобы позволить вызывающей стороне обрабатывать то, как они хотят справиться с обновлением

Примеры

  1. Использование прямого обновления
 public class SomeClass {
  public void process(List<String> inputList, List<StringContainer> errorContainerList, List<String> validList) {
    for (String str : inputList) {
      if (someCondition) {
        validList.add(str);
      } else {
        errorContainerList.add(new StringContainer(str, new SomeError()));
      }
    }
  }
}

void someCaller(List<String> inputList) {
    List<String> validList = new ArrayList<>();
    List<StringContainer> errorContainerList = new ArrayList<>();

    SomeClass obj = new SomeClass();
    obj.process(inputList, errorContainerList, validList);

    // Similarly,
    validList.remove(...);
    errorContainerList.add(...);
}
 
  1. Использование обратных вызовов
 public interface StringErrorHandler {
    void handle(String str, SomeError error);
}

public interface ValidInputHandler {
    void handle(String str);
}

public class SomeClass {
  public void process(List<String> inputList, StringErrorHandler errorHandler, ValidInputHandler validInputHandler) {
    for (String str : inputList) {
      if (someCondition) {
        validInputHandler.handle(str);
      } else {
        errorHandler.handle(str, new SomeError());
      }
    }
  }
}

void someCaller(List<String> inputList) {
    List<String> validList = new ArrayList<>();
    List<StringContainer> errorContainerList = new ArrayList<>();

    SomeClass obj = new SomeClass();
    obj.process(inputList, new StringErrorHandler() {
        
        @Override
        public void handle(String str, SomeError error) {
            errorContainerList.add(new StringContainer(str, error));
        }
    }, new ValidInputHandler() {
        
        @Override
        public void handle(String str) {
            validList.add(str);
        }
    });

    // Similarly,
    validList.remove(...);
    errorContainerList.add(...);
}
 

Комментарии:

1. Что вы подразумеваете под «хорошим» шаблоном? Что делает шаблон «хорошим»?

2. В чем проблема с выполнением любого из ваших способов? Есть ли исключение?

3. @Sweeper Поскольку подобный случай встречается очень часто, мне интересно, существуют ли какие-либо ранее существовавшие шаблоны, о которых я не знаю.

4. @NomadMaker Здесь проблема с 1-м подходом заключается в том, что если количество входных и выходных данных увеличивается, то увеличивается количество параметров для метода, что может усложнить рефакторинг кода. Для 2-го подхода мне интересно, не приведет ли это к путанице в отладке.

Ответ №1:

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

     public List<String> process(List<String> inputList) {
    List<String> validList = new ArrayList<>();
    for (String str : inputList) {
        if (someCondition) {
            validList.add(str);
        }
    }
    return validList;
}
 

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

 public ProcessResult process(List<String> inputList) {
    List<StringContainer> errorContainerList = new ArrayList<>();
    List<String> validList = new ArrayList<>();
    for (String str : inputList) {
        if (someCondition) {
            validList.add(str);
        } else {
            errorContainerList.add(new StringContainer(str, new SomeError()));
        }
    }
    return new ProcessResult(validList, errorContainerList)
}

public static class ProcessResult{
    public final List<String> validList;
    public final List<StringContainer> errorContainerList;

    public ProcessResult(List<String> validList, List<StringContainer> errorContainerList) {
        this.validList = validList;
        this.errorContainerList = errorContainerList;
    }
}
   void someCaller(List<String> inputList) {
    List<String> validList = new ArrayList<>();
    List<StringContainer> errorContainerList = new ArrayList<>();

    SomeClass obj = new SomeClass();
    ProcessResult pr=obj.process(inputList);
    validList.addAll(pr.validList);
    errorContainerList.addAll(pr.errorContainerList);
    

}
 

Это лучше по нескольким причинам, но наиболее важным является то, что у вашего метода нет побочных эффектов и что результат метода возвращается явно, что мы и должны делать в java

Комментарии:

1. В случае частых вызовов это решение создаст много временных переменных (ProcesResult). Как вы думаете, это вызовет какие-либо проблемы с GC?

2. Java действительно хороша в работе с избыточными объектами. В 99,999% случаев вам не нужно думать об этом, пока у вас не возникнут реальные проблемы с производительностью (проверьте преждевременную оптимизацию).