как избежать переключения оператора? - PullRequest
2 голосов
/ 22 января 2020

Я пытаюсь изучить клиент / сервер за java до сих пор, я получил основы. здесь, как принять и обслуживать много клиентов

public class Server {
    ServerSocket serverSocket;

    public void startServer() throws IOException {
        serverSocket= new ServerSocket(2000);
        while (true){
            Socket s= serverSocket.accept();
            new ClientRequestUploadFile(s).start(); //here is the first option.
        }
    }
}

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

public void startServer() throws IOException {
        serverSocket= new ServerSocket(2000);
        while (true){
            Socket s= serverSocket.accept();
            DataInputStream clientStream= new DataInputStream(s.getInputStream());
            String requestName=clientStream.readUTF();
            switch (requestName){
                case "ClientRequestUploadFile": new ClientRequestUploadFileHandler(s).start();break;
                case "clientRequestCalculator": new clientRequestCalculatorHandler(s).start();break;
                case "clientRequestDownloadFile": new clientRequestDownloadFileHandler(s).start();break;
            }
        }
    }

если существует 100 вариантов, есть ли способ избежать оператора switch (возможно, используются шаблоны проектирования)? имейте в виду, что может произойти новое вариант в будущем.

Ответы [ 2 ]

2 голосов
/ 23 января 2020

Это похоже на пример, где было бы уместно что-то вроде шаблона Command.

По сути, вам нужен способ сопоставить данную команду (в данном случае String) с выполнением соответствующей поведение.

Самый простой способ сделать это - создать командный интерфейс следующим образом:

interface Command {
    void execute();
}

Затем вы можете создать Map<String, Command>, который будет содержать ваши команды и отображать каждую входящую String в некоторый вспомогательный класс, который реализует Command и делает то, что вам нужно, когда вы видите эту команду. Тогда вы бы использовали что-то вроде:

commandMap.get(requestName).execute();

Это, однако, потребует небольшой настройки на лету при запуске программы, чтобы построить карту с командными строками и объектами Command. Это очень динамичный c способ настройки отображения, который может быть хорошим или плохим, в зависимости от того, как часто меняется ваш набор команд.

Если ваши команды более стати c, более элегантный способ настроить это - использовать enum для определения различных команд и их поведения. Вот довольно простой и обобщенный c пример того, как вы могли бы сделать это:

public class CommandPatternExample {
    public static void main(String[] args) throws Exception {
        CommandEnum.valueOf("A").execute(); // run command "A"
        CommandEnum.valueOf("B").execute(); // run command "B"
        CommandEnum.valueOf("C").execute(); // IllegalArgumentException
    }

    interface Command {
        void execute();
    }

    enum CommandEnum implements Command {
        A {
            @Override
            public void execute() {
                System.out.println("Running command A");
            }
        },
        B {
            @Override
            public void execute() {
                System.out.println("Running command B");
            }
        };
    }
}

Как указано в комментариях, нет способа обойти команду где-то для сопоставления объектов помощника в вашем коде. Главное, чтобы его не было в вашей бизнес-логике c, что затрудняет чтение метода, а где-то в его собственном классе.

0 голосов
/ 23 января 2020

Итак, у вас есть блок big-i sh switch, который в итоге start() s представляет собой некоторый фрагмент кода. Рекомендованным способом является использование существующих интерфейсов, так что это будет Runnable (содержащий метод void без параметров, как ваш start()).

Если вы реорганизуете весь блок в метод, он будет иметь два входа: Socket и requestName - поэтому его подпись будет выглядеть так:

Runnable getRequestCommand(Socket s, String request)

, который будет содержать ваш блок switch и будет возвращать что-то вроде

if ("ClientRequestUploadFile".equals(request)) {
    return new ClientRequestUploadFileHandler(s);
} 
// etc

Снова используя ранее существующие интерфейсы, это BiFunction<Socket, String, Runnable> (требует ввода строки запроса и сокета и возвращает работоспособный обработчик).

Теперь вы можете разделить каждого отдельного пользователя case и создайте такую ​​функцию:

BiFunction<Socket, String, Runnable> upload = (s, req) -> {
    return "ClientRequestUploadFile".equals(req) 
        ? new ClientRequestUploadFileHandler(s)
        : null;
}

Если вы сделаете то же самое для других случаев и сохраните их в Collection<BiFunction<Socket, String, Runnable>> (давайте назовем это handlers), ваш getRequestCommand() метод выглядит выше как

Runnable requestHandler = null;
for (BiFunction<Socket, String, Runnable> handler : handlers) {
    requestHandler = handler.apply(s, request);
    if (requestHandler != null) { break; } // found the match
}
return requestHandler;

Теперь ваш switch фактически также запускает созданный Runnable, так что вы также можете if (requestHandler != null) { requestHandler.run(); } здесь и не возвращать его вызывающей стороне. Как одна строка, это handlers.stream().map(h -> h.apply(s, request)).findFirst(Objects::nonNull).ifPresent(Runnable::run).

В любом случае, теперь вы застряли в создании всех BiFunction<> в исходном классе, но вы можете их экстернализовать, например. на enum.

enum RequestHandler {
    FILE_UPLOAD("ClientRequestUploadFile", ClientRequestUploadFileHandler::new),
    CALCULATE("clientRequestCalculator", ClientRequestCalculatorHandler::new),
    // ...
    ;

    // the String that needs to match to execute this handler
    private String request;
    // creates the runnable if the request string matches
    private Function<Socket, Runnable> createRunnable;

    private RequestHandler(String r, Function<Socket, Runnable> f) {
        request = r; createRunnable = f;
    }
    // and this is your handler method
    static void runOnRequestMatch(Socket socket, String request) {
        for (RequestHandler handler : values()) {
            Runnable requestHandler = request.equals(handler.request) 
                                          ? handler.createRunnable.apply(socket)
                                          : null;
            if (requestHandler != null) {
                requestHandler.run();
                break;
            }
        }
    }
}

И в вашем клиентском коде вы получите

// ...
Socket s= serverSocket.accept();
DataInputStream clientStream= new DataInputStream(s.getInputStream());
String requestName=clientStream.readUTF();
RequestHandler.runOnRequestMatch(s, requestName);

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

Более простой версией было бы создание коллекции функций в методе простым выполнением

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