Dependency inversion principle in JavaScript
S

4

14

Is anyone able to help illustrate Dependency inversion principle in JavaScript jQuery?

Which would highlight and explain these 2 points:

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.

What are abstractions or high/low level modules?

This will really help in my understanding, thanks!

Seemly answered 18/3, 2011 at 7:2 Comment(5)
Doesn't DIP stand for "Digital Image Processing" ?Greathearted
@Nishant: maybe if you were in the field of photography... in modern agile software development it stands for the dependency inversion principle.Exposition
@Domenic, @Nishant: I suppose the best thing to do is to add a non-acronym tag to make it clear what this question is about.Macrography
freshbrewedcode.com/derekgreer/2012/01/22/…Buffet
You could use inversify.ioCelt
E
36

I would say the DIP applies in JavaScript much the same way as it applies in most programming languages, but you have to be aware of the role of duck typing. Let's do an example to see what I mean...

Let's say I want to contact the server for some data. Without applying DIP, this might look like:

$.get("/address/to/data", function (data) {
    $("#thingy1").text(data.property1);
    $("#thingy2").text(data.property2);
});

With DIP, I might instead write code like

fillFromServer("/address/to/data", thingyView);

where the abstraction fillFromServer can for the particular case where we want to use jQuery's Ajax be implemented as

function fillFromServer(url, view) {
    $.get(url, function (data) {
        view.setValues(data);
    });
}

and the abstraction view can be implemented for the particular case of a view based on elements with IDs thingy1 and thingy2 as

var thingyView = {
    setValues: function (data) {
        $("#thingy1").text(data.property1);
        $("#thingy2").text(data.property2);
    }
};

Principle A:

  • fillFromServer belongs in a low-level module, handling as it does the low-level interaction between the server and the view. Something like, say, a settingsUpdater object would be part of a higher-level module, and it would rely on the fillFromServer abstraction---not on the details of it, which are in this case implemented via jQuery.
  • Similarly, fillFromServer does not depend on specifics of the DOM elements and their IDs to perform its work; instead, it depends on the abstraction of a view, which for its purposes is any type that has a setValues method. (This is what is meant by "duck typing.")

Principle B:

This is a bit less easy to see in JavaScript, with its duck-typing; in particular, something like view does not derive from (i.e. depend on) some kind of viewInterface type. But we can say that our particular instance, the thingyView, is a detail that "depends" on the abstraction view.

Realistically, it is "depending" on the fact that callers understand what kind of methods should be called, i.e. that callers are aware of the appropriate abstraction. In the usual object-oriented languages, it is easier to see the dependency of thingyView explicitly on the abstraction itself. In such languages, the abstraction would be embodied in an interface (say, IView in C# or Viewable in Java), and the explicit dependency is via inheritance (class ThingyView : IView or class ThingyView implements Viewable). The same sentiment applies, however.


Why is this cool? Well, let's say one day I needed to put the server data into textboxes with IDs text1 and text2 instead of <span />s with IDs thingy1 and thingy2. Furthermore, let's say that this code was being called very very often, and benchmarking revealed that critical performance was being lost via the use of jQuery. I could then just create a new "implementation" of the view abstraction, like so:

var textViewNoJQuery = {
   setValues: function (data) {
        document.getElementById("text1").value = data.property1;
        document.getElementById("text2").value = data.property2;
   }
};

Then I inject this particular instance of the view abstraction into my fillFromServer abstraction:

fillFromServer("/address/to/data", textViewNoJQuery);

This required no changes to fillFromServer code, because it depended only on the abstraction of a view with a setValues method, and not on the details of the DOM and how we access it. Not only is this satisfying in that we can reuse code, it also indicates that we have cleanly separated our concerns and created very future-proof code.

Exposition answered 18/3, 2011 at 8:12 Comment(3)
thanks for the lengthy but comprehensive answer, I'll take some time to digest this. much appreciated +1.Seemly
also, v. good example where detail is completely stripped away from the abstract function, both concepts I was not sure of before but can identify with now :)Seemly
Would this also be a good example of the related open/closed principle?Jat
E
6

EDIT:

This shows the usage of DIP in raw JavaScript and a less-complete jQuery example. However, the following description can be easily applied to jQuery. See jQuery example at the bottom.

The best way is to take advantage of the "Adapter Pattern" -- also called a "wrapper".

An Adapter is basically a way of wrapping an object or module in such a way that it provides the same consistent interface to its dependents. That way, the dependent class (usually a higher-level class) can easily swap out modules of the same type.

An example of this would be a high-level (or supra) module whom depends on Geo/Mapping modules.

Lets analyze this. If our supra module is already using GoogleMaps but then management decides its cheaper to go with LeafletMaps -- we don't want to have to rewrite every method call from gMap.showMap(user, latLong) to leaflet.render(apiSecret,latLong, user), et al. This would be a nightmare to have to port our application from one framework to an other this way.

What we want: We would like a "wrapper" that provides the same consistant interface to the supra module -- and do this for every lower-level module (or infra module).

