Species

Java style OOP, and AOP ability, watchable members, inheritance and ability to call super members.

Website:https://github.com/k33g/species

#Species

##JS Class structure, Java style OOP

Only for Webkit and FF (IE9 ?)

  • no side effect with variable scope
  • able to call super(parent) constructor even with several Class generations
  • able to call parent method in overridden method
  • Classes (aka "Species") are serializable, member are watchable (if you want)
  • AOP is possible
  • ability to write plugins
  • inheritance and ability to call super members
  • static members

##Usage

###Define Class

  • The keyword is Species.Class({ /* class definition */})
  • To instantiate a class : class_name.New(parameters)

####Example

var Human = Species.Class({
    /*--- Default Values ---*/

    FirstName : 'John',
    LastName : 'Doe',
    NickName : 'Johnny',

    /*--- Constructor ---*/
    // Convention : if you want a typed instance, you have to name wit underscore + variable name
    initialize : function _Human() {

        console.log('Human constructor');

        //Member without default value
        this.Remark = 'HELLO';

    },

    sayHello : function() {
        console.log('Hello, i am ' + this.FirstName + ' ' + this.LastName);
    }

    /*--- define static members ---*/
    Static : {
        x : function(v) { console.log('x',v); },
        y : function(v) { console.log('y',v); },
        count : 12
    }

});

/*--- get Human instance ---*/

var Bob = Human.New();

console.log(Bob.isInstance, Bob.isInstanceOf(Human), Bob.typeName);
/* -> displays : true, true, "Human" */

Remark : If you name the constructor like this initialize : function _Human(), you obtain a typed instances :

  • Bob.typeName return Human
  • Bob.isInstanceOf(Human) return true

###Serialization (json)

You can do that :

var s_bob = Species.serialize(Bob);
var  OtherBob = Species.deSerialize({from : s_bob, to : Human.New()});

###Inheritance

You have just to create a property Extends :

var Animal = Species.Class({
    Name : '???',
    initialize : function(args) {
        console.log('Animal constructor');
        this.Name = args.Name;
    }
});

var Dog = Species.Class({
    Extends : Animal,
    kind : '?',
    initialize : function(args) {
        console.log('Dog constructor');
        this.kind = args.kind;
        Dog.parent.initialize.call(this,args);
    }
});

var LittleDog = Species.Class({
    Extends : Dog,
    initialize : function(args) {
        console.log('LittleDog constructor');
        LittleDog.parent.initialize.call(this,args);
    }
});

Remark : to call parent method, use this : class_name.parent.initialize.call(this, args);

###Simple AOP

Species allows you to add method before and after an other method of a class or of an class instance :

####Species AOP methods

  • Species.aop.before(class_or_instance, 'name_of_method_as_string', function)
  • Species.aop.after(class_or_instance, 'name_of_method_as_string', function)

####Example :

var Human = Species.Class({
    Name : 'John Doe',
    walk : function() {
        console.log(this.Name + ' is walking');
    },
    initialize : function _Human(name) {
        if(name) this.Name = name;
    }
});

Species.aop.before(Human, 'walk', function() { console.log(this.Name + ' starts walking'); })

var Bob = Human.New();
Bob.walk();

var Sam = Human.New('Sam');
Species.aop.after(Sam, 'walk', function() { console.log(this.Name + ' stops walking'); })

Sam.walk();

###Watchable members

You can watch change of value of member of an instance (or all instances) of a class :

####Methods

  • Species.watch(class_or_instance, 'name_of_member_as_string', handler_function)
  • Species.unwatch(class_or_instance, 'name_of_member_as_string')

The handler function is something like that function(arg) { /* do something */ }, where arg is an object you can read to obtain old and new values of the watchable member (see example) :

arg : {
    propertyName
    oldValue
    newValue
}

####Example :

var Human = Species.Class({
    Name : 'John Doe',
    walk : function() {
        console.log(this.Name + ' is walking');
    },
    initialize : function _Human(name) {
        if(name) this.Name = name;
    }
});

Species.watch(Human,'Name', function(result) {
    console.log( 'Property : ' + result.propertyName + ' Old : ' + result.oldValue + ' New : ' + result.newValue);
});

