Skip to content

Commit

Permalink
comments
Browse files Browse the repository at this point in the history
  • Loading branch information
seanhess committed Oct 19, 2009
1 parent 3e17932 commit 959ef16
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 48 deletions.
Binary file added .README.mdown.swp
Binary file not shown.
37 changes: 36 additions & 1 deletion README.mdown
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
# Zero

Secret Dependency Injection Framework for Actionscript
Dependency Injection Framework for Actionscript

## Goals

1. To allow classes to "implement" interfaces in pieces
2. To make overriding by decoration simple
3. Make it easy to create short-term objects (Injecting factories)
4. Testing and Local Mocking should be easy
5. To make it configurable at runtime, including the ability to disconnect
6. To allow different implementations based on context
7. Beautiful syntax in both MXML and AS
8. Easy to override functionality of the core

## Use Cases

#### When testing, pass in mock implementations to a class

Switch to a mock server connection at runtime (a checkbox)

4 different views of a list of users. Invisible ones are inactive and don't receive updates

When a module is unloaded it is deactivated and GC'd

Give a particular view a unique implementation

Give a particular class a unique implementation

Give a set of views/classes a unique implementation

Proxy all service requests, to convert xml data to strongly typed data before returning

Log all requests in the system without requiring the implementations to know about it




190 changes: 190 additions & 0 deletions ideas.mdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
## Contexts without requirements

I have already seen that passing a context from one node to another is pretty easy. Not bad. The problem is waiting for an actual context object to get there.

So, if I continue to do it the way I am now, but then maintain a map of real context objects in the core, based on the original object.

contexts[owner] = new Context();

So, when a class is created by the framework, we create a context for it, mapped to its parent context (which is defined by the class the mapping is defined in.

This is awesome!

<Connect> or new Connect(this);

So, we don't have to grab the context from the current parent. Just have everything keep track of its owner (context) and we can recreate a tree of connectedness that way. Sweetness... The only thing is we need a way to enable communication between them.

Disconnecting everything in a particular context. VIEW - create a context mapping for each item in the view? Meh...

Well, but I can do something here... to create that tree I wanted :) The dictionary maintains a reference to.... umm..... well, whoever the parent was???

How would I use this?

Making everything in a particular view inactive -

1) I could go back to proxying EVERY request through the context chain

2) I could send a disconnect to everyone within that context

DependencyInterface doesn't have to change either way. You can just put the logic in Implementation instead. If it needs a context, give it one :)

Which is faster... proxy chains, or event chains? I'm guessing proxies?

A) send through the chain - this option makes it easier to change things at runtime. It makes it harder to set up binding (not if we use a proxy chain instead).

Proxy chains aren't that hard to set up either. ..

B) Just sent seek/update/disconnect commands through the chain

C) Connect directly, but have logic that detects the chain. For example, implementations are always given by context. When you seek, it tries to locate the context where that is stored.


Implementation
- created by the system.
- associated with a given context

Context
- implementations in it, or associated with it, or something
- parent context
- hasImplementationFor?

Interface - seek (context, type)
- Calls Register.seek(context, type)
- context = contexts[context]; (which happens to be a particular implementation)
- we know the parent


MOCK SITUATION - Library

We have a library database. The views are as follows

MainView
SearchView
ResultsView - (Book, Library)
BookView - (Book, Library)
EditView - (Book, Library)
AddView - (Book, Library)

DependencyInterfaces

LibraryService (Book)
- search(term:String):IList
- addBook(book:Book):void
- updateBook(book:Book):void
Book
- title:String
- details:String;

Library (Book, LibraryService)
- results:IList;
- currentBook:Book;
- currentPage:String;

- search(term:String)
- goToSearch()
- goToEdit(book:Book)
- goToAdd()
- goToShow(book:Book)


I define all implementations in Application. - each RULE has a context? Or what? I guess so. Yeah, sure.

Library: contexts[library_implementation] = the_application;

SearchView (Library)
new Library(this); - it has a context!
connect() - goes through the context, or just keeps it in mind?
- generate -
- context(SearchView).parent = context(MainView) - how do we know? We know it is a view because it extends DisplayObject? Because it has a "parent" property. so, we look for a context of its parent property, and create one if it doesn't exist. And so on, until we run into.... something.


