Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Alternative Flex DI Framework
ActionScript
branch: master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
doc
examples/CafeTownsend/src
source/src
test/dev/src
.gitignore
README.mdown

README.mdown

Introduction to Zero

Zero is a new way to approach managing dependencies in Actionscript (Inversion of Control). Instead of using interfaces, you create a simple facade without any functionality. Zero allows you to attach functionality to these psuedo-interfaces with ultimate flexibility.

Zero is not an MVC framework. It is a set of tools that would work with any pattern, including MVC. See the FAQ for more information.

Why Zero?

After reading through the entire introduction, visit the Use Cases Page to see how Zero solves these problems.

  • You want to be able to access the same data (state) in several places in your application without passing references around, but have read that global variables and singletons are bad.

  • You thought your code was decoupled. But when it comes time to refactor you have to change your application in many places. This is so annoying you start to put it off.

  • You have used an MVC framework and realized that views continue responding to state changes even when hidden or unloaded.

  • You want to add or change functionality of a class (or several classes) without that class needing to know about the change. For example: adding logging or translating the result of a function from xml to another object. Extending that class works well for the first change, but by the time you've added the same 3 independent changes to 12 classes, you're starting to go crazy.

  • You want to be able to add and remove functionality for a class at runtime. For example, turning logging on or off, or switching backends.

  • You realize that you need to change server technology from web services (HTTPService) to an AMF server (RemoteObject). However, the way you have to interact with those services is completely different.

  • You spend more time setting up and debugging your framework's configuration code than you do anything else.

  • Your server requires you to pass it objects with a very specific format. Your views would be much easier to code if they were different, but it's just too hard to translate between different kinds of objects all the time.

  • You need to start creating some complicated views before you even know how the server is going to work.

  • Your app is becoming so large that your once clean-and-logical MVC boilerplate and configuration code is becoming cluttered and unwieldy. It would be nice if there was some way to decentralize it.

  • You're working on a problem that is easily solved by a certain pattern, but your MVC framework makes it really hard to do it that way.

  • The Architect has selected an MVC framework, but junior members on the team aren't getting it. They code up good solutions to problems, but the code requires massive refactoring to make it fit into the paradigm of your framework.

  • You need to work on a view in a huge project, but it takes a full minute to compile, and another minute to drill down to it. You want to tweak the colors and keep looking at it afterward, but it takes so long to get there you forget what you were working on.

  • You need to write a class containing application (domain) logic. You don't yet know what it's interface will be, so you can't create unit tests yet, but you want to try it out while you develop without needing to plug it in to the rest of the application.

  • You've looked in to MVC frameworks, but the learning curve seems too steep. You'd rather spend time finishing your application and mastering Actionscript than figuring out somebody's domain-specific language.

Project Goals

Flexible - N:N mapping of implementations to facades. Easy to modify functionality with proxies. Simple factories for short-term objects. Simple testing and mocking. Ability to connect and disconnect pieces from the system. Runtime configuration.

Intuitive - Stay out of the way, look like normal code, and let the user go. No appreciable constraints on implementations. Strongly-typed configuration. Completely Testable.

Infinitely Scalable - Be performant and decentralized. Interfaces can be specified to depend on the context of the object asking for an implementation.

Getting Started

Note: The framework is only half done. You can't use it yet.

Visit Downloads and get the latest swc.

Contribute

Zero is open source. Please hack on it

http://github.com/seanhess/zero

Facades

When a class needs to use another class (creating a dependency), create a facade describing what the first class needs. This is very similar to creating an interface in Actionscript. For an example of how it looks, here is how you define a function. It looks just like an interface, except you use the public keyword, and pass the function on to "i"

public function doSomething(param:String):Boolean { return i.doSomething(param) }

You can also define getters and setters

[Bindable]
public function set name(value:String):void     { i.name = value }
public function get name():String               { return i.name }

You can specify events by putting an [Event] tag on the class, something impossible to do with an interface.

[Event(name="anEvent", type="flash.events.Event")]

Here is a full example. I use the naming convention of putting "I" before the name because it acts like an interface. You can always replace it with an actual interface if you change your mind. Please see the faq for information about what context means.

