Сократить, если в них содержатся заявления с. - PullRequest
0 голосов
/ 08 мая 2018

Я хотел спросить, был ли другой подход к ситуации с котельной плитой if-else, с которой я столкнулся.

У каждого if и else if есть сравнение string.contains. Я использую Java 8.

Есть ли способ сократить это, используя что-то вроде x ? y : z? Или, может быть, переключатель?

if (s.contains("ServerAdmin ")) {
      data.setServerAdmin(s) // This happens everywhere
} else if (s.contains("DocumentRoot")) {
      data.setDocumentRoot(s) // This happens everywhere
} else if (s.contains("ServerName")) {
      data.setServerName(s) // This happens everywhere
} else if (s.contains("ErrorLog")) {
      data.setErrorLog(s) // This happens everywhere
} else if (s.contains("CustomLog")) {
      data.setCustomLog(s) // This happens everywhere
} else if (s.contains("<Directory")) {
      data.setDirectory(s) // This happens everywhere
}

Есть ли лучший способ сделать это?

~ Заранее благодарим за любую помощь.

Ответы [ 6 ]

0 голосов
/ 09 мая 2018

Вы могли бы сделать это еще больше java-8-ish:

enum Operation implements Predicate<String> {
    SERVER_ADMIN {
        @Override
        public boolean test(String s) {
            return s.contains("ServerAdmin");
        }

        @Override
        public void accept(Data data, String input) {
            data.setServerAdmin(input);
        }
    };

    public abstract void accept(Data data, String input);
}


EnumSet.allOf(Operation.class)
            .stream()
            .filter(x -> x.test(s))
            .findAny()
            .ifPresent(x -> x.accept(data, s));
0 голосов
/ 08 мая 2018

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

private static final Map<String, BiConsumer<YourObj, String>> configActions;

static {
    Map<String, BiConsumer<YourObj, String>> tmp = new HashMap<>();
    tmp.put("ServerAdmin", YourObj::setServerAdmin);
    tmp.put("DocumentRoot", YourObj::setDocumentRoot);
    // ...
    configActions = Collections.unmodifiableMap(tmp);
}

public static YourObj load(List<String> directives) {
    YourObj config = new YourObj();
    directives.forEach(dir -> set(config, configActions, dir));
    return config;
}

static <T> void set(T obj, Map<String, BiConsumer<T, String>> setters, String value) {
    setters.entrySet().stream()
        .filter(e -> value.contains(e.getKey()))
        .findFirst()
        .ifPresent(e -> e.getValue().accept(obj, value));
}

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

В качестве примера рассмотрим, что произойдет, если у вас есть токены "LogFile" и "LogFileEncoding". Оба contain() "LogFile", но только один является правильным соответствием.

Если вы используете Java 9 или новее, вы можете инициализировать Map следующим образом:

private static final Map<String, BiConsumer<YourObj, String>> configActions = Map.of(
  "ServerAdmin", YourObj::setServerAdmin,
  "DocumentRoot", YourObj::setDocumentRoot,
  // ... 
);

Если у вас более десяти записей, вы можете использовать entry() builder:

private static final Map<String, BiConsumer<YourObj, String>> configActions = Map.ofEntries(
  Map.entry("ServerAdmin", YourObj::setServerAdmin),
  Map.entry("DocumentRoot", YourObj::setDocumentRoot),
  // ...
);

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

0 голосов
/ 08 мая 2018

Вероятно, чтобы показать, как Java-8 упрощает Java-кодирование, вот мой подход к решению этой проблемы на основе шаблона Command OO:

public interface Command {
    public void execute(Data data, String containerString);
}

public abstract class AbstractCommand implements Command {

  protected  String containeeString;
  protected AbstractBoundAction(String containeeString) {
    this.containeeString = containeeString;
  }

  protected abstract void action(Data data, String containerString);

  public void execute(Data data, String containerString) {
    if(containerString.contains(containeeString)) {
       action(data,containerString);
    }
  }
}
public class ServerAdminCommand extends AbstractCommand {
  public ServerAdminCommand() {
     super("ServerAdmin ");
  }

  protected void action(Data data, String containerString) {
     data.setServerAdmin(containerString);
  } 
}
public class DocumentRootCommand extends AbstractCommand {
  public DocumentRootCommand() {
     super("DocumentRoot");
  }

  protected void action(Data data, String containerString) {
     data.setDocumentRoot(containerString);
  } 
}
// And so on for the remaining data method invocation per contained strings

И использование всего этого стандартного кода:

String containerString = ....;
Data data = .....;
....
....
List<Command> commands = Arrays.asList(new ServerAdminCommand() , 
                         new DocumentRootCommand() /* etc...... */ );
commands.forEach(com -> com.execute(data,containerString));
0 голосов
/ 08 мая 2018

Я бы предложил добавить поле к тому, что s, и создать enum, перечисляющий возможные случаи: содержит ServerAdmin, содержит DocumentRoot и т. Д. Вызовите это enum поле s messageType для наши цели:

switch (s.messageType){
    case SERVER_ADMIN: data.setServerAdmin(s); break;
    case DOCUMENT_ROOT: data.setDocumentRoot(s); break;
    ...
}

Это короче, но поддерживает удобочитаемость и интуитивность.

ОБНОВЛЕНИЕ: только что увидел, что s это String. Если не полезно сделать s объектом вашего собственного дизайна по другим причинам, вероятно, не стоит оборачивать его, просто добавив enum (я бы даже не расширил String), поэтому я бы рекомендовал придерживаться if с.

0 голосов
/ 08 мая 2018

Это немного бестолково. Но попробуйте, если вы чувствуете себя лучше:

public class Data {
   private static Map<String, BiConsumer<Data, String>> SETTER_MAP = new LinkedHashMap<>(); // Use LinkedHashMap if you want to check the contains in order
   static {
       SETTER_MAP.put("ServerAdmin ", Data::setServerAdmin);
       ... your list goes on...
   }

   public void String setData(String s) {
     SETTER_MAP.entrySet().stream().filter(e -> s.contains(e.getKey())
                    .findFirst().ifPresent(e -> e.getValue().accept(this, s);
   }

}
0 голосов
/ 08 мая 2018

8-е решение Java, о котором я могу подумать, это

Map<String, Consumer<String>> map = new LinkedHashMap<>();
map.put("ServerAdmin", data::setServerAdmin);
map.put("DocumentRoot", data::setDocumentRoot);
// ...
map.entrySet().stream()
    .filter(entry -> s.contains(entry.getKey()))
    .findFirst()
    .ifPresent(entry -> entry.getValue().accept(s));

, который использует LinkedHashMap, чтобы сохранить порядок сравнения без изменений (в случае необходимости), затем просто ищет первый contains и применяет соответствующую функцию

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

private static final Map<String, BiConsumer<Data, String>> MAP = new LinkedHashMap<>();
static {
    MAP.put("ServerAdmin", Data::setServerAdmin);
    MAP.put("DocumentRoot", Data::setDocumentRoot);
    // ...
}
public void foo(Data data, String s) {
    MAP.entrySet().stream()
        .filter(entry -> s.contains(entry.getKey()))
        .findFirst()
        .ifPresent(entry -> entry.getValue().accept(data, s));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...