Monday, March 7, 2011

Javascript: validating object interfaces

The interface is one of the most used tool in object-oriented programming. Based on the Gang of Four’s Design Patterns book, it is the first principle of reusable OOP. Unfortunately Javascript can’t implement any interfaces. At least not the usual way. And that’s where my interface class comes in. :)

I got the basic idea from the book Pro Javascript Design Patterns [Apress]. The authors have mentioned three types of emulating an interface in Javascript.

  1. Comments:
    "The easiest and least effective way of emulating an interface is with comments. Mimicking the style of other object-oriented languages, the "interface" and "implements" keywords are used but are commented out so they do not cause syntax errors."
  2. Attribute checking:
    A bit better, but it can be easily tricked out by a careless programmer. In this method when creating a class, it is left to the programmer's conscience to decide which interface is implemented. The interface name is stored in an array. Then, when an interface check is required, a function is validating the content of this array. If it says the class is implementing the given interface, the function believes it and the program flow continues.
    Personally, I don’t really like this method.
  3. Duck typing:
    This is where I have had my inspiration :) There is a line in the book, I'm always smiling when I'm reading it: "Duck typing was named after the saying, 'If it walks like a duck and quacks like a duck, it's a duck.' " :)
    Well, I was quite satisfied with this solution. However the example, that the authors were providing is only checking, if the object has the given methods defined.

My improved interface class:

So I decided to improve this Duck typing method, and add some additional functionality. With my interface class, instead of just checking the methods of the class (or object), we can:
  • check variable and method existence
  • check method’s parameters
  • check method return value
  • check variable value
  • define callback error handling function
  • and we can even define own interface functions, to validate the internal state of the object.
It's quite exciting :)

Here is a brief explanation, of how is this class used:
1.) Define an interface:
1
2
3
4
5
var exampleInterface = new Interface("example", { 
   /* requirements*/
}, {
   /* functions called on error */
})

2.) Create your object

3.) Validate the object's state: by passing the validated object and the interface objects as parameters
1
Interface.check( /* object to validate */, /* interface object */[...]);


Let's see some comment-explained code. First, here are some example interfaces defined:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// defined interfaces to implement
// checks all these variables. They have to be defined.
var InterfaceVariables = new Interface('InterfaceVariables', {

   // checks, if it is true
   isBooleanVal: true, 

   // just check the variable if it is defined
   isNullVal: null,

   // this string has to contain this value
   stringVal: 'required string value',

   // variable check with interface function. In this example, a variable has to be greater than one. It must always have a bool return.
   isGreaterThanOne: function(obj) { 
      return obj.isGreaterThanOne > 1;
   },

   // array check. here are other items not allowed
   allowJustThese: ['item1', 'item2', 'item3'],

   // array check. other items are allowed
   theseAreRequired_butOthersAllowedToo: ['item1', 'item2', 'item3', ".."],


}, {
   // this object contains the error handling functions

 
   // this runs only if the isBooleanVal is not true
   isBooleanVal: function (intf, obj) {alert("isBooleanVal is not true. Required interface: "+intf.name+".");},

   // and this runs only if the stringVal value is not the required one.
   stringVal: function (intf, obj) {alert("stringVal value is not valid. Required interface: "+intf.name+".");}

});

var InterfaceMethods = new Interface('InterfaceMethods', {

   // checks if method has only these two params. if others are also enabled, put ".." in the end
   // ret: boolean      checks method's return value. Params: (expected return [, arg1, arg2, ...] )
   methodParamCheckAndReturnCheck: {
      args: ['num1', 'num2'], 
      ret: [12, 2, 3] 
   },

   // i think this is clear
   // method must not have parameters
   // ret: true      expected return value is 6
 methodNoParamAndReturnsSix: { 
      args: [],
      ret: 6
   },

   // checks if this is a function plus the parameters
   checkMethodParams: ['arg1', 'arg2', '..'],

   // just check function existence
   isFunctionDefined: null,

   // no parameters allowed
   isFunctionWithNoParams: [] 

},{
   methodNoParamAndReturnsSix: function (intf, obj) { alert("Error with method: methodNoParamAndReturnsSix. Interface: "+intf.name+"."); }
});


For example in the first interface I defined only variable's, and in the second one the methods' rules. Staying at the example, here is a class which "implements" these interfaces:

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
30
31
32
33
// class that has to implement the interface
var classToImplementInterface = (function() {

   // this variable should be true, so the interface callback function will be called
   this.isBooleanVal = false;
   this.isNullVal = null;
   this.stringVal = "required string value";
   this.isGreaterThanOne = 2;

   var privateVar = 'private'; // just for fun... (a class' interface is its public variables and methods, if you wouldn't know that :) )
   var privateMultiplyer = 2;

   this.methodParamCheckAndReturnCheck = function(num1, num2) {
      return num1 * num2 * privateMultiplyer;
   }

   // this method should return 6. So this will generate an error.
   this.methodNoParamAndReturnsSix = function() {
      return 5;
   }

   this.allowJustThese = Array('item1', 'item2', 'item3');
   this.theseAreRequired_butOthersAllowedToo = Array('item1', 'item2', 'item3', "item4", "item5");

   this.checkMethodParams = function (arg1, arg2, arg3) {}

   this.isFunctionDefined = function ( can, have, params ) {}

   this.isFunctionWithNoParams = function () {} // must not have params


 
})


And here is, how you can evaluate the rules:

1
2
3
4
5
6
7
/////////////////////////////////////////////////////////////////////
//       CONCRETE OBJECT to IMPLEMENT INTERFACES
/////////////////////////////////////////////////////////////////////
var concreteObject = new classToImplementInterface();

// before doing something validate object's interface
Interface.check(concreteObject, InterfaceVariables, InterfaceMethods);


A concrete example:

Let's say, there is a car class. A method is called which is responsible for moving objects on the canvas, and the car object is passed in as a parameter. However, to move that car, there are some object requirements. For example, is someone driving it? Or is the door closed?. These can be solved with variable checking. Then…has the given car object all the methods which are used later on? Or is the body state higher than 10%? This can be solved with an anonym function. These requirements must be satisfied for error-free run.


Well that's it. I'm quite satisfied with the results. It's small and simple. Probably it will be used only in development phase, so I haven't done any speed optimization.

If you are interested in further development, please let me know!

Source files:

  • index.html
  • interface.js
  • interface.min.js
  • functions.js

Download:




I'm very enthusiastic and appreciate every donation. I will spend the full amount on books, to improve my skills and create more projects like this (and even better).

Thanks,
Phil



Changelog


  • 1.1:
    • Added callback functionality
    • Callback parameters: ( interface object, checked object )
  • 1.0:
    • variable and method existence
    • method's parameters
    • method return value
    • variable value
    • and we can even define own interface functions, to validate the internal state of the object.

No comments:

Post a Comment