Introduction to Yan Container

What does Yan Container do?

How do I use Yan Container?

How is Yan Container different than other IOC containers?

Write two simple components

public class Foo {
    private final String hi;
    public Foo(String hi){
        this.hi = hi;
    }

    public void greet(Object obj) {
        System.out.println(hi + " " + obj);
    }
}

public class Bar {
    Foo foo;
    public Bar(Foo foo) {
        this.foo = foo;
    }

    public void greeting() {
        foo.greet(this);
    }

    public String toString(){return "bar";}
}	

Assemble components

Container yan = new DefaultContainer();
yan.registerValue("hello");
yan.registerConstructor(Foo.class);
yan.registerConstructor(Bar.class);	

Instantiate and use component

Bar bar = (Bar) yan.getInstanceOfType(Bar.class);
bar.greeting();

getInstanceOfType(Bar.class) will look at the constructor of Bar class and determine that it needs a Foo instance.

Container then constructs this Foo instance using the constructor of class Foo. Since the constructor of Foo requires a String as a parameter, container will search a component with type String, and that is resolved to the valule "hello".

Finally the value "hello" is passed into constructor of Foo, and the Foo instance is passed into the constructor of Bar.

In an IOC (Inversionn of Control) design, the Bar class does not bother finding itself a Foo but instead just wait for someone to provide it.

Introduce an interface for the dependency

Change the Foo class to implement a Greeter interface and change the Bar class to depend on Greeter instead.

public interface Greeter {
    void greet(Object whom);
}

public class Foo implements Greeter {
    private final String hi;
    public Foo(String hi) {
        this.hi = hi;
    }

    public void greet(Object whom) {
        System.out.println(hi + " " + whom);
    }
}

public class Bar {
    Greeter greeter;
    public Bar(Greeter greeter) {
        this.greeter = greeter;
    }

    public void greeting() {
        greeter.greet("bar");
    }
}

Assemble and use components just as before:

Container yan = new DefaultContainer();
yan.registerValue("hello");
yan.registerConstructor(Foo.class);
yan.registerConstructor(Bar.class);	

Bar bar = (Bar) yan.getInstanceOfType(Bar.class);
bar.greeting();

The Bar will be given a Foo, because Container understands that a Foo is a Greeter.

The Bar and the Foo no longer depend on each other, this is called the Dependency Inversion Principle since both components depend on the interface and no longer directly on each other.

Simple Lifecycle

Suppose the Foo object has to be initialized before being used by class Bar. This can be done with the default lifecycle support.

public class Foo {
    private final String hi;
    public Foo(String hi) {
        this.hi = hi;
    }

    public void initialize() {
    }

    public void greet(Object whom) {
        System.out.println(hi + " " + whom);
    }

    public void dispose() {
    }
}

public class Bar {
    Foo foo;

    public Bar(Foo foo) {
        this.foo = foo;
    }

    public void greeting() {
        foo.greet(this);
    }

    public String toString(){
        return "bar";
    }
}	

Now, assemble the components and set the lifecycle method.

Container yan = new DefaultContainer();

yan.registerValue("hello");
Component foo = Components.ctor(Foo.class);

DefaultLifecycleManager manager = new DefaultLifecycleManager();

Lifecycle lifecycle = manager.newLifecycle()
  .initializer("initialize")
  .disposer("dispose");
  
Component foo_with_lifecycle = manager.withLifecycle(foo, lifecycle);

yan.registerComponent(foo_with_lifecycle);
yan.registerConstructor(Bar.class);

To run the initializer and the disposer:

final Bar bar = (Bar)yan.getInstanceOfType(Bar.class);
manager.init();
bar.greeting();
manager.dispose();	

Customize component parameter

Change the Bar class by adding a member "name".

public class Bar {
    Foo foo;
    private final String name;

    public Bar(Foo foo, String name) {
        this.foo = foo;
        this.name = name;
    }

    public void greeting() {
        foo.greet(name);
    }
}

Now we can control the name of any Bar object.

However, if we assemble and run the components just as before, we will get "hello hello" rather than the expected "hello bar". This is because container resolves both the second parameter of Bar and the first parameter of Foo to the same String object: "hello".

We can register another value "bar" into the container by:

yan.registerValue("bar component", Components.value("bar"));

