Sunday, December 14, 2014

Object Oriented JavaScript - Part 2

Last time, I began with a rant. Well what did you expect? This is a blog about things bugging me, after all. On the hit list today is ECMAScript 6 (ES6). Why? Well, it seems they're finally getting around to implementing the "class" keyword. But before you jump for joy, it's just a thin shell over prototype inheritance. You don't get anything for it save for some syntactic sugar. All the real goodies of encapsulation are still no where to be found; still no private scopes, still no protected scopes.

I went on a search for Class.js on Google. I found six different versions, none of which was mine. That's to be expected though. I only released my version a few days ago. As for the other 6... well.... They all pretty much do the same thing, and still we get no real encapsulation. For me, this only further resolves my determination to complete all possible features of my Class.js library. Now, let's get down to the goods.

Class.js Usage

Class.js is built to be CommonJS compatible. However, it's also usable as a classic script tag include file. All you need to do is either require("Class") in your CommonJS environment or include script tags for the 4 library files, being sure to place Class.js last. After that, you're off to the races using full, Java-like class declarations in your code.

The Helper Functions

The keys to the syntax for using Class.js are the 6 helper functions that are members of the Class constructor function:
  • Class.Private
  • Class.Protected
  • Class.Public
  • Class.Static
  • Class.Final
  • Class.Property

When I use Class.js, I assign these 6 functions to local variables of the same name. That way, I don't have keep retyping "Class." on every member of the new class.

Example:

var Class = require('Class');
var Private = Class.Private;
var Protected = Class.Protected;
var Public = Class.Public;
var Static = Class.Static;
var Final = Class.Final;
var Property = Class.Property;

These 6 functions reproduce the corresponding scope and attribute keywords in Java. Java isn't exactly my favorite language, but given JS's original billing as Java's little brother.... Each of these functions has exactly the same declaration:

Class.??? = function(val) { ... };

These function are designed to take a single value that is either the value being assigned, or the return value from one of the other 5 functions. This allows multiple helpers to be used on a single member object. In this way, the helper functions act as wrappers to specify the scope of the object that was wrapped.

The ClassConstructor

The constructor has 2 parameters:
  • an optional first parameter for specifying the class name. If left blank, the class is an anonymous class.
  • a class definition object. This is where you will need the helper functions.

The Class Definition Object

This is where you define the actual layout of the class. What goes in here is not much different than what goes in to an equivalent Java class... save for the syntax of course. Here's an example class in Java, followed by the same class using Class.js.

Java Example:

class Book {
 private String bookType;
 private String name;
 private float size;
 private boolean onShelf;
 
 public Book(String name, float size) {
  this.name = name;
  this.size = size;
  this.bookType = "Book";
  this.onShelf = false;
 }
 
 protected void setBookType(String bookType) {
  this.bookType = bookType;
 }
 
 public void returnToShelf(Bookshelf bookshelf) {
  int shelf = bookshelf.shelfForBook(name);
  if (shelf >= 0) {
   List books = Array.asList(bookshelf.getBooksOnShelf(shelf));
   int index = books.indexOf(this);
   
   if (index >= 0)
    onShelf = true;
  }
 }

 public String getName() { return name; }
 public float getSize() { return size; }
 public String getBookType() { return bookType; }
}

Class.js:

var Book = new Class("Book", {
 _bookType: Private(),
 _name: Private(),
 _size: Private(0),
 _onShelf: Private(false),
 
 Constructor: Public(function Book(name, size) {
  this._name = name;
  this._size = size;
  this._bookType = "Book";
  this._onShelf = false;
 }),
 
 setBookType: Protected(function setBookType(bookType) {
  this._bookType = bookType;
 }),
 
 returnToShelf: Public(function returnToShelf(bookshelf) {
  var shelf = bookshelf.shelfForBook(this._name);

  if (shelf >= 0) {
   var books = bookshelf.getBooksOnShelf(shelf);
   int index = books.indexOf(this.Self);
   
   if (index >= 0)
    this._onShelf = true;
  }
 }),

 name: Public(Property({
  get: function() { return this._name; }
 })),
 size: Public(Property({
  get: function() { return this._size; }
 })),
 bookType: Public(Property({
  get: function() { return this._bookType; }
 }))
});

Save for the replacement of getXXX in the Java version with Properties in the JS version, these two classes are functionally equivalent. The privilege of every member of class Book is declared by wrapping its initializer using one of the appropriate helper functions. Notice that helper functions can be nested as well, as is the case when declaring properties. The only exceptions to this nesting capability are as follows:

  • Only 1 of Public, Private, and Protected can appear in a member declaration.
  • Protected and Static cannot be used together. Why? Consider this: a protected member is an inheritable member of the class and therefore part of the prototype (well, sort of...) while a static member exists on the class itself and is not at all part of the prototype. Where could I put that and keep both semantics?
  • Property and Final cannot be used together. Why? Properties are calculated values. Final values are constant. Who ever heard of a recalculated constant in programming?
  • Private, Static, and Property cannot be used together. Why? Any two of those together is ok, but when all three get together you wind up with a calculated value that is accessible on the class, but only to instances of that class. In essence, the use cases for this are identical to the use cases for private properties. Static becomes redundant, so there's no good reason I could come up with to allow it.
Feel free to disagree with me on any of the bottom 3 cases. If someone manages to convince me I'm wrong, I'll gladly change it.

Properties are internally set up using Object.defineProperty(), but there's a catch. Only the get and set elements of a property description are of any real use. The value element can be used, but the same result can be achieved without needing to use a property. The enumerable, configurable are hard coded to true and false, respectively. The writable flag is set automatically based on the helpers applied to the member.

Did you happen to notice that every reference to a member was preceded by the this keyword? That's just the nature of JS. Object members can only be accessed through their scopes. At the same time, did you notice that the use of the "this" keyword within the class always refers to the instance of that class? One of my goals was to make programming using Class.js as close to classical object oriented programming as possible without changing the language.

The constructor for any Class definition is always declared with the Constructor keyword. It doesn't have to be there, but beware! If you inherit from a class that has a Constructor, you need to include a Constructor as well! Remember, I implemented Java-style object inheritance. In Java, a subclass must call super() in its constructor before the class can be considered initialized. We'll get into more of the inheritance details in part 3.

1 comment:

  1. King, sorry to bother you hear but please get in touch with me at andrew.w.royce@gmail.com. Thanks!

    ReplyDelete