|
|||||
|
|||||
Tutorial
IntroductionWhat does Yan Container do?
How do I use Yan Container?
How is Yan Container different than other IOC containers?
Simple ExampleWrite two simple componentspublic 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 componentsContainer yan = new DefaultContainer(); yan.registerValue("hello"); yan.registerConstructor(Foo.class); yan.registerConstructor(Bar.class); Instantiate and use componentBar 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 dependencyChange 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. Simple Life-cycleSimple lifecycleSuppose 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(); Component foo_with_lifecycle = manager.newLifecycle() .initializer("initialize") .disposer("dispose") .manage(foo); 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 ParameterCustomize component parameterChange 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". Customize component parameter locallyIn 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. 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 ComponentSingleton ComponentAlthough 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();
Scoped SingletonIt is also possible to specify a scope for a singleton. Component foo = Components.ctor(Foo.class)
.withArgument(0, Components.value("hello"));
Component singleton_foo = foo.singleton(new ThreadLocalScope());
Java Bean ComponentJava Bean componentsSo far, we've seen components using public constructors. You may get an impression that Yan exclusively uses public constructor for dependency injection. 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. 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. 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 constructorAlthough 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? 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 settersIn the previous example, class Bar accepts a Foo object from both the constructor and the setFoo() method. 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"))
);
Mandatory and optional propertiesPros/Cons of setter injection and constructor injectionConstructor injection and setter injection are the most popular
Yet, setter injection has its own valuable benefits:
Combine constructor injection and setter injection togetherIn Yan, it is possible to combine strength from both injection methods. i.e. use constructor injection for the mandatory fields; use setter injection for the optional properties. Example: public class CopyCommand{ private final String source; private final String dest; private boolean overwrite = false; public CopyCommand(String src, String dst){ this.source = src; this.dest = dst; } public void setOverwrite(boolean o){ this.overwrite = o; } ... }
The following code creates a Component for CopyCommand that can be configured by property name instead of parameter position: Component copy_command = jfun.yan.etc.Beans.beanComponent( Components.ctor(CopyCommand.class), new String[]{"source", "destination"} );
Finally, a sample configuration code: final Component target = copy_command .withProperty("source", Components.value("c:/src")) .withProperty("destination", Components.value("d:/dest"));
Optional property/parameter and default valueOptional Java Bean PropertyIf we did not use [withProperty()|http://yan.codehaus.org/api/jfun/yan/Component.html#withProperty(java.lang.Object, jfun.yan.Creator)] 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. yan.registerComponent(
Components.bean(Components.ctor(Bar.class), new String[]{"name"})
.optionalProperty("name")
);
Configure the default value for a propertyWe can further customize the defaulting policy by giving a default value. yan.registerComponent(
Components.bean(Components.ctor(Bar.class), new String[]{"name"})
.withDefaultProperty("name", Components.value("bar"))
);
Optional Parameter and Default Parameter valueNot 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"))
);
To provide a different default value for a parameter, [withDefaultArgument()|http://yan.codehaus.org/api/jfun/yan/Component.html#withDefaultArgument(int, jfun.yan.Creator)] 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 InjectionFactory based injectionSo 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? We believe it is a pity if an IOC container has no support for this most general form of object instantiation. 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 Girl(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: Instantiating CollectionInstantiate a listSuppose 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()|http://yan.codehaus.org/api/jfun/yan/Components.html#list(jfun.yan.Creator\[\])] 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 arrayWhat if instead of a list, Bar expects an array of Foo? The API is very similar: We just need to switch from [Components.list(. . .)|http://yan.codehaus.org/api/jfun/yan/Components.html#list(jfun.yan.Creator\[\])] 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.LinkedHashMapWhat 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:
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 mylistNow 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. 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 MyHashMapmap() opens a new window. 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 Pairmap() 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. 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);
Instantiating Customized BeanInstantiate MyBean.At the beginning when I showed Yan Container to my friends, my friend Joe challenged the "no-intrusion" concept:
And I said:
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. Before starting though, a very important interface Binder needs to be brought to your attention. 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! 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 here's 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; How to disable auto-wiringHow to disable auto-wiring?In auto-wiring, each parameter and property gets injected automatically by the container, 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. 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.
About Abstract Factory PatternAbout Abstract Factory PatternSome say that IOC container invalidates the need for factory or abstract factory. 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. 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. This thought could easily scare away us lazy programmers and drive us to use less Object-Oriented design such as reflection. 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 InheritanceContainer InheritanceComponents 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. |
|||||
|
Copyright 2003-2006 - The Codehaus. All rights reserved unless otherwise noted.
Powered by Atlassian Confluence
|
|||||