The string "bar component" is used as a key for the value bar. This is because the container is a hash table, two components with the same key cannot exist in the container at the same time. We have to give it a different key.

To use this new value, we need to register Foo and Bar slightly different:

Container yan = new DefaultContainer();
yan.registerValue("greeting", "hello");
yan.registerValue("bar component", "bar");
yan.registerComponent(Components.ctor(Foo.class).withArgument(0, Components.useKey("greeting")));
yan.registerComponent(Components.ctor(Bar.class).withArgument(1, Components.useKey("bar component")));

Thus, by explicitly informing the container which component to use for the otherwise ambiguous parameter, we can get the expected result: "hello bar".

(By the way, did you find that Components.useKey() is very similar to the "beanref" available in the Spring framework?)

Customize component parameter locally

In the previous example, two auxiliary components with distinct keys are registered into the container. The component parameter can then explicitly reference these components for customization.

This might not be desirable in certain cases because it also makes visible the two auxiliary components to other irrelevant components in the container. It doesn't look like a serious problem for our tiny example, but for bigger application, it tends to convolute things together. In fact, it is very similar to introducing global variables in a program, which is normally discouraged.

The solution is simple. For a component to be used as a parameter of another one, Yan does not require a component to be registered in a container at all.We can replace the Components.useKey() directly with Components.value().

Container yan = new DefaultContainer();
yan.registerComponent(Components.ctor(Foo.class).withArgument(0, Components.value("hello")));
yan.registerComponent(Components.ctor(Bar.class).withArgument(1, Components.value("bar")));

In fact, if the only component we care is Bar, Foo does not need to be registered at all. We can make Foo local using withArgument().

Container yan = new DefaultContainer();
Component foo = Components.ctor(Foo.class)
    .withArgument(0, Components.value("hello"));
yan.registerComponent(
    Components.ctor(Bar.class)
        .withArgument(0, foo)
        .withArgument(1, Components.value("bar"))
);	

Thus, only Bar is registered and made visible to other components in the container. Foo and the two string values are just local dependents. They are private to Foo and invisible to any other components.

Singleton Component

Although singleton pattern is considered harmful by some people, it is undoubtedly useful in real life when you simply want a _single_ object all the time. Call it "singleton" or not, this is sometimes a hard requirement that has to be fulfilled.

Yan allows any component to be made singleton so that it always returns the same instance..

Component foo = Components.ctor(Foo.class)
    .withArgument(0, Components.value("hello"));
Component singleton_foo = foo.singleton();

Java Bean components

So far, we've seen components using public constructors. You may get an impression that Yan exclusively uses public constructor for dependency injection. No, Yan has no requirement about how the dependency should be injected into your business ojbect.

If you read on, you can see how Java Bean setters can be used (as in Spring Framework).

First, let's re-write the class Foo and Bar to follow Java Bean convention:

public class Foo {
    private String hi;
    
    public Foo() {
    }

    public void setHi(String h) {
        this.hi = h;
    }

    public void greet(Object whom) {
        System.out.println(hi + " " + whom);
    }
}

public class Bar {
    private Foo foo;
    private String name;

    public Bar() {
    }

    public void setFoo(Foo f) {
        this.foo = f;
    }

    public void setName(String n) {
        this.name = n;
    }

    public void greeting() {
        foo.greet(name);
    }
}

Now if we register and assemble components as before, we'll get a Foo object and a Bar object with all member variables set to null.

This is because container does not know that you want it to call Java Bean setters after the Foo and Bar objects are constructed.

In order to use Java Bean setters to inject dependency, we need to use the Components.bean() function to create a Component that uses Java Bean setters.

Container yan = new DefaultContainer();
yan.registerValue("hello");
yan.registerComponent(Components.bean(Foo.class));
yan.registerComponent(Components.bean(Bar.class));

And still, we get "hello hello" out of this. Solution is very similar to what we have done, except we need to replace "withArgument" with "withProperty":

Container yan = new DefaultContainer();
Component foo = Components.bean(Foo.class)
    .withProperty("hi", Components.value("hello"));
yan.registerComponent(Components.bean(Bar.class)
    .withProperty("foo", foo)
    .withProperty("name", Components.value("bar"))
);

Java Bean with non-default constructor

