Adapter

The Adapter pattern allows us to access the functionality of an object using a different interface. As the name suggests, it adapts an object so that it can be used by components expecting a different interface. The following diagram clarifies the situation:

Adapter

The preceding diagram shows how the Adapter is essentially a wrapper for the Adaptee, exposing a different interface. The diagram also highlight the fact that the operations of the Adapter can also be a composition of one or more method invocations on the Adaptee. From an implementation perspective, the most common technique is composition where the methods of the Adapter provides a bridge to the methods of the Adaptee. This pattern is pretty straightforward so let's work immediately on an example.

We are now going to build an adapter around the LevelUP API, transforming it into an interface that is compatible with the core fs module. In particular, we will make sure that every call to readFile() and writeFile() will translate into calls to db.get() and db.put(); this way we will be able to use a LevelUP database as a storage backend for simple filesystem operations.

Let's start by creating a new module named fsAdapter.js. We will begin by loading the dependencies and exporting the createFsAdapter() factory that we are going to use to build the adapter:

Next, we will implement the readFile() function inside the factory and ensure that its interface is compatible with the one of the original function from the fs module:

In the preceding code, we had to do some extra work to make sure that the behavior of our new function is as close as possible to the original fs.readFile() function. The steps performed by the function are described as follows:

As we see, the function that we created is quite rough; it does not want to be a perfect replacement for the fs.readFile() function but it definitely does its job in the most common situations.

To complete our small adapter, let's now see how to implement the writeFile() function:

Also, in this case, we don't have a perfect wrapper, we will ignore some options such as file permissions (options.mode), and we will forward any error that we receive from the database as it is.

Finally, we only have to return the fs object and close the factory function using the following lines of code:

Our new adapter is now ready; if we now write a small test module, we can try to use it:

The preceding code uses the original fs API to perform a few read and write operations on the filesystem and should print something like the following to the console:

Now, we can try to replace the fs module with our adapter, as follows:

var levelup = require('level');
var fsAdapter = require('./fsAdapter');
var db = levelup('./fsDB', {valueEncoding: 'binary'});
var fs = fsAdapter(db);

Running again our program should produce the same output, except the fact that none of the file that we specified is read or written using the filesystem; instead, any operation performed using our adapter will be converted into an operation performed on a LevelUP database.

The adapter that we just created might look silly; what's the purpose of using a database in place of the real filesystem? However, we should remember that LevelUP itself has adapters that enable the database to also run in the browser; one of these adapters is level.js (https://npmjs.org/package/level-js). Now, our adapter should make perfect sense; we can think of using it to share with the browser code, which relies on the fs module! For example, the web spider that we created in Chapter 2, Asynchronous Control Flow Patterns, uses the fs API to store the web pages downloaded during its operations; our adapter will allow it to run in the browser, by applying only minor modifications! We soon realize that Adapter is an extremely important pattern also when it comes to sharing code with the browser, as we will see in more detail in Chapter 6, Recipes.

There are plenty of real-world examples of the Adapter pattern: we list some of the most notable examples here for you to explore and analyze: