This entry is part 5 of 5 in the series SOLID JavaScript

This is the fifth and final installment in the SOLID JavaScript series which explores the SOLID design principles within the context of the JavaScript language.  In this final installment, we’ll examine the Dependency Inversion Principle.

 

The Dependency Inversion Principle

The Dependency Inversion Principle relates to the stability and reusability of higher-level components within an application.  The principle states:

A. High-level modules should not depend on low-level modules.  Both should depend on abstractions.

B. Abstractions should not depend upon details.  Details should depend upon abstractions.

The primary concern of the Dependency Inversion Principle is to ensure that the main components of an application or framework remain decoupled from the ancillary components providing low-level implementation details.  This ensures that the important parts of an application or framework aren’t affected when the low level components need to change.

The first part of the principle concerns the method of coupling between high level modules and low level modules.  With traditional layered architecture, high level modules (components encapsulating the core business logic of the application) take dependencies upon low level modules (components providing infrastructure concerns).  When adhering to the Dependency Inversion Principle, this relationship is reversed (i.e. inverted).  Rather than high level modules being coupled to low level modules, low level modules are coupled to interfaces declared by the high level modules.  For example, given an application with persistence concerns, a traditional design might contain a core module which relies upon the API defined by a persistence module.  Refactoring toward the Dependency Inversion Principle, the persistence module would be modified to conform to an interface defined by the core module.

The second part of the Dependency Inversion Principle concerns the proper relationship between abstractions and details.  To understand this portion of the principle, it helps to consider its applicability to the language from whence the principle was conceived: the C++ language.

Unlike some statically typed languages, C++ doesn’t provide a language level construct for defining interfaces.  What it does provide is the separation of class definition from class implementation.  In C++, classes are defined using a header file which lists the member methods and variables of a class along with a source file which contains the implementation of any member methods.  Since any member variables and private methods are declared within the header file, it’s possible for classes intended to be used as abstractions to become dependent upon the implementation details of the class.  This is overcome by defining classes which only contain abstract methods (known in C++ as pure abstract base classes) to serve as interfaces for implementing classes.

DIP and JavaScript

As a dynamic language, JavaScript doesn’t require the use of abstractions to facilitate decoupling.  Therefore, the stipulation that abstractions shouldn’t depend upon details isn’t particularly relevant to JavaScript applications.  The stipulation that high level modules shouldn’t depend upon low level modules is, however, relevant.

When discussing the Dependency Inversion Principle in the context of statically-typed languages, the concern of coupling is both semantic and physical.  That is to say, if a high level module is coupled to a low level module, it is coupled both to the semantic interface as well as the physical definition of the interface defined within the low level module.  The implication is that high level module dependencies should be inverted both for dependencies upon 3rd party libraries as well as native low level modules.

To explain, consider a .Net application which might encapsulate useful high level modules which have a dependency upon a low level module providing persistence concerns. While the author is likely to have expressed a similar API for the persistence interface whether the DIP was adhered to or not, the high level module would not be capable of being reused in another application without bringing along the dependency upon the low level module where the persistence interface is defined.

In JavaScript, the applicability of the Dependency Inversion Principle is relevant only to the semantic coupling of high level modules to low level modules.  As such, adherence to the DIP can be achieved simply by expressing the semantic interface in terms of the application’s needs as opposed to coupling to the implicit interface defined by whatever implementation is chosen for a low level module.

To illustrate, consider the following example:

$.fn.trackMap = function(options) {
    var defaults = { 
        /* defaults */
    };
    options = $.extend({}, defaults, options);

    var mapOptions = {
        center: new google.maps.LatLng(options.latitude,options.longitude),
        zoom: 12,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    },
        map = new google.maps.Map(this[0], mapOptions),
        pos = new google.maps.LatLng(options.latitude,options.longitude);

    var marker = new google.maps.Marker({
        position: pos,
        title: options.title,
        icon: options.icon
    });

    marker.setMap(map);

    options.feed.update(function(latitude, longitude) {
        marker.setMap(null);
        var newLatLng = new google.maps.LatLng(latitude, longitude);
        marker.position = newLatLng;
        marker.setMap(map);
        map.setCenter(newLatLng);
    });

    return this;
};

var updater = (function() {    
    // private properties

    return {
        update: function(callback) {
            updateMap = callback;
        }
    };
})();

$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater
});

In this listing, we have a small library which converts a div target into an map used to show the current location of a item being tracked.  The trackMap function has two dependencies: the 3rd party Google Maps API and a location feed.  The responsibility of the feed object is to simply invoke a callback (supplied during the initialization process) with a new latitude and longitude position when the icon location should be updated.  The Google Maps API is used to do the actual rending of the map to the screen.

While the interface of the feed object may or may not have been designed in terms of the trackMap function, the fact that its role is simple and focused makes it easy to substitute different implementations.  Not so with the Google Maps dependency.  Since the trackMap function is semantically coupled to the Google Maps API, switching to a different mapping provider would require the trackMap function to be rewritten or an adapter to be written to adapt another mapping provider to Google’s specific interface.

To invert the semantic coupling to the Google Maps library, we need to redesign the trackMap function to have a semantic coupling to an implicit interface which abstractly represents the functionality needed by a mapping provider.  We would then need to implement an object which adapts this interface to the Google Maps API.  The following shows this alternate version of the trackMap function:

$.fn.trackMap = function(options) {
    var defaults = { 
        /* defaults */
    };

    options = $.extend({}, defaults, options);

    options.provider.showMap(
        this[0],
        options.latitude,
        options.longitude,
        options.icon,
        options.title);

    options.feed.update(function(latitude, longitude) {
        options.provider.updateMap(latitude, longitude);
    });

    return this;
};


$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater,
    provider: trackMap.googleMapsProvider
});

In this version, we’ve redesigned the trackMap function to express its needs in terms of a generic mapping provider interface and have moved the implementation details out into a separate googleMapsProvider component which can be bundled as a separate JavaScript module.  Here’s our googleMapsProvider implementation:

trackMap.googleMapsProvider = (function() {
    var marker, map;

    return {
        showMap: function(element, latitude, longitude, icon, title) {
            var mapOptions = {
                center: new google.maps.LatLng(latitude, longitude),
                zoom: 12,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            },
                pos = new google.maps.LatLng(latitude, longitude);

            map = new google.maps.Map(element, mapOptions);

            marker = new google.maps.Marker({
                position: pos,
                title: title,
                icon: icon
            });

            marker.setMap(map);
        },
        updateMap: function(latitude, longitude) {
            marker.setMap(null);
            var newLatLng = new google.maps.LatLng(latitude,longitude);
            marker.position = newLatLng;
            marker.setMap(map);
            map.setCenter(newLatLng);
        }
    };
})();

With these changes, our trackMap function is now more resilient to the changes that might occur to the Google Maps API and is capable of being reused with another mapping provider.  That is, as long as it’s API can be adapted to the needs of our application.

Whither Dependency Injection?

While not particularly related, the concept of Dependency Injection is often confused with the Dependency Inversion Principle due to a similarity in terminology.  For this reason, a discussion of the differences between the two concepts may prove helpful for some.

Dependency Injection is a specific form of Inversion of Control in which the concern being inverted is how a component obtains its dependencies.  When using Dependency Injection, dependencies are supplied to a component rather than the component obtaining the dependency by means of creating an instance of the dependency, requesting the dependency through a factory, requesting the dependency from a Service Locator, or any other means of initiation by the component itself.  Both the Dependency Inversion Principle and Dependency Injection are concerned with dependencies and both use the notion of inversion to contrast an alternate approach to a presumed standard approach.  However, the Dependency Inversion Principle isn’t concerned with how components obtains their dependencies, but with the decoupling of high level components from low level components.  In a sense, the Dependency Inversion Principle might be said to be another form of Inversion of Control where the concern being inverted is which module defines the interface.

Conclusion

This brings us to the end of our series.  While in the course of our examination we saw variations in how the SOLID design principles apply to JavaScript over other languages, each of the principles were shown to have some degree of applicability within JavaScript development.

Tagged with:  
This entry is part 4 of 5 in the series SOLID JavaScript

This is the forth installment in the SOLID JavaScript series which explores the SOLID design principles within the context of the JavaScript language. In this installment, we’ll be covering the Interface Segregation Principle.

 

The Interface Segregation Principle

The Interface Segregation Principle relates to the cohesion of interfaces within a system. The principle states:

Clients should not be forced to depend on methods they do not use.

When clients depend upon objects which contain methods used only by other clients, or are forced to implement unused methods with degenerate functionality (potentially leading to Liskov Substitution Principle violations), this can lead to fragile code. This occurs when an object serves as an implementation for a non-cohesive interface.

The Interface Segregation Principle is similar to the Single Responsibility Principle in that both deal with the cohesion of responsibilities. In fact, the ISP can be understood as the application of the SRP to an object’s public interface.

JavaScript Interfaces

So, what does this principle have to do with JavaScript? After all, JavaScript doesn’t have interfaces, right? If by interfaces we mean some abstract type provided by the language to establish contracts and enable decoupling, then such an assertion would be correct. However, JavaScript does have another type of interface. In the book Design Patterns – Elements of Reusable Object-Oriented Software, Gamma et al., we find the following definition of an interface:

Every operation declared by an object specifies the operation’s name, the objects it takes as parameters, and the operation’s return value. This is known as the operation’s signature. The set of all signatures defined by an object’s operations is called the interface to the object. An object’s interface characterizes the complete set of requests that can be sent to the object.