// dependency/ILibrary.as
package dependency 
{
    [Event(name="newBook", type="flash.events.Event")]
    public class ILibrary extends Facade
    {
        /**
         * Passing the context to the constructor is required
         */
        public function ILibrary(context:*)         { super(context) }

        public function get books():IList           { return i.books }

        public function addBook(title:String):void  { i.addBook(title) }
    }
}

You can use arguments to simplify passing the arguments along

public function manyArguments(one:SomeClass, two:String, three:int):void { i.manyArguments(arguments) }

What's the point? Why not use interfaces?

Doing it this way gives us all the benefits of interfaces (Strong typing, coding to an interface), while allowing us to do more than the type system would (pass it a proxy that "pretends" to implement the interface). See FAQ for a more detailed answer.

Using Facades

Now that you've created a facade, you can use it anywhere in your code with the new operator. Zero then goes out and finds the right object(s) for your facade to work with. The code using new doesn't know which, and it doesn't care. Pass in this when creating it so Zero knows who is asking for it. This is the Context.

var library:ILibrary = new ILibrary(this); 

You can create them in MXML as well. The context is set automatically in mxml.

<dependency:ILibrary id="library"/>

This may give some of you shivers up and down your spine, but the code is easier to read, it's mxml compatible, doesn't require any meta-data wiring, and still provides the same benefits as passing dependencies in by hand.

Implementations

Ok, now let's write a class to give to this facade. It can look any way we want. You don't event have to implement all the functions. We don't implement an interface, because the class isn't required to implement all the functions of the facade by itself. Zero's debugger will catch any mismatched functions.

package service
{
    public class Library
    {
        /**
         * books is read-only because the interface only defines
         * a getter
         */
        public var books:IList = new ArrayCollection();

        public function addBook(title:String):void 
        {
            books.addItem(title);
            trace("Added " + title + " to library");
            dispatchEvent(new Event());
        }
    }
}

Wiring

You can easily connect an implementation to a Facade using ActionScript or MXML. If you want an implementation to apply to your entire application, put this code in your main app.

// MyApplication.mxml
import dependency.ILibrary;
import service.Library;

var connect:Connect = new Connect(this);
connect.add(new Implement(ILibrary, Library));

You can do it in MXML as well

<!-- MyApplication.mxml -->
<zero:Connect>
    <zero:Implement dependency="{ILibrary}" factory="{Library}"/>
</zero:Connect>

Multiple Implementations

A single class doesn't need to be responsible for the whole Facade.

package service
{
    public class Books
    {
        public var books:IList;
    }
}

package service
{
    public class BookCreator
    {
        public function addBook(title:String):void
        {
            trace("Do something different");
        }
    }
}

You can then connect both of them to the facade. Later implementations have priority.

// MyApplication.mxml
var connect:Connect = new Connect(this);
connect.add(new Implement(ILibrary, Books));
connect.add(new Implement(ILibrary, BookCreator)); 

or in MXML

// MyApplication.mxml
<zero:Connect>
    <zero:Implement dependency="{ILibrary}" factory="{Books}"/>
    <zero:Implement dependency="{ILibrary}" factory="{BookCreator}"/>
</zero:Connect>

Factories

You can define factories for short-term objects, like value objects or models. Instead of creating a single copy of the implementation, it creates a new one every time it is requested.

// interface
public class IBook extends Facade
{
    public function get title():String           { return i.title }
    public function set title(value:String):void { i.title = value }
}

// implementation
public class Book
{
    public var title:String
}

And you tell Zero to connect them like this

// MyApplication.mxml
connect.add(new Factory(IBook, Book));

or in mxml

<!-- MyApplication.mxml -->
<zero:Connect>
    <zero:Factory dependency="{IBook}" factory="{Book}"/>
</zero:Connect>

You can then use the new operator, like normal, but Zero creates a new instance every time.

// inside of some other class, like services/Library.as
var book:IBook = new IBook(this);

or in mxml

<!-- inside a view, like a form? -->
<dependency:IBook title="here is a title"/>

Proxying

So far, everything we've done could be easily accomplished with a good DI framework. But proxying is where Zero really starts to shine. You can use a Proxy to extend existing functionality using the decorator pattern, without needing to implement all the methods.

