Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Alternative Flex DI Framework

tree: 6381265c87

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 doc
Octocat-spinner-32 source
Octocat-spinner-32 test
Octocat-spinner-32 .gitignore
Octocat-spinner-32 README.mdown
README.mdown

Introduction to Zero

Zero is a new way to approach Dependency Injection in Actionscript. Instead of using interfaces, you create lightweight objects that work as a gateway to the rest of the system. Zero allows you to attach functionality to interfaces with ultimate flexibility.

Please also see the FAQ

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

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

Flexible - N:N mapping of implementations to interfaces. 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.

Getting Started

Visit Downloads and get the latest swc.

Contribute

Zero is open source. Please hack on it

http://github.com/seanhess/zero

DependencyInterfaces

Instead of a normal AS Interface, you create a simple class that defines an interface. Here is how to define a function.

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

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

Here is a full example. Please see the faq for information about the context.

package dependency 
{
    [Event(name="newBook", type="flash.events.Event")]
    public class DLibrary extends DependencyInterface
    {
        /**
         * Passing the context to the constructor is required
         */
        public function DLibrary(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) }

Using Dependencies

You use the new operator anywhere as if you were creating the class. Zero takes care of the rest. You should always pass in the context (a reference to the object creating it) when creating a DependencyInterface.

var library:DLibrary = new DLibrary(this); 

Don't worry, you are not creating a dependency on the implementation. Think of the DependencyInterface as a normal AS Interface. DependencyInterfaces are easy to refactor to Interfaces and vice versa.

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

<dependency:DLibrary id="library"/>

Implementations

There are no constraints on implementations at all. You don't event have to implement all the functions.

package service
{
    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 
        {
            books.addItem(title);
            trace("Added " + title + " to library");
            dispatchEvent(new Event());
        }
    }
}

Connecting Implementations

You can easily connect an implementation to a DependencyInterface using ActionScript.

import dependency.DLibrary;
import service.Library;

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

You can do it in MXML as well

<zero:Connect>
    <zero:Implement dependency="{DLibrary}" factory="{Library}"/>
</zero:Connect>

Multiple Implementations

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

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 interface. Later implementations have priority.

var connect:Connect = new Connect(this);
connect.add(new Implement(DLibrary, Books));
connect.add(new Implement(DLibrary, BookCreator)); 

or in MXML

<zero:Connect>
    <zero:Implement dependency="{DLibrary}" factory="{Books}"/>
    <zero:Implement dependency="{DLibrary}" 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 DBook extends DependencyInterface
{
    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

connect.add(new Factory(DBook, Book));

or in mxml

<zero:Connect>
    <zero:Factory dependency="{DLibrary}" factory="{BookCreator}"/>
</zero:Connect>

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

var book:DBook = new DBook(this);

or in mxml

<dependency:Book title="here is a title"/>

Proxying

You can use a Proxy to extend existing functionality. If you only need to override functionality, use multiple implementations instead.

public class LibraryProxy extends ObjectProxy
{
    public function addBook(title:String):void
    {
        trace("Do something cool");
        object.addBook(title);
        trace("Do something even cooler!");
    }
}

They are connected similarly

var connect:Connect = new Connect(this);
connect.add(new Implement(DLibrary, Library));
connect.add(new Proxy(DLibrary, LibraryProxy));

DependencyInterfaces in Implementations

Any class can ask for a dependency, including the implementations you specify for other interfaces. Here's our Library example revisited, using the DBook 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:DBook = new DBook(this);
            book.title = title;

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

Disconnecting an Interface

You can disconnect an interface by calling its disconnect method.

library.disconnect();

And reconnect it with connect

library.connect();

Disconnecting a View

You can disconnect an entire context

var connect:Connect = new Connect(this);

// disconnects everything inside this context
connect.disconnect(); 

// disconnects some other context
connect.disconnect(someOtherObject);

// disconnects every context that matches the pattern
connect.disconnect("view.*");

Implementation by View

You can specify a different implementation for everything within a particular view by putting new connect instructions in it.

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

<!-- SomeOtherView.mxml -->
<zero:Connect>
    <Implement dependency="{DLibrary}" 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(DLibrary, 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)

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

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

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

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

Testing and Mocking

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

var library:DLibrary = new DLibrary(); // I left out the context!
library.implementation = new MockLibrary();

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.

var connect:Connect = new Connect(this);
connect.wipe();
// add pristine implementations

More Information

Glossarygg

Use Cases

[Questions][questions]

Something went wrong with that request. Please try again.