Java 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

我遇到了需要更改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();                     // 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      
    }

}