var Bob = Human.New();

Bob.Name = 'Bob Morane';

var Sam = Human.New('Sam');

Species.watch(Sam, 'Name', function(result) {
    console.log( 'Only for Sam : Property : ' + result.propertyName + ' Old : ' + result.oldValue + ' New : ' + result.newValue);
});
Sam.Name = 'Sammy'

##More classical OOP

With Species, you can thinking like in Java (or C#), and almost reproduce your classical OOP mind logic.

###Pattern examples :

####Singleton :

var Singleton = Species.Class({
    Instantiated : false,
    Instance : {},
    Name : '',

    getInstance : function(name) {
        if(!Singleton.Instantiated) {
            Singleton.Instantiated = true;
            Singleton.Instance = Singleton.New(name);
        } else {
            console.log('already instantiated');
        }
        return Singleton.Instance;
    },

    initialize : function(name) {
        this.Name = name;
    }
});

####Factory :

var Duck = Species.Class({
    Kind : 'Duck',
    initialize : function() {
        console.log('I am a ' + this.Kind);
    }
});

var RealDuck = Species.Class({
    Extends : Duck,
    Kind : 'RealDuck'
});

var PlasticDuck = Species.Class({
    Extends : Duck,
    Kind : 'PlasticDuck'
});

var DucksFactory = Species.Class({
    getInstance : function(choice) {
        switch(choice) {
            case 'D' : return Duck.New(); break;
            case 'R' : return RealDuck.New(); break;
            case 'P' : return PlasticDuck.New(); break;
            default : return Duck.New(); break;
        }
    }
});

####Proxy :

var Human = Species.Class({
    Name : '',
    walking : function() {
        console.log(this.Name + ' is walking');
    },

    initialize : function(name) {
        this.Name = name;
    }
});

var HumanProxy = Species.Class({
    human : {},
    walking : function(){
        console.log('before walking ...');
        this.human.walking();
        console.log('after walking ...');
    },

    initialize : function(human) {
        this.human = human;
    }
});

var Bob = HumanProxy.New(Human.New('Bob'));
Bob.walking();

var Sam = HumanProxy.New(Human.New('Sam'));

Sam.walking();

####Decorator :

var Car = Species.Class({
    price : 0,
    Price : function(arg){
        return arg === undefined ? this.price : this.price = arg;
    }
});

var AstonMartin = Species.Class({
    Extends : Car,
    initialize : function() {
        this.Price(1000000);
        console.log("Aston Martin base price : " + this.Price());
    }
});


var Option = Species.Class({
    Extends : Car,
    originalCar : {},
    optionPrice : {},
    initialize : function(originalCar, optionPrice){
        this.originalCar = originalCar;
        this.optionPrice = optionPrice;
    },

    Price : function(){
        return this.originalCar.Price() + this.optionPrice;
    }

});

var AirConditioning = Species.Class({
    Extends : Option,
    initialize : function(car) {
        AirConditioning.parent.initialize.call(this,car,5000);
        console.log("AirConditioning : 5000");
    }
});

var Parachute = Species.Class({
    Extends : Option,
    initialize : function(car) {
        Parachute.parent.initialize.call(this,car,55000);
        console.log("Parachute : 55000");
    }
});

var Amphibious = Species.Class({
    Extends : Option,
    initialize : function(car) {
        Amphibious.parent.initialize.call(this,car,555000);
        console.log("Amphibious : 555000");
    }
});


var astonMartin = AstonMartin.New();
astonMartin = AirConditioning.New(astonMartin);
astonMartin = Parachute.New(astonMartin);
astonMartin = Amphibious.New(astonMartin);

console.log("Aston Martin total price : " + astonMartin.Price());

####Etc. ...

##One More Thing : plugins

It's very easy to add functionalities to Species :

var Species = (function (species) {

    species.something = function(arg) { /*doing something*/ }

    return species;
}(Species));

##License

Species is available under the terms of the MIT-License.

Copyright 2011, Philippe Charrière


http://closure-compiler.appspot.com/home

// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
// @output_file_name species.min.js
// @code_url https://raw.github.com/k33g/species/master/js/species.js
// ==/ClosureCompiler==