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.