Even cooler, the proxy does not have to be aware of what is actually implementing ILibrary. You don't have to reference another class anywhere.

public class LibraryProxy extends ObjectProxy
{
    public function addBook(title:String):void
    {
        trace("Do something cool");
        object.addBook(title); // I have no idea what object is, here.
        trace("Do something even cooler!");
    }
}

They are connected similarly

// MyApplication.mxml
var connect:Connect = new Connect(this);
connect.add(new Implement(ILibrary, Library));
connect.add(new Proxy(ILibrary, LibraryProxy));

Facades in Implementations

Any class can ask for a facade, including an implementation. Here's our Library example revisited, using the IBook factory:

public class Library
{
    /**
     * books is immutable because the interface only defines
     * a getter
     */
    public var books:IList = new ArrayCollection();

    public function addBook(title:String):void 
    {
        var book:IBook = new IBook(this);
            book.title = title;

        books.addItem(book);
        trace("Added " + title + " to library");
    }
}

Mocking

You can create a mock implementation that implements some or all of the methods of the class. You can switch implementations by changing the value of the implement object, or by commenting one or the other out.

// mock/MockLibrary.as
public class MockLibrary
{
    public var books:IList;

    public function addBook(title:String):void
    {
        trace("I'm pretending to add a book");
    }
}



// MyApplication.mxml
var imp:Implement = new Implement(ILibrary, MockLibrary);
var connect:Connect = new Connect(this);
connect.add(imp);

// now, you can switch back to the real version by doing
imp.factory = Library;

Disconnecting an Interface

You can disconnect an interface by calling its stop method.

library.stop();

And reconnect it with start

library.start();

Or permanently stop it by setting its implementation to null

library.implementation = null;

Disconnecting a View

You can disconnect an entire context

// disconnect everything inside of the current view or object
var connect:Connect = new Connect(this);
connect.stop(); 

// disconnects some other object you have a handle on
var connect:Connect = new Connect(subView);
connect.stop();

// disconnects every context that matches the pattern
var connect:Connect = new Connect("view.*");
connect.stop();

Implementation by View

You can specify a different implementation for everything within a particular view by putting new connect instructions in it. In this example, every view inside of SomeOtherView will get ADifferentLibrary, while the rest of the application will get Library.

<!-- MyApplication.mxml -->
<zero:Connect>
    <Implement dependency="{ILibrary}" factory="{Library}"/>
</zero:Connect>

<!-- SomeOtherView.mxml -->
<zero:Connect>
    <Implement dependency="{ILibrary}" factory="{ADifferentLibrary}"/>
</zero:Connect>

Implementation by Other Context

The same applies for any context, actually, just define the connect instance with the other class as the context.

var connect:Connect = new Connect(someOtherObject);
    connect.add(...);

Implementation by Class

You can pass a class into Connect instead of a reference

import view.SomeView;

var connect:Connect = new Connect(SomeView);
    connect.add(...);

Implementation by Package

You can pass in a string with wildcards too.

var connect:Connect = new Connect("view.admin.*");
    connect.add(...);

Remove Implementations

You can use Connect's remove function to remove an implementation instruction

var lib:Implement = new Implement(ILibrary, Library);

var connect:Connect = new Connect(this);
    connect.add(lib);
    connect.remove(lib); 

Updating Implementations

Remember, you can override an implementation simply by adding a new one (Later implementations have higher priority)

Instead, you could remove an instruction and add a new one, or you can simply update the properties of an existing one

var lib:Implement = new Implement(ILibrary, Library);

var connect:Connect = new Connect(this);
    connect.add(lib);

    lib.factory = AnotherLibrary; // updates all interfaces connected to it. 

Testing

Dependencies will not connect unless you specify a context. You can deliberately skip that step, and pass in your own.

var library:ILibrary = new ILibrary(); // I left out the context!
library.implementation = new MockLibrary(); // it will use mine!

You can also connect an implementation by package or class as described above. You can wipe out all information in the register. This makes it easy to ensure there is no stale information in the register.

// TestLibrary.as -> setUp()
var connect:Connect = new Connect(this);
connect.wipe();
connect.add(new Implementation(ILibrary, MockLibrary);

More Information

FAQ

Use Cases

Examples

Something went wrong with that request. Please try again.