-> System Creates a new Library Implementation - it doesn't exist yet, so we can't run into it's context. But we can put a context on a RULE. the context of a rule could be an object - in which case you only use it if you run into it, or a regexp, in which case your context name has to match it. Assume the FullImplementation Rule has a context of an object, the main application.

In Generate, when we're testing to see if it matches, we call
context.hasAncenstor(rule.context); -

- i have a view object
- the rule context is a parent view

1) check to see if the context object has a parent. No (context.parent)
2) check to see if the source has a parent - YES (context.source.parent) - MainView
3) if no, return false
4) If YES, see if we have a context for the parent - NO
5) If NO, see if the source has a parent - YES - Application
6) See if we have a context for the parent - YES
7) See if it matches - YES - return true

! Create a new Library, and save its context as a new context, referencing it, and with the Application as a parent.



So, I'm holding a reference to every object in the system that contains a reference to an interface. Yikes!

Now, let's have Libary get a reference to LibraryService

context.matches(rules.context);

contexts[library_instance] is already set to the_applications_context

1) the LibraryService Interface has a context of the LibraryImplementation

does LibraryImplementation have a context in the system? (Yes, w/ parent = Application)

1) does it match? NO
2) does the context have a parent? yes, Application
3) does it match? YES




OK >>> Now, let's create a new implementation of Library, that just traces things, and give it a context of MainView (kind of useless, but let's pretend)

When BookView goes to get an instance of Library

1) does it match? NO
2) does it have a parent in its context? NO
3) does it have a parent itself? YES
--
1) does it match? YES

When the mock Library goes to get a reference to LibraryService

MockLibrary has a context of MainView
The LibraryService interface has a context of MockLibrary

1) does it match? NO
2) have a parent? YES - MainView
3) match? NO
4) parent? YES - Application
5) match? YES

return true!

If I had implementated LibraryService as well, it would return that..

So... why not put rules into the context object?

Application - Rule for all implementations
MainView - Rule for Library (MockLibrary)

then, you can just get the rules for it, and see if any exist?

You could still have them in one big list, and just filter them that way, I suppose. Well, the idea is that you DON'T return the others if you found them hiding out in that context.

Sheesh this is a tough problem! Do I even gain anything by it?

I should start by designing a how-to page, similar to the one for Sinatra and God, and solicit feedback based on that. I think I have a good idea of how it should work.