Here is a vary simple example:

var infra1 = (function(){
    function alertMessage(message){
        alert(message);
    }

    return {
        notify: alertMessage
    };
})();

var infra2 = (function(){
    function logMessage(message){
        console.log(message);
    }

    return {
        notify: logMessage
    };
})();


var Supra = function(writer){
    var notifier = writer;
    function writeMessage(msg){
        notifier.notify(msg);
    }

    this.writeNotification = writeMessage;
};


var supra;

supra = new Supra(infra1);
supra.writeNotification('This is a message');

supra = new Supra(infra2);
supra.writeNotification('This is a message');

Notice that no matter which type of lower-level module "write" we use (in this case infra1 and infra2), we do not have to rewrite any of the implementation of our high-level module, Supra. This is because DIP takes advantage of two different Software Design Principles: "IoC" (Inversion of Control) and "DI" (Dependency Injection).

The best analogy that I've come across is the picture shown below.

enter image description here Every electrical source relies on an interface specific for the types of things that need to plug into it.

jQuery Description:

This pattern can be easily applied to the usage of frameworks such as jQuery. One example would be the simple DOM-Query handle. We can use DIP to allow loose-coupling so that if we ever decide to switch frameworks or rely on native DOM-Query methods, maintenance is easy:

var jQ = (function($){

    return {
        getElement: $
    };
})(jQuery);

var nativeModule = (function(){

    return {
        getElement: document.querySelector
    };
})();


var SupraDOMQuery = function(api){
    var helper = api, thus = this;

    function queryDOM(selector){
        el = helper.getElement(selector);
        return thus;
    }

    this.get = queryDOM;
};


var DOM;

DOM = new SupraDOMQuery(jQ);
DOM.get('#id.class');

DOM = new SupraDOMQuery(nativeModule);
DOM.get('#id.class');

Obviously, this example would need magnitudes of more functionality in order to be practical, but I hope it gets the point across.

Basically, the differences between an Adapter and a Facade become somewhat trivial. In a Facade, you're looking at probably a single module that wraps an API or another module; whereas an Adapter creates a consistent Facade API for each of its modules, and exploits this technique to obviate tight-coupling.

Most of the JavaScript Design Patterns books go over the Adapter Pattern; one that specifically goes over a 'jQuery Adapter' is Learning JavaScript Design Patterns by Addy Osmani published by O'Reilly -- here. However, I also advise looking into Pro JavaScript Design Patterns by Dustin Diaz and Ross Harmes published by Apress -- check it out. Nevertheless, I think its important to understand the context in which we plan to implement DIP in relation to jQuery.

I hope this helps to clarify things :)

Ensilage answered 17/4, 2014 at 19:3 Comment(0)
M
1

Here's my understanding and would appreciate feedback. The key test is 'who has the power'.

In a traditional implementation

High Level (HL) Code --> Low Level (LL) Code.

So for example

LL code

function LLdoAlert(text) { alert(message); }
function LLdoConsole(text) { console.log(message); }

HL Code

LLdoAlert('Hi there'); 
LLdoConsole('Hi there');

Here the LL code has the power. Change the LL function name for example, HL code breaks.

With dependency inversion

High Level (HL) Code --> HL/LL service interface <-- Low Level (LL) Code.

Where the HL code also owns the service interface. So for example

HL Code

var HLdoOutputSI = {
  exec: function(method, text) {
        if (this[method]) this[method](text);
    },
  register: function(name, fn) {
    this[name] = fn;
  }
}

HLdoOutputSI.exec('alert', 'Hi there');
HLdoOutputSI.exec('console', 'Hi there');

LL code:

HLdoOutputSI.register('alert', function(text)  { alert(message); });
HLdoOutputSI.register('console', function(text) { console.log(message); }

Here we can now have any number of LL code items registering functions, but none break the HL code. (If none register, functionality is skipped). If LL code want's to play, they have to follow the HL code method. I.e. power now shifted from LL to HL.

Marinate answered 11/11, 2019 at 18:57 Comment(0)
P
0

Dependency inversion principle in JavaScript jQuery

There's no any connection between DI and jQuery. DI is all about the structure and assembling application from compoents. jQuery is a handy wrapper around DOM, nothing more, it has no any structure or components.

You can use DI to assembly Your JavaScript Application, but it would looks much the same no matter do You use jQuery or not.

Pelota answered 22/11, 2011 at 19:48 Comment(1)
Alexey, I voted up because this is an important note to consider. However, in JavaScript, DI & IoC deviates radically from the norm as JS doesn't support interfaces. Between my example and AMD (such as Node's native module-loader), DI is applicable. As so, DIP has been implemented into jQuery and Raw-JavaScript using an Adapter. The most important question to ask is, 'In what context does bcm plan to implement DIP in relation to jQuery?'. If it is strictly jQuery then we should all take note of your answer (and regardless); otherwise, there are several possibilities. Thx for noting on jQ's DI.Ensilage

© 2022 - 2024 — McMap. All rights reserved.