Although Java Bean Specification requires you to always use a parameter-less constructor, Yan allows you to use a non-default constructor with parameters when you want to.

First of all, you may ask "why?". If Java Bean Specification requires so, why do I ever want to use a non-default constructor?

Well, the simple answer is: You may or you may not want to. Whether to conform to Java Bean Specification is your call, not Yan's.

Some thought we had is: the concept of Java Bean is borrowed into dependency injection. What dependency injection needs is not real Java bean, but just some standard setters.

If we re-write the Bar class as follows:

public class Bar {
    private Foo foo;
    private String name;

    public Bar(Foo f) {
        this.foo = f;
    }

    public void setFoo(Foo f) {
        this.foo = f;
    }

    public void setName(String n) {
        this.name = n;
    }

    public void greeting() {
        foo.greet(name);
    }
}

We've got a non-standard Java Bean with a parametered constructor and public setters.

To use such non-standard Java Bean, the syntax for the component is slightly different:

Container yan = new DefaultContainer();
Component foo = Components.bean(Foo.class)
    .withProperty("hi", Components.value("hello"));
yan.registerComponent(foo);
yan.registerComponent(Components.bean(Components.ctor(Bar.class))
    .withProperty("foo", foo)
    .withProperty("name", Components.value("bar"))
);

Selecting Java Bean setters

In the previous example, class Bar accepts a Foo object from both the constructor and the setFoo() method.

When component Bar is assembled, the Foo object will be injected twice by the constructor and the setter.

Although it did not cause us any trouble, sometimes we may want to tell the container not to call setFoo() because the foo object is already injected in constructor.

There are many ways in Yan to do it. The most straight-forward one is to explicitly specify the properties to inject.

yan.registerComponent(
    Components.bean(Components.ctor(Bar.class), new String[]{"name"})
        .withProperty("name", Components.value("bar"))
);

Optional Java Bean Property

If we did not use withProperty() to specify the value of property name in the previous example, an exception will be thrown indicating that the property name cannot be resolved.

Yan fails when any dependency cannot be satisfied.

This behavior can be customized though. For example, we may tell the container that: skip property "name" when it cannot be resolved.

This is done by calling optionalProperty() function.

yan.registerComponent(
    Components.bean(Components.ctor(Bar.class), new String[]{"name"})
        .optionalProperty("name")
);

Configure the default value for a property

We can further customize the defaulting policy by giving a default value.

For example, we may say: if property "name" is not found, please do not fail, do not skip it, but use value "bar" instead.

yan.registerComponent(
    Components.bean(Components.ctor(Bar.class), new String[]{"name"})
        .withDefaultProperty("name", Components.value("bar"))
);

Optional Parameter and Default Parameter value

Not just Java Bean properties, parameters of constructor or any other kind of component can be made optional and can be given a default value.

Suppose we did not register component foo into the container, the above code will fail to instantiate Bar because constructor of Bar requires a Foo object, which cannot be resolved.

We could make this parameter optional by saying:

yan.registerComponent(
    Components.bean(Components.ctor(Bar.class).optionalParameter(0),
        new String[]{"name"})
        .withDefaultProperty("name", Components.value("bar"))
);

This way, we can successfully instantiate a Bar object with its "foo" member set to null. Of course, it will still fail when greeting() is invoked.

To provide a different default value for a parameter, withDefaultArgument() can be used.

yan.registerComponent(
    Components.bean(Components.ctor(Bar.class)
        .withDefaultArgument(0, Components.bean(Foo.class)),
        new String[]{"name"})
        .withDefaultProperty("name", Components.value("bar"))
);

Explicitly ask for a defaut value

withDefaultProperty(), withDefaultArgument() both rely on whether the property/parameter can be resolved in a certain container. In addition, one can also explicitly ask for the default value.

In the example where we want to ignore the property "foo" for component Bar, we listed all the properties that we are interested and just left "foo" out. It was not a very straight-forward solution.

Using useDefault(), we can achieve the same result, and the code might be more readable to some:

yan.registerComponent(
    Components.bean(Components.ctor(Bar.class)
        .withDefaultArgument(0, Components.bean(Foo.class)),)
    .withProperty("foo", Components.useDefault())
    .withDefaultProperty("name", Components.value("bar"))
);

Factory based injection

So far, we've covered constructor-based and java bean based injections. Now we want to introduce a more flexible way of injection: factory.