3 changes: 3 additions & 0 deletions source/src/net/seanhess/zero/dependency/Connect.as
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package net.seanhess.zero.dependency
{
import net.seanhess.zero.util.Invalidator;

/**
* An MXML tag to facilitate mapping implementations
*/
[DefaultProperty("rules")]
public class Connect
{
Expand Down
3 changes: 3 additions & 0 deletions source/src/net/seanhess/zero/dependency/DependencyError.as
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package net.seanhess.zero.dependency
{
/**
* Needs work.
*/
public class DependencyError extends Error
{
public function DependencyError(message:String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ package net.seanhess.zero.dependency
import mx.core.IMXMLObject;

/**
* The implementation will be
* You can extend DependencyInterface to have the system pass you
* an implementation. This class proxies the implementation.
*
* You MUST send in a reference to the context when extending this if
* you want it to grab the implementation from the system.
*
* You can pass in null as the context, and set implementation to your
* own class for mocking.
*/
public class DependencyInterface implements IMXMLObject
{
Expand Down
1 change: 0 additions & 1 deletion source/src/net/seanhess/zero/dependency/Implement.as
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package net.seanhess.zero.dependency
{
/**
* Allows you to specify a full impelemntation, which matches up everything that matches
* Can match based on regular expression
*/
public class Implement
{
Expand Down
6 changes: 6 additions & 0 deletions source/src/net/seanhess/zero/dependency/Implementation.as
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package net.seanhess.zero.dependency
import flash.utils.Proxy;
import flash.utils.flash_proxy;

/**
* Proxy to allow us to manage multiple overlapping implementations
*
* Also lets DependencyInterfaces just send in "arguments" instead of the
* parameters.
*/
public class Implementation extends Proxy
{
public var onlyImplementation:*;
Expand Down
2 changes: 1 addition & 1 deletion source/src/net/seanhess/zero/dependency/Intercept.as
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.seanhess.zero.dependency
{
/**
* Allows you to wrap calls. Should allow you to do regular expressions in the match.
* Allows you to wrap calls. Should allow you to use wildcards in the match
*/
public class Intercept
{
Expand Down
8 changes: 7 additions & 1 deletion source/src/net/seanhess/zero/dependency/Register.as
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ package net.seanhess.zero.dependency
import mx.collections.ArrayCollection;
import mx.collections.ListCollectionView;

/**
* Global register of creation rules and caches of singleton
* implementations (the default)
*/
public class Register
{
/**
Expand Down Expand Up @@ -50,7 +54,9 @@ package net.seanhess.zero.dependency
{
throw new DependencyError("Could not find implementation for " + type + " in context "+ context);
}



// doesn't currently support multiple rules per dependency
var rule:Rule = rules.getItemAt(rules.length-1) as Rule;

var instance:*;
Expand Down
46 changes: 3 additions & 43 deletions source/src/net/seanhess/zero/dependency/Rule.as
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,15 @@ package net.seanhess.zero.dependency
import mx.core.ClassFactory;
import mx.core.IFactory;

/**
* Instructions to create an implementation for a DependencyInterface
*/
public class Rule
{
public static const IMPLEMENT:String = "implement"; // means it creates one instance
public static const FACTORY:String = "factory"; // means it creates a new one every time
public static const PROXY:String = "proxy"; // means it pumps everything through it. Needs to use the cache rule of the parent object?

// Remember that BasicProxy nonsense?
// That way you can avoid having a billion proxies
// You ALWAYS have one (extra) proxy
// Or, should I make the interface itself be that kind of a proxy?
// It could get a reference to all the several implementations, and a list of the rules
// then it could know where to go for each thing?

// if two classes are listen as implementations for the same interface
// the latter takes priority
// if nothing overlaps, hopefully we can just do both, right?
// if things DO overlap, we use the latter...
// how would you even DO that? a try/catch block?... sure... you can specifically catch TypeErrors

// Proxies - can my little proxy handle other proxies? He gets asked to call "send" for which he has a proxy and an
// implementation. The proxy is second (thank goodness?).

// Definitely easiest to send EVERYTHING through the proxy. So do that!
// Does a proxy have to actually BE a proxy? If so, how do you set the item?
// We'd need to be able to change the item it is proxying? NO - so they can extend ObjectProxy



// They either need to extend ObjectProxy, and send everything through them, or they need
// some other way to know the item they'll be proxying, right?

// Since I try to make this class-based... as in, the class makes the decisions, let's go with
// the other option.

// Overlapping implementations... Hmm, just

// Overlapping implementations - just use the later one... Or rather, try the later one first?
// Or, should we create a NEW proxy that uses all our stuff for us.
// How would it call a proxy?
//

// callProperty
// try calling the first proxy...
// no, proxies need access!
// k, do it the normal way.
// so, when it returns an implementation, though, it needs to be

// just pass in the real implementation for now?

public var kind:String;

public var interfaces:RegExp = /.*/;
Expand Down
3 changes: 3 additions & 0 deletions source/src/net/seanhess/zero/dependency/Utils.as
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package net.seanhess.zero.dependency
{
import flash.utils.getQualifiedClassName;

/**
* Just returns the full name of the class, without the stupid double-colon
*/
public class Utils
{
public function getName(source:*):String
Expand Down
3 changes: 3 additions & 0 deletions source/src/net/seanhess/zero/util/Invalidator.as
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ package net.seanhess.zero.util
import flash.events.TimerEvent;
import flash.utils.Timer;

/**
* delays stuff
*/
[Event(name="commit", type="flash.events.Event")]
public class Invalidator extends EventDispatcher
{
Expand Down

0 comments on commit 959ef16

Please sign in to comment.