Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency inversion principle in JavaScript

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!

like image 530
bcm Avatar asked Mar 18 '11 07:03

bcm


People also ask

What does dependency inversion principle mean?

The Dependency Inversion Principle (DIP) states that high level modules should not depend on low level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.

What is the benefit of dependency inversion principle?

This principle seeks to "invert" the conventional notion that high level modules in software should depend upon the lower level modules. The principle states that high level or low level modules should not depend upon each other, instead they should depend upon abstractions.

What is inversion of control in Javascript?

In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework.

Why is it called dependency inversion?

It turns out that a long time ago higher level modules depending on lower level modules was something that was considered good practice (before Object Oriented Design). The name “Dependency Inversion” is alluding to this.


2 Answers

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.

like image 99
Domenic Avatar answered Oct 01 '22 20:10

Domenic


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.

like image 44
John Bell Avatar answered Oct 01 '22 20:10

John Bell