API Design: Don't Expose More than Necessary

Jakob Jenkov
Last update: 2014-05-25

The more of the internals of an API that is exposed to the user, the more the user needs to learn before she can master that API. This is not in line with the goal of being "as easy to learn as possible" mentioned in the introduction.

For instance, let's say I have a web crawler component which is capable of crawling and indexing a website. This could be useful if developing either a search engine or a search engine optimization (SEO) tool (I've done both - that's why this example is convenient). Say I had the Crawler and Indexer separated internallly, so I could change one without affecting the other. To the user on the outside though, I do not want to expose the Indexer. Writing an indexer is a complicated matter, and not one that I expect users of the web crawler to undertake themselves. Here is how my Crawler would look (as a sketch):

public class Crawler{

  protected Indexer         indexer  = new IndexerImpl();
  protected CrawlerListener listener = null;

  public Crawler(CrawlerListener listener){
    this.listener = listener;
  }

  public void crawl(String url){
    ...
  }

}

Notice how the Indexer is not visible to the outside world. In other words, the user doesn't have to learn about Indexer in order to use the Crawler. The Crawler works as a central point of access (a facade really) to the crawler API.

Notice how I could also change the API to allow an Indexer to be plugged in, if necessary:

public class Crawler{

  protected Indexer         indexer  = null;
  protected CrawlerListener listener = null;

  public Crawler(CrawlerListener listener){
    this.indexer  = new IndexerImpl();
    this.listener = listener;
  }

  public Crawler(CrawlerListener listener, Indexer indexer){
    this.indexer  = indexer;
    this.listener = listener;
  }


  public void crawl(String url){
    ...
  }

}

Now the user can plugin an Indexer if she is up to the task of implementing one. The API is now exposing the Indexer as well as the Crawler, which isn't desirable most of the time. However, the user is still able to just ignore it, and use the first constructor. The user still doesn't need to know about the IndexerImpl class.

The ability to plugin an Indexer could be useful during unit testing of the crawler. Plugging in a mock Indexer would make it possible to test the Crawler in isolation. This is not nearly as easy when it is not possible to plugin a mock Indexer. So, this code now adheres to the tip Design for Testing. Note however, that this is a bit of a tradeoff between exposing as little as possible, and designing for testability. The world isn't perfect. Neither is software design.

Notice also how this code uses the tip Provide Sensible Defaults. The first constructor creates an instance internally of the default Indexer implementation IndexerImpl.

Jakob Jenkov

Featured Videos

Java ConcurrentMap + ConcurrentHashMap

Java Generics

Java ForkJoinPool

P2P Networks Introduction

















Close TOC
All Tutorial Trails
All Trails
Table of contents (TOC) for this tutorial trail
Trail TOC
Table of contents (TOC) for this tutorial
Page TOC
Previous tutorial in this tutorial trail
Previous
Next tutorial in this tutorial trail
Next