我正在开发一个小型的Web库,并且想知道我应该反复调用GET,POST,PUT等HTTP处理程序方法。
首先,在基类中给出if else ...
块调用方法的变量,它们有一个默认实现,向客户端返回错误。由于对不受支持的方法的请求需要带有允许方法的标头,我需要反思地查找哪些方法被覆盖(顺便说一下,Servlet API就是这样)。
public abstract class Resource {
public Response handle(HttpServletRequest request) {
String action = request.getMethod();
if(action.equals("GET"))
return get(request);
else if(action.equals("POST"))
return post(request);
...
}
protected Response get(HttpServletRequest request) {
return new Response(METHOD_NOT_ALLOWED);
}
protected Response post(HttpServletRequest request) {
return new Response(METHOD_NOT_ALLOWED);
}
}
此解决方案的缺点是灵活性降低,因为可用方法在基类中得到修复,直到handle
方法在子类中重新实现。
另一种变体是根据签名反复查找HTTP处理程序方法(取HttpServletRequest
并返回Response
)。这些方法将存储在Map中,并根据地图中的键进行反射调用。
public abstract class Resource {
private Map<String, Method> handlers;
public Resource() {
handlers = findHttpHandlerMethodsReflectivly();
}
public Response handle(HttpServletRequest request) {
String action = request.getMethod();
Method handler = handlers.get(action);
return (Response)handler.invoke(this, request);
}
}
此解决方案的优点是简单的实现和灵活性,但由于在地图中搜索和反射方法调用,缺点可能是更多的运行时开销。并且类的接口有点“软”(或动态),编译器没有机会检查它。但我不确定这是不是一个缺点,因为没有其他类应该依赖HTTP处理程序方法,它们是外部Web界面和Java系统的边界。
第三种选择和最干净的OOP将是“polygenelubricants”推荐的策略模式。它看起来像这样:
class MyResource extends Resource {
register("GET",
new RequestHandler{
@Override Response handle(HttpServletRequest request) {
new Response(OK);
}
}
);
}
这是干净的OOP,但代码实际上是丑陋和冗长的。虽然Scala的工具支持仍然很差,但我更喜欢Scala和闭包。将此与具有继承和固定方法的解决方案进行比较:
class MyResource extends Resource {
@Override Response get(HttpServletRequest request) {
return new Resonse(OK);
}
}
您更喜欢什么?为什么?其他想法?
由于固定的HTTP方法集,我已经了解到这里不需要反射。策略模式的方法很简洁,但它看起来很冗长。所以我决定采用固定方法和继承。
答案 0 :(得分:9)
在这种情况下不应该使用反射,特别是因为没有必要开始使用(参见 Effective Java 2nd Edition,Item 53:Prefer接口到反射)。
您应该定义Map<String, Method> handlers
类型,而不是使用java.lang.reflect.Method
并拥有interface RequestHandler
,而应使用Map<String, RequestHandler> handlers
。
它看起来像这样:
interface RequestHandler {
Response handle(HttpServletRequest req);
}
然后,不是反复搜索处理程序,而是使用显式put
填充映射(或者使用配置文件等)。然后,您可以更加干净地调用Method.invoke
而不是反思RequestHandler.handle
。
enum
键选项如果您只有几个不同的类型的请求方法而没有计划使其可扩展,那么您可以拥有enum RequestMethod { GET, POST; }
。
这允许您声明Map<RequestMethod, RequestHandler> handlers;
。请注意,enum
有一个valueOf(String)
方法,您可以使用该方法从名称中获取常量。
interface
vs abstract class
在这里,我将再次从 Effective Java 2nd,第18项:首选接口到抽象类推迟Josh Bloch的判断:
总而言之,
interface
通常是定义允许多个实现的类型的最佳方式。这一规则的一个例外是,易于进化被认为比灵活性和权力更重要。在这种情况下,您应该使用abstract class
来定义类型,但前提是您理解并且可以接受这些限制。
本书已经更详细地介绍了您正在努力解决的问题。在这种特殊情况下,可能是使用abstract class
(即“固定方法”方法)的情况,因为那里有少量和固定类型的请求方法。
答案 1 :(得分:7)
我不想在这里使用反射,因为可能的HTTP方法是众所周知的,并且不会很快改变。所以你并没有真正获得额外的复杂性和缺乏运行时检查。您可以使用地图代替if...elseif
,以使您的代码更清晰,更易于扩展。
如果使用类似“NOSUCHMETHOD”的方法名称调用,您的反射代码会崩溃。
答案 2 :(得分:1)
良好的OO设计应该支持可扩展性,但是在需要时应该支持。以下是关于我与您分享的可扩展性(对我而言)的一些想法。
第1级:无可扩展性
事先知道一切,你可以用最简单易懂的方式对其进行编码。
第2级:使用 hooks封装可扩展性
组件封装良好,但必须能够调整其行为或进行配置。该组件提供了一个钩子,您可以在其中插入以改变其行为。钩子可以采用许多不同的形式。例如,组件提供了一种将策略挂钩到其中的方法,或者组件在内部使用了<verb, actionHandler>
的映射,但是映射是由另一个组件在外部填充的,等等。
第3级:运行时可扩展性
事情不是先进的,代码是动态加载的,系统是“可编写脚本”的,在DSL中表示自定义规则等。这种程度的灵活性需要使用反射,因为需要在一个代码中调用代码片段。动态的方式。
在您的情况下,HTTP谓词列表是固定的,并且是资源的内部。然后我会选择最简单和最常用的方法1.其他任何东西看起来都像过度工程,我不介意一个小if-else
列表,当它被证明是合理的。