Blog-Archiv

Dienstag, 7. April 2015

JS Overrides

What we would like to do is reuse software just by modifying a small bit of it to achieve what we intended. Building a flower view instead of a bird view, in other words: reuse the bird framework to frame flowers :-)

Object-oriented programming languages have been providing this for a long time. JavaScript pretends to be object-oriented. Let's see whether we can turn a "Hello World" module into a "Good Day" module by using overrides. Overrides are the core of any framework technique. They are the >modifying a small bit of it<.

Test Page

Following HTML page is our test bed. JavaScript code will be between the <script> tags. Output will go the the element of id "output".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!DOCTYPE HTML>
<html>
  <head>
    <title>Override module functions</title>
  </head>
       
  <body>
    
    <p id="output">(No output present)</p>
    
    <script type="text/javascript">
    </script>
    
  </body>

</html>

"Hello World" Framework One

Here is the first attempt to write a reusable "Hello World" framework in JavaScript. Ambitiously it tries to achieve a maximum of privacy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
      var helloWorldModule = function() {
        var createHello = function() {
          return "Hello";
        };
        var createWorld = function() {
          return "World";
        };
        var createHelloWorld = function() {
          return createHello()+" "+createWorld();
        };
        
        return {
          createHello: createHello,
          createWorld: createWorld,
          createHelloWorld: createHelloWorld
        };
      };
      
      var helloWorld = helloWorldModule();
      
      helloWorld.createHello = function() {
        return "Good";
      };
      helloWorld.createWorld = function() {
        return "Day";
      };

      var output = helloWorld.createHelloWorld();
      document.getElementById("output").innerHTML = output;

The "Hello World" module provides a main function that puts together "Hello World" by calling two service functions that either produce "Hello" or "World". All functions are private, because they are implemented within the module function scope. Finally we return an object that exposes all three functions to the caller. This return object is the module instance.

On the module instance we overwrite the two service functions to produce "Good" and "Day" instead of "Hello" and "World". The asset is to use the main function to produce "Good Day" without having to implement this any more.

But it did not work. The privately defined functions remained private, and the createHelloWorld() function did not call the overrides but the original functions. This is to be seen:

Hello World

We expected "Good Day". The reason is that the implemented calls in the function body of createHelloWorld() were not changed, they were still directed towards the private functions.

But we can do it!

"Hello World" Framework Two

We learnt that the function pointers in the body of createHelloWorld() must be overwritable. So let's put all overridable functions into a common module object, and call the functions by using that module object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
      var helloWorldModule = function() {
        var that = {};
        
        that.createHello = function() {
          return "Hello";
        };
        that.createWorld = function() {
          return "World";
        };
        that.createHelloWorld = function() {
          return that.createHello()+" "+that.createWorld();
        };
        
        return that;
      };
      
      var helloWorld = helloWorldModule();
      
      helloWorld.createHello = function() {
        return "Good";
      };
      helloWorld.createWorld = function() {
        return "Day";
      };

      var output = helloWorld.createHelloWorld();
      document.getElementById("output").innerHTML = output;

Here all functions are implemented as members of the that object, which is finally returned as module instance. That means their function pointers are exposed to the outside world for overrides. And the implementation of createHelloWorld() uses exactly these pointers. When you change them, the implementation will be changed.

Now it worked. This is to be seen on the page:

Good Day

The drawback is that privacy was lost. All functions are public now. There is no protected mode like in other OO languages. I already pointed this out in my Blog about Encapsulation versus Inheritance in JS.

Please note that createHelloWorld() must call that.createHello() and that.createWorld(). The JS interpreter would stop with a runtime error when calling createHello() without that!

Reusing Super

Now that we have gone so far, let's try to reuse the original (super) implementations. What about this code, built upon "Hello World" Framework Two:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
      var helloWorld = helloWorldModule();
      
      var superCreateHello = helloWorld.createHello;
      helloWorld.createHello = function() {
        return "Good";
      };
      
      var superCreateWorld = helloWorld.createWorld;
      helloWorld.createWorld = function() {
        return "Day";
      };
      
      var superCreateHelloWorld = helloWorld.createHelloWorld;
      helloWorld.createHelloWorld = function() {
        return superCreateHello()+" "+superCreateWorld()+" was turned into "+superCreateHelloWorld();
      };
      
      var output = helloWorld.createHelloWorld();
      document.getElementById("output").innerHTML = output;

To be able to reuse the overwritten implementations we must store the pointers to these functions into some local variable, I called them superXXX. After storing the old implementations we can overwrite them.

Then we also overwrite createHelloWorld() to use the stored functions to access the original implementations. But mind that superCreateHelloWorld() already uses the overwritten service functions, it does not yield "Hello World", it yields "Good Day"!

Now the output is:

Hello World was turned into Good Day

There is no clean inheritance and code reusage concept in JavaScript. You always have to know exactly what you are doing. Think of a function name as a pointer to an object that is a function. This is the reason why I like to write

var someFunction = function() {
};

instead of

function someFunction() {
}

(Besides, there is a semicolon missing ....)



Keine Kommentare: