1
0
Fork 0
Classical object-oriented framework for JavaScript [unmaintained] https://gnu.org/software/easejs
 
 
 
 
 
 
Go to file
Mike Gerwitz c10fc5818a Added very basic formatted output and failure tolerance for test case
The one year anniversary of the beginning of the ease.js project is quickly
approaching. I find myself to be not quite where I had expected many months ago,
but find that the project has evolved so much further than I had event
originally anticipated. My main motivation behind the project continues to be
making my life at work easier, while providing an excellent library that others
can hopefully benefit from. If anything, it's a fascinating experiment and
clever hack around JavaScript.

Now I find myself with a newborn child (nearly four weeks old), who demands my
constant attention (and indeed, it is difficult to find the desire to put my
attention elsewhere). Still - I am a hacker. Software is my passion. So the
project must move forward.

I also find myself unwilling to create a blog for ease.js. I feel it's
inappropriate for a project that's in its (relative) infancy and does not have
much popularity (it has never been announced to anyone). As such, I feel that
commit messages will serve my purpose as useful journal entries regarding the
status of the project. They will also be interesting easter eggs for those who
would wish to seek them out for additional perspective on the project. (Granted,
one could easy script the discovery of such entries by examining the absurd
length of the commit message...perhaps the git log manpages would be useful).

So. Let's get back to the project.

ease.js is currently going through a strong refactoring in order to address
design issues that have begun to creep up as the project grew. The initial
design was a very simple one - a "series of modules", as it was originally
described in a CommonJS sense, that would provide features of a classical
Object-Oriented system. It would seem ironic that, having a focus on
classical Object-Oriented development, one would avoid developing the project in
such a paradigm. Instead, I wished to keep the design simple (because the
project seemed simple), more natural to JS developers (prototypal) and
performant (object literals do not have the overhead of instantiation). Well,
unfortunately, the project scope has increased drastically due to the success of
the implementation (and my playfulness), the chosen paradigm has become awkward
in itself and the performance benefit is indeed a micro-optimization when
compared with the performance of both the rest of the system and the system that
will implement ease.js as a framework.

You can only put off refactoring for so long before the system begins to trip
over itself and stop being a pleasure to work with. In fact, it's a slap in the
face. You develop this intricate and beautiful system (speaking collectively and
generally, of course) and it begins to feel tainted. In order to prevent it from
developing into a ball of mud - a truly unmaintainable mess - the act of
refactoring is inevitable, especially if we want to ensure that the project
survives and is actively developed for any length of time.

In this case, the glaring problem is that each of the modules are terribly,
tightly coupled. This reduces the flexibility of the system and forces us to
resort to a system riddled with conditionals. This becomes increasingly apparent
when we need to provide slightly different implementations between environments
(e.g. ES5/pre-ES5, production/development, etc and every combination).
Therefore, we need to decouple the modules in order to take advantage of
composition in order to provide more flexible feature sets depending on
environment.

What does this mean?

We need to move from object literals for the modules to prototypes (class-like,
but remember that ease.js exists because JS does not have "classes"). A number
of other prototypes can be extracted from the existing modules and abstracted to
the point where they can be appropriately injected where necessary. Rather than
using conditions for features such as fallbacks, we can encapsulate the entire
system in a facade that contains the features relevant to that particular
environment. This will also have the consequence that we can once again test
individual units rather than systems.

At the point of this commit (this entry was written before any work was done),
the major hurdle is refactoring the test cases so that they do not depend on
fallback logic and instead simply test specific units and skip the test if the
unit (the prototype) is not supported by the environment (e.g. proxies in a
pre-ES5 environment). This will allow us to finish refactoring the fallback and
environment-specific logic. It will also allow us to cleanly specify a fallback
implementation (through composition) in an ES5 environment while keeping ES5
detection mechanisms separate.

The remaining refactorings will likely be progressive. This all stemmed out of
the desire to add the method hiding feature, whose implementation varies
depending on environment. I want to get back to developing that feature so I can
get the first release (v0.1.0) out. Refactoring can continue after that point.
This project needs a version number so it can be used reliably.
2011-10-12 18:53:52 -04:00
doc Merge branch 'master' into 'virtual/master' 2011-06-30 23:00:13 -04:00
lib Oops. Dummy want to name things correctly? (#25) 2011-09-02 22:44:11 -04:00
test Added very basic formatted output and failure tolerance for test case 2011-10-12 18:53:52 -04:00
tools Added very basic formatted output and failure tolerance for test case 2011-10-12 18:53:52 -04:00
.gitignore Added node_modules to .gitignore 2011-05-23 18:27:14 -04:00
LICENSE Changed license to LGPL 2010-11-10 22:07:03 -05:00
Makefile Merge branch 'master' into 'virtual/master' 2011-06-30 23:00:13 -04:00
README.hacking Added beginning of README.hacking 2011-09-02 22:24:16 -04:00
README.md Removed new virtual/master reference from README that was added in master branch 2011-09-21 18:24:15 -04:00
index.js Added exports for {Abstract,Final}Class 2011-05-23 20:00:34 -04:00
package.json Permitting newer versions of uglify-js 2011-09-20 23:14:46 -04:00

README.md

ease.js

ease.js is a collection of CommonJS modules intended to "ease" the transition into JavaScript from other Object-Oriented languages. It provides an intuitive means of achieving classical inheritance and has planned support traits/mixins.

Current support includes:

  • Simple and intuitive class definitions
  • Classical inheritance
  • Abstract classes and methods
  • Interfaces
  • Visibility (public, protected and private members)
  • Static and constant members

While the current focus of the project is Object-Oriented design, it is likely that ease.js will expand to other paradigms in the future.

This project is still under development. Please read the manual for more information.

Release/npm Note: The first release of ease.js (and subsequently, availability on npm) will come once the features are solidified and committed to. This is to ensure adoption is not stifled by design alterations. Version 0.1.0 release is near; please see the v0.1.0 roadmap for out-standing issues.

Full Documentation

Full documentation is available at the following URL:

http://easejs.org/manual/ (Multiple Pages)

http://easejs.org/manual.html (Single Page)

Bug Reports / Feature Requests

Please direct bug reports and feature requests to the bug tracker located at http://easejs.org/bugs/

Why ease.js?

There are already a number of libraries/frameworks that permit basic classical Object-Oriented development, so why ease.js? While many of the existing solutions certainly provide viable solutions, they are largely incomplete. Until the appearance of ECMAScript 5, many of the features enjoyed by classical OO developers were elusive to JavaScript. The aim of this project is to provide an intuitive framework in a CommonJS format which also addresses ES5 issues and is an all-inclusive solution to OO techniques.

ECMAScript reserves certain keywords that hint at classical OO in future versions, but said features are uncertain. ease.js will satisfy the classical OO itch until the time where ECMAScript itself includes it, at which time ease.js will still be useful for providing a transition in order to support older browsers. ease.js may also be useful in the future to augment the feature set of whatever native ECMAScript implementation is decided upon.

Why Classical OOP in JavaScript?

ease.js was created (historically) for a number of reasons:

  • To "ease" Object-Oriented developers into JavaScript by providing a familiar environment.
  • To provide the maintenance and development benefits of classical OOP.
  • To provide features missing from the language, such as proper encapsulation through private/protected members, interfaces, traits, intuitive inheritance, etc.
  • To encapsulate the hacks commonly used to perform the above tasks.

Many JS purists believe that classical Object-Oriented programming should be left out of the language and one should stick strictly to prototypal development. While the two are related (both Object-Oriented), they can be applied to different problem domains in order to achieve results that are more natural or intuitive to developers. ease.js works seamlessly with existing prototypes, allowing the developer to choose whether or not they want to use "classes".

How to Use

Please note that, as the project is under active development, the API may change until the first release.

ease.js uses the CommonJS module format. In the examples below, Node.js is used.

Defining Classes

The constructor is provided as the __construct() method (influenced by PHP).

    var Class = require( 'easejs' ).Class;

    // anonymous class definition
    var Dog = Class(
    {
        'private _name': '',

        'public __construct': function( name )
        {
            this._name = name;
        },

        'public bark': function()
        {
            console.log( 'Woof!' );
        },

        'public getName': function()
        {
            return this._name;
        }
    });

The above creates an anonymous class and stores it in the variable Dog. You have the option of naming class in order to provide more useful error messages and toString() output:

    var Dog = Class( 'Dog',
    {
        // ...
    });

Extending Classes

Classes may inherit from one-another. If the supertype was created using Class.extend(), a convenience extend() method has been added to it. Classes that were not created via Class.extend() can still be extended by passing it as the first argument to Class.extend().

Multiple inheritance is not supported. ease.js is very generous with the options it provides to developers as alternatives, so pick whichever flavor your are most comfortable with: interfaces, traits or mixins. Multiple inheritance will not be added in the future due to problems which have been addressed by interfaces and traits.

Note that traits and mixins are not yet available. They are planned features and will be available in the future.

    var SubFoo = Foo.extend(
    {
        'public anotherMethod': function()
        {
        },
    });

    // if Foo was not created via Class.extend(), this option may be used (has
    // the same effect as above, even if Foo was created using Class.extend())
    var SubFoo = Class.extend( Foo,
    {
        'public anotherMethod': function()
        {
        },
    });

Abstract Classes

Abstract classes require that their subtypes implement certain methods. They cannot be instantiated. Classes are considered to be abstract if they contain one or more abstract methods and are declared using AbstractClass rather than Class. If a class contains abstract methods but is not declared abstract, an error will result. Similarily, if a class is declared to be abstract and contains no abstract methods, an error will be thrown.

    var AbstractClass = require( 'easejs' ).AbstractClass;

    var AbstractFoo = AbstractClass(
    {
        // a function may be provided if you wish the subtypes to implement a
        // certain number of arguments
        'abstract public fooBar': [ 'arg' ],

        // alternatively, you needn't supply implementation details
        'abstract public fooBar2': [],
    });

If the abstract method provides implementation details (as shown by fooBar(), subtypes must implement at least that many arguments or an exception will be thrown. This ensures consistency between supertypes and their subtypes.

Abstract classes can be extended from just as an other class. In order for its subtype to be instantiated, it must provide concrete implementations of each abstract method. If any methods are left as abstract, then the subtype too will be considered abstract and must be declared as such.

    // can be instantiated because concrete methods are supplied for both
    // abstract methods
    var ConcreteFoo = Class.extend( AbstractFoo,
    {
        'public fooBar': function( arg )
        {
        },

        'public fooBar2': function()
        {
        },
    });

    // cannot be instantiated because one abstract method remains
    var StillAbstractFoo = AbstractClass.extend( AbstractFoo,
    {
        'public fooBar': function( arg )
        {
        },
    });

Interfaces

Interfaces can be declared in a very similar manner to classes. All members of an interface must be declared as abstract.

    var MyType = Interface(
    {
        'abstract public foo': []
    });

To implement an interface, use the implement() class method:

    var ConcreteType = Class.implement( MyType ).extend(
    {
        'public foo': function() {}
    });

Note that, if a concrete implementation for each method is not provided, the implementing type must be declared abstract.

Use of Reserved Words

Though JavaScript doesn't currently implement classes, interfaces, etc, it does reserve the keywords. In an effort to ensure that ease.js will not clash, the following precautions are taken:

  • Class is used with a capital 'C'
  • Interface is used with a capital 'I'
  • Reserved keywords are quoted when used (e.g. in property strings)