Regardless of whether a language provides a separate construct for representing interfaces or not, all objects have an implicit interface comprised of the set of public properties and methods of the object. For example, consider the following library:

var exampleBinder = {};
exampleBinder.modelObserver = (function() {
    /* private variables */
    return {
        observe: function(model) {
            /* code */
            return newModel;
        },
        onChange: function(callback) {
            /* code */
        }
    }
})();

exampleBinder.viewAdaptor = (function() {
    /* private variables */
    return {
        bind: function(model) {
            /* code */
        }
    }
})();

exampleBinder.bind = function(model) {
    /* private variables */
    exampleBinder.modelObserver.onChange(/* callback */);
    var om = exampleBinder.modelObserver.observe(model);
    exampleBinder.viewAdaptor.bind(om);
    return om;
};

This listing presents a library named exampleBinder whose purpose is to facilitate two-way data-binding. The public interface of the library is represented by the bind method. The responsibilities of change notification and view interaction have been separated into the objects modelObserver and viewAdaptor respectively to allow for alternate implementations. These objects represent the implementations of the interfaces expected by the bind method. Any object adhering to the semantic behavior represented by these interfaces may be substituted for the default implementations.

Though the JavaScript language may not provide interface types to aid in specifying the contract of an object, the object’s implicit interface still serves as a contract to clients within an application.

ISP and JavaScript

The following sections discuss some of the consequences to Interface Segregation Principle violations in JavaScript. As you’ll see, while the ISP is applicable to the design of JavaScript applications, the language features offer a little more resiliency to non-cohesive interfaces than in statically-typed languages.

Degenerate Implementations

In statically-typed languages, one issue that arises from violations of the ISP is the need to create degenerate implementations. In languages like Java and C#, all methods declared by an interface must be implemented. For cases where a particular interface is required, but only a subset of the behavior is relevant for a given usage scenario, the unused methods are generally stubbed out with empty implementations or with implementations which simply throw an exception indicating the method isn’t actually implemented. In JavaScript, cases where only a subset of an object’s interface is utilized doesn’t end up posing the same issues since a substituting object need only provide the expected properties to conform to the consumed portion of the object’s interface. Nevertheless, such implementations can still lead to Liskov Substitution Violations.

For instance, consider the following example:

var rectangle = {
    area: function() { 
        /* code */
    },
    draw: function() { 
        /* code */
    }
};

var geometryApplication = {
    getLargestRectangle: function(rectangles) { 
        /* code */
    }
};

var drawingApplication = {
    drawRectangles: function(rectangles) {
       /* code */
    }
};

While a substitute rectangle could be created with only an area() method in order to meet the needs of geometryApplication’s getLargestRectangle method, such an implementation would represent a LSP violation with respect to drawingApplication’s drawRectangles method.

Static Coupling

Another issue that arises in statically-typed languages is the issue of static coupling. In statically-typed languages, interfaces play a significant role in the design of loosely-coupled applications. In both static and dynamic languages, there are times when an object may need to collaborate with multiple clients (e.g. in cases where shared state may be required). For statically-typed languages, it’s considered a best practice for clients to interact with objects using Role Interfaces. This allows clients to interact with an object which may require the intersection of multiple roles as an implementation detail without coupling the client to unused behavior. In JavaScript, this isn’t an issue since objects are decoupled by virtue of the language’s dynamic capabilities.

Semantic Coupling

One consequence that’s equally relevant between JavaScript and statically-typed languages is the issue of semantic coupling. Semantic coupling is the interdependency that occurs when one portion of an object’s behavior is coupled to another. When an object’s interface specifies non-cohesive responsibilities, changes to adapt to the evolving needs of one client may inadvertently affect another client resulting from changes made to shared state or behavior. This is also one of the potential consequences to violating the Single Responsibly Principle, though this is typically viewed from the perspective of the impact non-cohesive behavior has on collaborating objects. From an ISP perspective, this consequence is extended to extensions via inheritance or object substitution.

Extensibility

Another important role interfaces play in JavaScript applications is in the area of extensibility. The most prevalent example of this can be seen in the use of callbacks where a function conforming to the expected interface is passed to a library to be invoked at some point in the future (e.g. upon a successful AJAX request). The Interface Segregation Principle becomes relevant when such interfaces require the implementation of an object with multiple properties and/or methods. When an interface begins to require the implementation of a significant number of methods, this makes working with the consuming library more difficult and is likely an indication that the interfaces represent non-cohesive responsibilities. This is often described as “fat” interfaces.

In summary, the dynamic capabilities of JavaScript leave us with a few less consequences to the occurrence of non-cohesive interfaces than with statically-typed languages, but the Interface Segregation Principle has its place in the design of JavaScript applications nonetheless.

Next time, we’ll take a look at the final principle in the SOLID acronym: The Dependency Inversion Principle.

Tagged with: