API Design: Don't Expose More than Necessary
Jakob Jenkov |
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
.
Tweet | |
Jakob Jenkov |