First, what do we do most frequently in our every-day Java program? calling constructor? setting Java Bean properties? No, we invoke methods! Based on their high level semantics in the application domain, we call some methods "factory" if their responsibility is spelled as "creating products".

We believe it is a pity if an IOC container has no support for this most general form of object instantiation.

In Yan, using factory is as easy as using constructor or Java Bean.

Imagine now our business object design changes such that the constructor of Foo is hidden, and a static factory method is exposed to create Greeter object:

public interface Greeter {
    void greet(Object whom);
}

public class Foo implements Greeter {
    private final String hi;

    private Foo(String hi) {
        this.hi = hi;
    }

    public static Greeter instance(String hi) {
        return new Foo(hi);
    }

    public void greet(Object whom) {
        System.out.println(hi + " " + whom);
    }
}

public class Bar {
    Greeter greeter;

    public Bar(Greeter greeter) {
        this.greeter = greeter;
    }

    public void greeting() {
        greeter.greet("bar");
    }
}

No more. we cannot use constructor-based injection any more because the constructor is hidden.

But don't worry. You won't hear any arrogant interference like "No! This design is bad. It does not work with the container!". Yan is a polite container. It won't say "no" to any of your own business object design decision. It follows your rule, not the opposite. And here is how Yan work with this new design:

Container yan = new DefaultContainer();
yan.registerValue("hello");
yan.registerComponent(Components.static_method(Foo.class, "instance"));
yan.registerConstructor(Bar.class);

Bar bar = (Bar) yan.getInstanceOfType(Bar.class);
bar.greeting();

Yan also supports invoking an instance method to instantiate a component:

Components.method(obj, "method_name") can be used for this purpose. It invokes obj.method_name(. . .) to instantiate component. It is more often combined with dynamic component. We'll cover them in the advanced sections.

Instantiate a list

Suppose the class Bar is changed as:

public class Foo {
    private String hi;

    public Foo() {
    }

    public void setHi(String h) {
        this.hi = h;
    }

    public void greet(Object whom) {
        System.out.println(hi + " " + whom);
    }

}

public class Bar {
    private java.util.List foos;
    private String name;

    public Bar() {
    }

    public void setFoo(java.util.List f) {
        this.foos = f;
    }

    public void setName(String n) {
        this.name = n;
    }

    public void greeting() {
        for (java.util.Iterator it = foos.iterator(); it.hasNext();) {
            ((Foo) it.next()).greet(name);
        }
    }
}

Now, to instantiate a Bar, we need to give it a list of Foo objects instead of just one.

Suppose we want to create two Foo objects, one will say "hello", one will say "goodbye". And we want to pass these two Foo objects in a list to Bar.

Function Components.list() can be used to do this job:

Component foo = Components.bean(Foo.class);
Component foos = Components.list(new Component[]{
    foo.withProperty("hi", "hello"),
    foo.withProeprty("hi", "goodbye")
});
Component bar = Components.bean(Bar.class).withProperty("foos", foos);

Instantiate an array

What if instead of a list, Bar expects an array of Foo? The API is very similar: We just need to switch from Components.list(. . .) to Components.array(. . .).

Component foo = Components.bean(Foo.class);
Component foos = Components.arraynew Component[]{
    foo.withProperty("hi", "hello"),
    foo.withProeprty("hi", "goodbye")
});
Component bar = Components.bean(Bar.class).withProperty("foos", foos);

Instantiate a java.util.LinkedHashMap

What if instead of list or array, Bar expects a java.util.Map object that contains Foo objects?

public class Foo {
    private String hi;

    public Foo() {
    }

    public void setHi(String h) {
        this.hi = h;
    }

    public void greet(Object whom) {
        System.out.println(hi + " " + whom);
    }
}

public class Bar {
    private java.util.Map foos;
    private String name;

    public Bar() {
    }

    public void setFoo(java.util.Map f) {
        this.foos = f;
    }

    public void setName(String n) {
        this.name = n;
    }

    public void greeting(String key) {
        ((Foo) foos.get(key)).greet(name);
    }
}

We want to be able to get the following result:

bar.greeting("morning") yields "hello bar";
while bar.greeting("evening") yields "goodbye bar".

Well. Components.hashmap() is your friend.

