Adapter Pattern

The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two existing systems, making it possible for an object to be used as if it had a different interface.

Adapter pattern allows to associate new functionality with existing objects without introducing explicit dependencies:

More details:

Example: Abstracting a File System API Across Environments

Let’s consider the following scenario:

An application needs to interact with a file system, but the implementation may vary depending on the environment: in a Node.js environment, it might use the native file system or an S3-based implementation, while in a browser, it could rely on a domain-specific file system or use read/write access to local folders (e.g., via Chromium-based APIs).

Suppose we define the following interfaces, with implementations tailored to different environments (Node.js, browser, etc.):

interface File {
  path: string;
  read(): AsyncGenerator<Uint8Array>;
}

interface FileSystem {
  get(path: string): Promise<File>;
  list(path: string): AsyncGenerator<File>;
}

The core application should be able to use this API without being tightly coupled to the implementation details. To achieve this, we can associate a specific FileSystem implementation with the application context like so:

// Define the application context object used throughout the app
class ApplicationContext { ... }

// Use an adapter to associate a FileSystem with the context,
// without introducing direct dependencies between FileSystem and ApplicationContext
const [getFileSystem, setFileSystem] = newAdapter<FileSystem, ApplicationContext>("sys.fileSystem");

We can then initialize the application:

// Create a new application context
const applicationContext = new ApplicationContext();

// Associate a browser-specific file system implementation
const fileSystem = new BrowserBasedFileSystem();
setFileSystem(applicationContext, fileSystem);

Later, when we need access to the file system in another part of the application:

const fileSystem = getFileSystem(applicationContext);
const configFile = await fileSystem.get("/app.config.json");
...

Benefits