Search code examples
javascriptapioverloadingnaming

API design (naming etc) workaround for overloading methods in JavaScript


Given the following hypothetical method overloads (the example is using typescript type syntax to indicate the argument and return value types):

class TestObject {
  getContent(logicalPath: string, version: string = 'latest') : string | Buffer | stream
  getContent(digest: string) : string | Buffer | stream
  getContent(contentPath: string) : string | Buffer | stream
}

What is the most elegant way to achieve such thing is JavaScript? Basically what needed here is a way to retrieve a 'content' given different parameter sets. The same content can be requested either as string, Buffer or stream. How many methods with different names should we have and what kind of arguments should we use? Assume that the most used method will be getContent(logicalPath: string) : string | Buffer | stream. What is the best way to request for a different return type: use different method name or specify it in the argument? Internally, logicalPath and version will be resolved to digest, and digest will be resolved to contentPath.

The most obvious solution is to have 9 different methods with long name, eg `getContentByLogicalPathAsString, getContentByLogicalPathAsBuffer, etc. But that is very ugly.


Solution

  • Don't create 9 methods for all combinations, create 3 methods that return objects implementing an interface with another 3 methods:

    class TestObject {
      getContentByLogicalPath(path: string, version: string = 'latest'): Content
      getContentByDigest(digest: string): Content
      getContent(Path: string): Content
    }
    interface Content {
      toString(): string;
      toBuffer(): Buffer;
      toStream(): Stream;
    }
    

    If the getContentBy… methods get unwieldy, consider actually overloading the method by not accepting plain strings but rather identifier objects:

    type ContentIdentifer = LogicalPath | Digest | ContentPath;
    class TestObject {
      getContent(id: ContentIdentifier): Content
    }
    

    These can be implemented via a simple discriminated union, or actually as separate classes with methods to convert into each other:

    class LogicalPath {
      type: 'logical';
      toDigest(): Digest;
      toConentPath(): ContentPath;
      …
    }
    class Digest {
      type: 'digest';
      toConentPath(): ContentPath;
      …
    }
    class ContentPath {
      type: 'content';
      …
    }