Java API设计变更最佳实践
我遇到了需要更改API的情况,需要知道我拥有的最佳选项: 最初,我的API声明:Java API设计变更最佳实践,java,api,design-patterns,Java,Api,Design Patterns,我遇到了需要更改API的情况,需要知道我拥有的最佳选项: 最初,我的API声明: DFS dfs = new DFS(Graph); dfs.runDFS(source); 现在,我在DFS代码中添加了另一个函数,用于返回从输入顶点到源顶点的DFS路径。 因此,我的新clean API如下所示: DFS dfs = new DFS(Graph, source); // BREAKS THE CONTRACT. dfs.runDFS(); // BREA
DFS dfs = new DFS(Graph);
dfs.runDFS(source);
现在,我在DFS代码中添加了另一个函数,用于返回从输入顶点到源顶点的DFS路径。
因此,我的新clean API如下所示:
DFS dfs = new DFS(Graph, source); // BREAKS THE CONTRACT.
dfs.runDFS(); // BREAKS THE CONTRACT.
dfs.getPathFromSource(vertex);
如果我维护向后兼容性(维护2个构造函数和2个runDFS函数),我的客户端将遇到另一个问题:
DFS dfs = new DFS(Graph);
dfs.runDFS();
dfs.getPathFromSource(vertex);
虽然向后兼容,但有一个bug,因为任何地方都没有提到源代码(无论是在构造函数中还是在函数调用中)
请在此场景中建议最佳API实践。感谢您可以使用设计模式,这样您就可以将图形的遍历与节点的处理分离开来
public interface NodeVisitor {
public void visit(GraphNode node);
}
您需要一个执行路径计算的实现:
public class PathCalculatingVisitor implements NodeVisitor {
public PathCalculatingVisitor(GraphNode target) {
// source will be the start of the traversal
}
public void visit(GraphNode node) {
// implement path calculation logic
}
}
DFS类应接受以下实例:
// overloads runDFS(GraphNode source)
public void runDFS(GraphNode source, NodeVisitor visitor) {
// do traversing, then
visitor.visit(node);
}
最后,用法:
PathCalculatingVisitor pathCalculatingVisitor = new PathCalculatingVisitor(target);
DFS dfs = new DFS(graph);
dfs.runDFS(source, pathCalculatingVisitor);
pathCalculatingVisitor.getPath();
我认为这种设计更适合未来,如果需要的话,您可以添加更多的访问者,而无需接触DFS类,因此这符合要求。您可以保持向后兼容性,但要以延迟产生错误为代价: 在v1中,调用
runDFS()
将是编译时错误,但在具有向后兼容性的v2中,只有在运行时调用runDFS()
并确定构造函数未提供源代码时,才会出现错误
对于新方法,如果构造函数未提供源代码,则会失败
如果构造函数中没有source
,则只有对runDFS(source)
的调用有效
创意2
您还可以通过子类化创建一个全新的API:
public class DFS2 extends DFS {
private Object mySource;
public DFS2(Graph g) {
super(g);
}
public DFS2(Graph g, Object source) {
mySource = source;
}
public void runDFS() {
super.runDFS(mySource);
}
public Path getPathFromSource(Vertex vertex) {
.... code goes here ...
}
}
DFS2获得了新的API。旧代码仍然使用旧代码,并且您共享大部分代码本身
DFS2 dfs = new DFS2(Graph, source);
dfs.runDFS();
dfs.getPathFromSource(vertex);
这取决于DFS
的功能,以及有无源操作是否有意义。一般来说,只有在没有其他选择或者已经发展到需要重新设计的程度时,才应该中断API
在这种情况下,更改似乎不是主要的(至少这是我从您的描述中得到的),因此您可以尝试同时满足新旧客户机代码库(请参见下面的示例)
如果您认为合适,您甚至可以将旧构造函数和旧版本的runDFS
标记为deprecated
,并在将来逐渐删除它们
public class DFS {
public DFS(Graph graph) { ... } // you have the option to mark this as deprecated
public DFS(Graph graph, Source source) { ... } // New constructor
public void runDFS() { // New API
if (this.source == null) {
throw new IllegalStateException("Source is null!");
}
doRun(this.source);
}
// Again you have the option to mark this as deprecated
public void runDFS(Source source) {
// handle here the case where client already provided a source with the new
// constructor. Should we replace it? Should we throw an exception?
this.source = source;
doRun(source);
}
private void doRun(Source source) {
// this is private so it can be called by both runDFS() and runDFS(Source)
// do whatever you did before here
}
public Path getPathFromSource(Vertex vertex) { // New API
if (source == null) {
throw new IllegalStateException("Source is null!");
}
// do the job for the new API here
}
}