Java 如何避免switch语句?
我正在努力学习java中的客户机/服务器 到现在为止,我已经掌握了基本知识。 这里介绍如何接受和服务众多客户Java 如何避免switch语句?,java,design-patterns,Java,Design Patterns,我正在努力学习java中的客户机/服务器 到现在为止,我已经掌握了基本知识。 这里介绍如何接受和服务众多客户 public class Server { ServerSocket serverSocket; public void startServer() throws IOException { serverSocket= new ServerSocket(2000); while (true){ Socket s=
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个选项,有没有办法避免切换语句(可能是设计模式)?
请记住,将来可能会出现新选项。这似乎是一个类似命令模式的示例 基本上,您需要一种将给定命令(在本例中为
字符串
)映射到执行适当行为的方法
最简单的方法是创建如下命令界面:
interface Command {
void execute();
}
然后,您可以创建一个映射
,它保存您的命令,并将每个传入的字符串
映射到某个帮助器类中,该类实现命令
,并在看到该命令时执行您希望执行的操作。然后你会使用它,比如:
commandMap.get(requestName).execute();
然而,这将需要在程序启动时进行一些动态设置,以使用命令字符串和命令对象构建映射。这是一种非常动态的设置映射的方法,这可能是一件好事,也可能是坏事,这取决于命令集更改的频率
如果您的命令更为静态,那么设置它的一种更为优雅的方法是使用enum
来定义各种命令及其行为。下面是一个相当简单和通用的示例,说明了如何做到这一点:
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");
}
};
}
}
正如注释中所指出的,在代码中的某个地方没有办法避免使用命令到辅助对象的映射。最重要的是不要将它放在您的业务逻辑中,这会使方法很难阅读,而是放在它自己的类中。因此,您有一个很大的ish
开关
块,它最终会start()
生成一些代码。推荐的方法是使用现有的接口,这样就可以是可运行的(包含一个void
方法,没有参数,就像您的start()
)
如果您将整个块重构为一个方法,它将有两个输入:Socket
和requestName
——因此它的签名如下所示:
Runnable getRequestCommand(Socket s, String request)
它将包含您的开关
块,并返回如下内容
if ("ClientRequestUploadFile".equals(request)) {
return new ClientRequestUploadFileHandler(s);
}
// etc
再次使用预先存在的接口,这是一个双功能(它需要请求字符串和套接字作为输入,并返回可运行的处理程序)
现在,您可以拆分每个案例
,并创建这样一个函数:
BiFunction<Socket, String, Runnable> upload = (s, req) -> {
return "ClientRequestUploadFile".equals(req)
? new ClientRequestUploadFileHandler(s)
: null;
}
现在,您的开关实际上也启动了创建的Runnable
,因此您也可以如果(requestHandler!=null){requestHandler.run();}
,而不将其返回给调用方。作为一行,这是handlers.stream().map(h->h.apply(s,request)).findFirst(Objects::nonNull).ifPresent(Runnable::run)
无论如何,现在您必须在原始类中创建所有的BiFunction
s,但是您可以将它们外部化,例如,到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;
}
}
}
}
现在您得到的代码比以前多得多,但处理本身已从接受套接字的类中移除,因此更好的单一责任;这允许您通过向enum
添加值来添加功能,而无需触摸原始代码
一个更简单的版本是在方法中创建functions集合,只需执行以下操作
Collection<BiFunction<Socket,String,Runnable>> createMappings() {
return Arrays.asList(
createMapping("ClientRequestUploadFile", ClientRequestUploadFileHandler::new),
createMapping("clientRequestCalculator", ClientRequestCalculatorHandler::new),
);
}
private BiFunction<Socket,String,Runnable> createmapping(String req, Function<Socket, Runnable> create) {
return (s, r) -> req.equals(r) ? create.apply(s) : null;
}
Collection createMappings(){
返回数组.asList(
createMapping(“ClientRequestUploadFile”,ClientRequestUploadFileHandler::new),
createMapping(“clientRequestCalculator”,ClientRequestCalculatorHandler::new),
);
}
专用双功能createmapping(字符串请求、函数创建){
return(s,r)->req.equals(r)→create.apply(s):null;
}
使用命令模式。使用字符串作为键从映射中查找适当的命令,并执行()它。我认为您无法避免的是,测试每种情况,独立于开关情况或for循环。你可以试着用一种聪明的方式来制作东西(例如,检查你的字符串是否以“ClientRequest”开头,以避免在字符串是其他内容时测试你的所有3个案例)。@azurefrog你能给我举个例子吗?:)创建一个包含请求名和命令创建的enum
,并迭代所有值,直到找到匹配项为止。@daniu您所说的命令创建是什么意思?很清楚,谢谢您,还有一个问题,在我的情况下,这样做可以吗?{@Override public void execute(){new ClientRequestUploadFileHandler.start();}当然可以,但是您需要修改命令接口,使其execute方法接受套接字作为参数,以便它在helper方法中可见。此外,请记住,枚举的名称需要与命令字符串匹配,因此类似于ClientRequestUploadFile{@Override public void execute(socket s){new ClientRequestUploadFileHandler(s).start();}}
你是一名艺术家,我现在在项目中工作,我是一名应届毕业生,我可以在一些项目中听取你的意见吗,而不是大型项目近50个班。(这个项目可以帮助你找到一份工作),我会做所有的事情,你只要给我一张便条plz:)
// ...
Socket s= serverSocket.accept();
DataInputStream clientStream= new DataInputStream(s.getInputStream());
String requestName=clientStream.readUTF();
RequestHandler.runOnRequestMatch(s, requestName);
Collection<BiFunction<Socket,String,Runnable>> createMappings() {
return Arrays.asList(
createMapping("ClientRequestUploadFile", ClientRequestUploadFileHandler::new),
createMapping("clientRequestCalculator", ClientRequestCalculatorHandler::new),
);
}
private BiFunction<Socket,String,Runnable> createmapping(String req, Function<Socket, Runnable> create) {
return (s, r) -> req.equals(r) ? create.apply(s) : null;
}