Component foo = Components.bean(Foo.class);

final java.util.Map foos = new java.util.HashMap();
foos.put("morning",foo.withProperty("hi", Components.value("hello")));
foos.put("evening",foo.withProperty("hi", Components.value("goodbye")));

Component foos = Components.hashmap(foos);

Instantiate a mylist

Now we know how to create a component for standard java.util.List for sequential collection; how to create a component for standard java.util.Map for associative map. We also showed how to create an array component.

But Java collection framework has more classes and interfaces. What if we want to create a java.util.Set? A java.util.LinkedList? A java.util.TreeMap?

What about a List of List? An Array of HashMap? A List of List of Array of HashMap?

What about collection classes outside of java.util package?

Suppose we have a MyList class that we want to use instead of java.util.List (for whatever the reason). The Bar class becomes:

public class Bar {
    private MyList foos;
    private String name;

    public Bar() {
    }

    public void setFoo(MyList f) {
        this.foos = f;
    }

    public void setName(String n) {
        this.name = n;
    }

    public void greeting() {
        for (int i = 0; i < foos.size(); i++) {
            ((Foo) foos.getAt(i)).greet(name);
        }
    }
}

Obviously Yan will not have a mylist() function that instantiates MyList for you because Yan is not aware of MyList.

The idea is, you can first use Components.list() or Components.array() to get a Component that creates an array or a list. Then use a jfun.yan.Map object to convert this array or list to MyList. (It is your list, you know how to populate it, do you?)

Creating an array or a list is easy, let's assume we are gonna use array. The next thing is how you use jfun.yan.Map to convert an array to MyList.

jfun.yan.Map m = new jfun.yan.Map(){
    public Object map(Object obj){
    final Object[] arr = (Object[])obj;//the object is an array.
    return MyList.fromArray(arr);
    //assume fromArray is tbe function to convert an array to a MyList.
}};

This jfun.yan.Map object is responsible for converting an object (which should be of type Object[]) to a MyList object.

With this jfun.yan.Map object, we are ready for the main-course:

Component foo = Components.bean(Foo.class);
Component foos = Components.array(new Component[]{
    foo.withProperty("hi", "hello"),
    foo.withProeprty("hi", "goodbye")
});
Component mylist = foos.map(m);

Instantiate a MyHashMap

map() opens a new window. All kinds of component instances can be instantiated by mapping from instance of another component.

Suppose we have a MyHashMap object, we could use map() to convert a java.util.Map to it.

jfun.yan.Map convert_to_myhashmap = new jfun.yan.Map(){
    public Object map(Object obj){
        final java.util.Map m = (java.util.Map)obj;
        return populate_myhashmap_with_map(m);
}};
...
Component myhashmap = Components.hashmap(. . .).map(convert_to_myhashmap);

Instantiate a Pair

map() enables transformation from one component to another. Sometimes, however, we may want to transform more than one components to another component.

For example, componentA creates an object of type A, componentB creates an object of type B. How do I get a component that creates a Pair object which contains both the result from componentA and componentB?

Similar to jfun.yan.Map, there are jfun.yan.Map2, jfun.yan.Map3, jfun.yan.Map4, jfun.yan.Map5 interfaces that represent transformation from 2-5 objects to another object.

To create a Pair, we need to use jfun.yan.Map2 interface:

jfun.yan.Map2 m2 = new jfun.yan.Map2(){
    public Object map(Object o1, Object o2){
        return new Pair(o1, o2);
    }
}
Component foo1 = . . .;
Component foo2 = . . .;
Component pair = Monad.map(foo1, foo2, m2);

Instantiate MyBean.

At the beginning when I showed Yan Container to my friends, my friend Joe challenged the "no-intrusion" concept:

"The 'no-intrusion' doctrine is just over-emphasized. There has to be some coding standard or convention to make things work. We should just go ahead and follow that convention, whatever it is.
For an extreme example, what if somebody uses 'giveXXX' rather than 'setXXX' for injecting dependency? If you don't support it, should I call that an 'intrusion' or what?
"

And I said: "Hold it there, buddy. Yan does support it. When I said no intrusion, I meant it."

Now, let's imagine that Moon Microsystems defines a Java Nuts Specification which uses giveXXX for setting property value and shareXXX for getting property value. We have no interest in the sharer (getter), but let's take a look at how the "giver" is handled.

Before starting though, a very important interface Binder needs to be brought to your attention.

This is how Binder is defined:

public interface Binder{
    Component bind(Object obj);
}

Looks very similar to jfun.yan.Map interface. It is also responsible for mapping an instance created from another component.

The difference is the return type. Rather than directly converting the instance to another object, Binder returns another Component which in turn can be used to create the target object.(In the real API, return type of bind is Creator, a super interface of Component, but for now let's not worry about that minor detail.):

Pseudo code for staging:

	instance1 <- component_stage1.create();
    component_stage2 <- binder.bind(instance1);
    instance2 <- component_stage2.create();

If you still don't see the value of such a simple interface, it is fine, you will see it once we start doing MyBean.

Alright, armed with the knowledge of Binder, let's buckle up and go!

First, Let's design class MyBean that follows the Java Nuts Specification:

public class MyBean {
    private double money;
    private long time;
    private String name;

    public MyBean() {
    }

    public void giveMoney(double amount) {
        this.money = amount;
    }

    public void giveTime(long ms) {
        this.time = ms;
    }

    public void giveName(String n) {
        this.name = n;
    }
}

Our target function should look like this:

Component mybean(Class cls, String[] props);

mybean() should create a Component that creates an object of class "cls" and set the properties following the Java Nuts Specification.

So that mybean(MyBean.class, new String[]{"money", "time", "name"}) will create a Component that instantiates a MyBean object using all the properties.

And heres' how we implement this function:

Component mybean(Class cls, final String[] props){
    final Component stage0 = Components.ctor(cls, null);
    final Binder binder = new Binder(){
        public Component bind(Object instance){
        Component r = Components.value(instance);
        for(int i=0; i<props.length;i++){
        final String method_name = "give"+props[i];
        final Component invoke_method = Components.method(instance, method_name);
        r = r.seq(invoke_method);
        }
    return r;
	}
}
    return stage0.followedBy(binder);
}

In summary, stage0 is responsible for invoking default constructor;
binder is responsible for invoking "givers" against the MyBean object.
stage0.followedBy(binder) combines these two together and does the entire job.

How to disable auto-wiring?

In auto-wiring, each parameter and property gets injected automatically by the container, which saves us quite some work.

It works just fine in an application with small number of components to be laced up.

It may not be desirable, however, if the application scales up. Configuration using auto-wiring in an application with large number of components tends to be less predictable and less maintainable. It is like walking in a maze to figure out who exactly is injected to whom.

Using combinators like withArgument(), withProperty(), one can already manual-wire some dependencies. The container is only responsible for dependencies not explicitly specified in manual-wiring. In case when you absolutely don't want auto-wiring stand in your way, Yan provides two ways of disabling it.

About Abstract Factory Pattern

Some say that IOC container invalidates the need for factory or abstract factory.

We strongly disagree with this view because we believe an IOC container should be non-intrusive and should remain invisible to the application domain.

We claim that it is an anti-pattern to have the application code depend on IOC container which should only be used by the person responsible for assemblying various business components.

For most application developers, factory and abstract factory are still necessary patterns when some object creation logic needs to be encapsulated or abstracted.

For example, our Bar class may be refactored to require an abstract factory instead of Foo or Greeter:

public interface GreeterFactory {
    Greeter createGreeter(String hi);
}

public class Bar {
    GreeterFactory factory;
    private final String name;
    private final String hi;

    public Bar(GreeterFactory factory, String hi, String name) {
        this.factory = factory;
        this.hi = hi;
        this.name = name;

    }

    public void greeting() {
        factory.createGreeter(hi).greet(name);
    }
}

This new Bar class uses an abstract factory to create Greeter object on the fly, with no dependency on Container.

Abstract factory pattern does have one awkward drawback: implementations of abstract factory typically depend on concrete classes of its product.

For example, if we have two implementations of interface Greeter:

public class Foo1 implements Greeter{
    . . .
}

public class Foo2 implements Greeter{
    . . .
}

We will need two implementation classes of GreeterFactory that creates Foo1 and Foo2 respectively:

public class Foo1Factory implements GreeterFactory {
    Greeter createGreeter(String hi) {
        return new Foo1(hi);
    }
}

public class Foo2Factory implements GreeterFactory {
    Greeter createGreeter(String hi) {
        return new Foo2(hi);
    }
}

Very slight difference, but we need two classes.

Imagine that we have 100 difference implementations of interface Greeter, we will need 100 extra implementation classes for GreeterFactory!

This thought could easily scare away us lazy programmers and drive us to use less Object-Oriented design such as reflection.

For example:

public class Bar {
    Class greeter_class;
    private final String name;
    private final String hi;

    public Bar(Class greeter_class, String hi, String name) {
        this.greeter_class = greeter_class;
        this.hi = hi;
        this.name = name;
    }

    public void greeting() {
        Constructor ctor = greeter_class.getConstructor(new Class[] { String.class });
        Greeter greeter = (Greeter) ctor.invoke(new Object[] { hi });
        greeter.greet(name);
    }
}

The design is less elegant but it does save programmers from having to create many implementation classes of GreeterFactory. java.lang.Class is used instead as a factory free of charge.

Java anonymous class can make things a little bit better but the syntax for anonymous class is still cumbersome.

Dilemma? As a big fan of "programming-against-interface", I strongly recommend you to stick to the first deisgn where class Bar depends on an abstract GreeterFactory interface.

In order to liberate pogrammers from creating the 100 extra GreeterFactory implementation classes, Yan offers a factory() function that will automatically implement GreeterFactory.

Component foo1 = Components.ctor(Foo1.class);
Component foo2 = Component.ctor(Foo2.class);
 
Component factory1 = foo1.factory(GreeterFactory.class);
Component factory2 = foo2.factory(GreeterFactory.class);

Component bar = Components.ctor(Bar.class).withArgument(0, factory1);

This saves programmers lot of work. It is typically not necessary to create one factory implementation for each and every different product. When Yan is assemblying components, it uses the factory() to automatically generate a factory class.

Application programmers can now follow the "programming-against-interface" principle and be rest-assured that they don't need to do boring extra work like creating trivial factory classes.

Container Inheritance

Components can be stored in different containers to be kept separate. Components from container 1 is not visible to those from container 2 and vice versa. However, if we wish, we can let them communicate in a controlled manner.

For example, if we create two conainers and register our Foo and Bar separately:

Component foo = Components.ctor(Foo.class);
Component bar = Components.ctor(Bar.class);
Container yan1 = new DefaultContainer();
Container yan2 = new DefaultContainer();
yan1.registerComponent(foo);
yan2.registerComponent(bar);

Now if we try to instantiate Bar from yan2, we will get an error because it depends on Foo while Foo cannot be found in yan2.Now if we try to instantiate Bar from yan2, we will get an error because it depends on Foo while Foo cannot be found in yan2.

We can create a new virtual container that can make components in yan1 visible to yan2.

Container yan = yan2.inherit(yan1);

If we now try to instantiate Bar from container yan, Foo will be visible to Bar and therefore the Bar component can be instantiated successfully. While since neither yan1 nor yan2 was changed by inherit(), trying to instantiate Bar from either of them will still result in an exception.

The immutable nature of inherit() enables real flexible thing. For example, we could also create another virtual container that makes components in yan2 visible to yan1:

    Container another_yan = yan1.inherit(yan2);

And YET another conainer that makes yan1 inherit yan3 (some other container):

    Container yet_another_yan = yan1.inherit(yan3);

These virtual containers can all exist in the program at the same time and we programmers can flexibly choose between them or even use them all.

"Hey, so yan1 is parent of yan2 and yan2 is parent of yan1? What the heck is that?" Well, they may or may not make sense to your application, or they may make sense to one but not the other. To understand it better, we can think of this as a "what-if" question.

If the question is "what if yan1 is a parent of yan2?", we go from the virtual container yan; and if the question is "what if yan2 is a parent of yan1?", we start from the virtual container another_yan.

If you know relational database such as Oracle or SQL Server, containers are like tables that hold physical data, while the virtual containers created by inherit() are like views that you can join the tables at will. In view 1, you can make Tom parent of Jack, while in view 2, you can make Jack parent of Tom. However you play this logical trick, the physical tables are not changed a bit.

This relational model of managing data has been proved more flexible and easier to maintain than if the "Tom is parent of Jack" relationship were hard-wired. We believe container management can benefit from this model too.