Skip to content

Achieving Abstraction In JavaScript

In computer science, abstraction is to hide certain details and only show the essential features of the object. Abstraction tries to reduce and factor out details so that the developer can focus on a few concepts at a time. This approach improves understandability as well as maintainability of the code. While abstraction is well understood and well applied in languages like Java, C++,  this approach is not discussed much for JavaScript. In this post, we will try to give some insight for abstraction of JavaScript.

First of all, there is not built in support for traditional abstraction in JavaScript. At least, there are not any types like interfaces and abstract classes. Developers are in general does not care for abstraction. Although, ignoring abstraction does not cause any problems while writing small piece of code, it can be relatively difficult when you are dealing with a large scale application. On the other hand, abstraction provides ways of dealing with cross-cutting concerns and enables us to avoid tightly coupled code. There is a good saying about abstraction “Program to an interface, not an implementation.” from Design Patterns book. To deal with complex applications one should use interfaces because using same interface you can change implementation whenever you want without much effort. For those reasons, we should have abstraction in JavaScript, too.

For abstraction, one can use interfaces and abstract classes but JavaScript does not have such features. We can alternatively define interfaces using prototypes. Let’s see how we define an interface in JavaScript.

//ItemRepo interface
var ItemRepo = {
   addItem : function(item){},
   removeItem : function(id){},
   getItem:function(id){}
};

We have just defined which methods ItemRepo has. We will now define two implementations for ItemRepo : the first one is to save and retrieve data by ajax and the second one is to save and retrieve data from cookie. Let’s see how we implement ItemRepo interface.

var ItemRepoAjax = function(url){
   this.url = url;
};
var ItemRepoCookie = function(){};
//Extend the ItemRepo for Ajax
ItemRepoAjax.prototype = Object.create(ItemRepo);
//Extend the ItemRepo for Cookie
ItemRepoCookie.prototype = Object.create(ItemRepo);
ItemRepoAjax.prototype.addItem = function(item){
   //actual add item code
};
ItemRepoCookie.prototype.addItem = function(item){
   //actual add item code
};

As we have defined our implementations for ItemRepo, Let’s create a new class that will make use of ItemRepo.

var ItemController = function(itemRepo){
   this.itemRepo = itemRepo;
};
ItemController.prototype.add = function(item){
   itemRepo.addItem(item);
};

As long as we have two implementation for ItemRepo, we can easily switch our repository between one another to save and retrieve data. This will improve maintainability and testability of the code. We could easily mock the itemRepo object and test the rest with the mock. Moreover, we could change our way of saving and retrieving data without changing the rest of the code. Let’s make use of one of the ItemController and ItemRepo.

var itemRepo = new ItemRepoAjax("url");
var itemController = new ItemController(itemRepo);
itemController.add({item:"myItem"});

As we have implemented our controller class, we may want to log or profile methods for this class. We can do that by extending ItemController and injecting a logger into it. Let’s see how do that.

var Logger = {
   log : function(log){}
}
var ConsoleLogger = function() {};
ConsoleLogger.prototype = Object.create(Logger);
ConsoleLogger.prototype.log = function(log) {
   //actual logging code
}
// Define new Controller
var LoggingItemController = function(itemRepo, logger){
   this.itemRepo = itemRepo;
   this.logger = logger;
}
LoggingItemController.prototype = Object.create(ItemController);
LoggingItemController.prototype.add = function(item) {
   this.logger.log("start");
   this.itemRepo.addItem(item);
   this.logger.log("stop");
};
var consoleLogger = new ConsoleLogger();
var itemRepo = new ItemRepoCookie();
var loggingItemController = 
   new LoggingItemController(itemRepo, consoleLogger);
loggingItemController.add("item");

Moreover, we can apply classic patterns easily by using interface definitions. To make it clear, let’s see how we define Factory pattern in JavaScript.

var ItemRepoFactory = {
   getItemRepo : function(type) {}
};
var ConcreteItemRepoFactory = function() {};
ConcreteItemRepoFactory.prototype = Object.create(ItemRepoFactory);
ConcreteItemRepoFactory.prototype.getItemRepo = function(type) {
   if (type === "ajax") {
      return new ItemRepoAjax("url");
   }
   return new ItemRepoCookie();
};

Using interfaces in JavaScript not only increases maintainability, it also increases testability. We can easily write mock classes to test the code. Let’s see how we test the code with mock objects.

var ItemRepoMock = function(mockItem){
   this.mockItem = mockItem;
};
//Extend the ItemRepo for Ajax
ItemRepoMock.prototype = Object.create(ItemRepo);
ItemRepoMock.prototype.getItem = function(id){
   return this.mockItem;
}
function testGetItem(){
   var mockItem =  "mock";
   var mockItemRepo = new ItemRepoMock(mockItem);
   var itemController = new ItemController(mockItemRepo);
   //An equal method is needed.
   equal(itemController.getItem("id"), mockItem);
}

By defining those interfaces, one can reuse objects create by using dependency injection pattern. There are libraries that supports dependency injection like wire, but we can create a manual dependency injection on start of our application by defining every variable. Having defined dependencies at the beginning, we could easily change type of itemRepo or logger without touching any code but the configuration for dependencies.

In consequence, JavaScript is a weakly typed language and does not have classical support for abstraction. We have tried to achieve abstraction in JavaScript by defining interfaces and using them. Nevertheless, interfaces do not affect our code at all. They are nothing more than a way of documenting code for human beings. All the examples we have shown above would work exactly the same even if we completely remove the interfaces. However, it is about making roles explicit. This increases readability, understandability and maintainability of the code.

Stay updated!

Receive insights on tech, leadership, and growth.

7 Comments

  1. Manikandan R Manikandan R

    Hi Mate,

    Thank you very much for this awesome tutorial. I tried to implement a similar approach in my project. But it is not working. I am getting “Object doesn’t support this property or method” error while trying to create the prototype.
    ItemRepoAjax.prototype = Object.create(ItemRepo);
    Please help

  2. Object.create method is not compatible with IE 7 and IE 8. I guess, you are trying to browse your application with browsers that does not support some of ECMAScript 5 standards. Check out ECMAScript standards that is followed by browsers.

  3. Max Vi Max Vi

    why not use just `ItemRepoAjax.prototype = new ItemRepo();` instead?

  4. Faris Rayhan Faris Rayhan

    This is awesome tutorial on how we can implement interface in our javascript code. As we know javascript does not contains built in interface by default.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.