PicoContainer
  1. PicoContainer
  2. PICO-91

refactor ComponentAdapter+ComponentFactory relationship

    Details

    • Type: Improvement Improvement
    • Status: Open Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 1.0-beta-5
    • Fix Version/s: 3.0
    • Component/s: PicoContainer (Java)
    • Labels:
      None
    • Environment:
      all
    • Number of attachments :
      0

      Description

      By design, there are implementation dependencies between a factory and its adapter. Every time you create a new adapter, you need to create a new factory, which has zero reuse potential. Parrallel class hierarchy.

      First mentioned:

      http://lists.codehaus.org/pipermail/picocontainer-dev/2003-November/001606.html

      (see followup as well).

      It would be a good idea to fix this in one go together with

      PICO-90 (http://jira.codehaus.org/secure/ViewIssue.jspa?key=PICO-90)

      as these two issues are somewhat related.

        Activity

        Hide
        Leo Simons added a comment -

        every time you have a new kind of component you need a new adapter and a new factory. Furthermore, you *can't freely swap* (all factories have an explicit implementation dependency on an adapter). The responsibility split between adapter and factory is in some way broken. One option is to change the relationship: for example from

        factory creates adapter

        to

        factory uses adapter

        The combo I like best atm is

        adapter uses factory

        where a factory is just that, a factory, and the adapter handles everything but the 'new' (and with non-type-3 IoC, the init and dispose). Furthermore, the container need not care or know about factories...

        container uses adapter

        ...is enough.

        In my pet container I went further and introduced many-to-many selection policy, where an adapter is associated with a selector inside the container:

        Container-<<uses>>----0..m--->Selector
        Selector-<<locates>>1------>Adapter
        Adapter-<<uses>>----1..n--->Factory

        This is essentially a more generic form of a hashmap. In JDK-1.5 terms, the less generic hashmap variant is:

        container<key,adapter>
        Adapter has-a Factory

        In Ruby terms, the selector list functions as a rich switch/case statement, and the selector is the implementation of the '===' operator.

        In pseudocode (fragments):

        Container.get(arg):
        for each selector
        if selector.select(arg)
        return adapters.get(selector).get()
        return null

        SingleUseAdapter.get():
        return factory.newInstance()

        SingletonAdapter.get():
        lazyInit()
        return instance

        Factory.newInstance():
        return new Something

        Much cleaner. IoC.

        Show
        Leo Simons added a comment - every time you have a new kind of component you need a new adapter and a new factory. Furthermore, you * can't freely swap * (all factories have an explicit implementation dependency on an adapter). The responsibility split between adapter and factory is in some way broken. One option is to change the relationship: for example from factory creates adapter to factory uses adapter The combo I like best atm is adapter uses factory where a factory is just that, a factory, and the adapter handles everything but the 'new' (and with non-type-3 IoC, the init and dispose). Furthermore, the container need not care or know about factories... container uses adapter ...is enough. In my pet container I went further and introduced many-to-many selection policy, where an adapter is associated with a selector inside the container: Container-<<uses>>---- 0..m --->Selector Selector- <<locates>> 1 ------>Adapter Adapter- <<uses>> ---- 1..n --->Factory This is essentially a more generic form of a hashmap. In JDK-1.5 terms, the less generic hashmap variant is: container<key,adapter> Adapter has-a Factory In Ruby terms, the selector list functions as a rich switch/case statement, and the selector is the implementation of the '===' operator. In pseudocode (fragments): Container.get(arg): for each selector if selector.select(arg) return adapters.get(selector).get() return null SingleUseAdapter.get(): return factory.newInstance() SingletonAdapter.get(): lazyInit() return instance Factory.newInstance(): return new Something Much cleaner. IoC.
        Hide
        Jon Tirsen added a comment -

        I don't get it. How would you do it?

        Any chance you could describe it in more concrete terms? Like how would the DefaultComponentAdapter use a factory? For what?

        We had sort of the notion of a pluggable object that instantiated the objects (don't remember the name), but it didn't work. There's more logic associated to doing that than just creating an instance of a class. That's why we ended up with the adapter instead. Feature-envy, various parts of the container moved towards the factory, and the factory stopped being just a factory.

        The ComponentAdapterFactory is there for one reason only, to allow a PicoContainer to select a component-adapter to use when the user does not specify one. It's a completely different set of logic to doing that, for example the NanningComponentAdapterFactory introspects the component to see whether it's an aspect, a component that can get aspects applied to it, or a POJO (without intf/impl-separation). That's all different component adapters. There's no way I can put that logic somewhere else.

        I don't get the notion of a ComponentFactory. Isn't that a developer? How can someone other than a developer create a component?

        Show
        Jon Tirsen added a comment - I don't get it. How would you do it? Any chance you could describe it in more concrete terms? Like how would the DefaultComponentAdapter use a factory? For what? We had sort of the notion of a pluggable object that instantiated the objects (don't remember the name), but it didn't work. There's more logic associated to doing that than just creating an instance of a class. That's why we ended up with the adapter instead. Feature-envy, various parts of the container moved towards the factory, and the factory stopped being just a factory. The ComponentAdapterFactory is there for one reason only, to allow a PicoContainer to select a component-adapter to use when the user does not specify one. It's a completely different set of logic to doing that, for example the NanningComponentAdapterFactory introspects the component to see whether it's an aspect, a component that can get aspects applied to it, or a POJO (without intf/impl-separation). That's all different component adapters. There's no way I can put that logic somewhere else. I don't get the notion of a ComponentFactory. Isn't that a developer? How can someone other than a developer create a component?
        Hide
        Leo Simons added a comment -

        Jon wrote: "I don't get it. How would you do it? Any chance you could describe it in more concrete terms?"

        Sure. How about some code? Take a look here:

        http://cvs.sourceforge.net/viewcvs.py/jicarilla/jicarilla-sandbox/platform/container/

        here are some sample adapters:

        http://cvs.sourceforge.net/viewcvs.py/jicarilla/jicarilla-sandbox/platform/container/

        here is a "type-3" component factory:

        http://cvs.sourceforge.net/viewcvs.py/*checkout*/jicarilla/jicarilla-sandbox/platform/container/impl/src/java/com/leosimons/jicarilla/container/factories/Type3ComponentFactory.java?content-type=text%2Fplain&rev=1.6

        here's a testcase that shows it works:

        http://cvs.sourceforge.net/viewcvs.py/jicarilla/jicarilla-sandbox/platform/container/impl/src/test/com/leosimons/jicarilla/container/test/integration/pico/

        Jon also wrote: "The ComponentAdapterFactory is there for one reason only, to allow a PicoContainer to select a component-adapter to use when the user does not specify one. It's a completely different set of logic to doing that, for example the NanningComponentAdapterFactory introspects the component to see whether it's an aspect, a component that can get aspects applied to it, or a POJO (without intf/impl-separation). That's all different component adapters. There's no way I can put that logic somewhere else. "

        Why not? A ComponentAdapter selection policy should be a "policy-style" object, not a "factory-style" object. The key point is to decouple your policies from the "new" keyword (to me, "factory" is about calling the "new" keyword and doing any initialization).

        With the nanning example, in my chosen decomposition, I would actually much rather apply the policy you desribe at container population time (since the policy that should be applied is already known at that stage). Something like

        AspectSupportingContainerBuilder.add(componentKey, componentClass):
        if(component instanceof aspect)
        container.add(
        new EquivalenceSelector( componentKey ),
        new SingletonAdapter(
        new AspectFactory( componentClass )
        )
        )
        else if( canHaveAspectsApplied( component ) )
        container.add(
        new EquivalenceSelector( componentKey ),
        new SingletonAdapter(
        new AspectSupportingFactory( componentClass )
        )
        )
        else
        container.add(
        new EquivalenceSelector( componentKey ),
        new SingletonAdapter(
        new BasicType3Factory( componentClass )
        )
        )

        where you obviously will have a set of builders that reuses more code

        Jon also wrote: "I don't get the notion of a ComponentFactory. Isn't that a developer? How can someone other than a developer create a component?"

        I don't get your question! Re: the factory pattern. A factory creates instances of things. A component factory creates instances of components.

        I am sure there are also other possible decompositions that remove the parallel hierarchies which are probably less radical; I just haven't thought of any just yet.

        Show
        Leo Simons added a comment - Jon wrote: "I don't get it. How would you do it? Any chance you could describe it in more concrete terms?" Sure. How about some code? Take a look here: http://cvs.sourceforge.net/viewcvs.py/jicarilla/jicarilla-sandbox/platform/container/ here are some sample adapters: http://cvs.sourceforge.net/viewcvs.py/jicarilla/jicarilla-sandbox/platform/container/ here is a "type-3" component factory: http://cvs.sourceforge.net/viewcvs.py/*checkout*/jicarilla/jicarilla-sandbox/platform/container/impl/src/java/com/leosimons/jicarilla/container/factories/Type3ComponentFactory.java?content-type=text%2Fplain&rev=1.6 here's a testcase that shows it works: http://cvs.sourceforge.net/viewcvs.py/jicarilla/jicarilla-sandbox/platform/container/impl/src/test/com/leosimons/jicarilla/container/test/integration/pico/ Jon also wrote: "The ComponentAdapterFactory is there for one reason only, to allow a PicoContainer to select a component-adapter to use when the user does not specify one. It's a completely different set of logic to doing that, for example the NanningComponentAdapterFactory introspects the component to see whether it's an aspect, a component that can get aspects applied to it, or a POJO (without intf/impl-separation). That's all different component adapters. There's no way I can put that logic somewhere else. " Why not? A ComponentAdapter selection policy should be a "policy-style" object, not a "factory-style" object. The key point is to decouple your policies from the "new" keyword (to me, "factory" is about calling the "new" keyword and doing any initialization). With the nanning example, in my chosen decomposition, I would actually much rather apply the policy you desribe at container population time (since the policy that should be applied is already known at that stage). Something like AspectSupportingContainerBuilder.add(componentKey, componentClass): if(component instanceof aspect) container.add( new EquivalenceSelector( componentKey ), new SingletonAdapter( new AspectFactory( componentClass ) ) ) else if( canHaveAspectsApplied( component ) ) container.add( new EquivalenceSelector( componentKey ), new SingletonAdapter( new AspectSupportingFactory( componentClass ) ) ) else container.add( new EquivalenceSelector( componentKey ), new SingletonAdapter( new BasicType3Factory( componentClass ) ) ) where you obviously will have a set of builders that reuses more code Jon also wrote: "I don't get the notion of a ComponentFactory. Isn't that a developer? How can someone other than a developer create a component?" I don't get your question! Re: the factory pattern. A factory creates instances of things. A component factory creates instances of components. I am sure there are also other possible decompositions that remove the parallel hierarchies which are probably less radical; I just haven't thought of any just yet.
        Hide
        Leo Simons added a comment -

        I wrote: "With the nanning example, in my chosen decomposition, I would actually much rather apply the policy you desribe at container population time"

        Put another way, remove a lot of policy decisions from the container and encapsulate it in a helper object. You will then probably have a proliferation of different "builders" that apply different policies. So unless you add another layer of abstraction (something like policy objects), you will indeed still have sort-of parallel class hierarchies (one for each policy). But some things have been decoupled, so they will be smaller.

        Put yet another way, we've replaced inheritance with composition.

        Show
        Leo Simons added a comment - I wrote: "With the nanning example, in my chosen decomposition, I would actually much rather apply the policy you desribe at container population time" Put another way, remove a lot of policy decisions from the container and encapsulate it in a helper object. You will then probably have a proliferation of different "builders" that apply different policies. So unless you add another layer of abstraction (something like policy objects), you will indeed still have sort-of parallel class hierarchies (one for each policy). But some things have been decoupled, so they will be smaller. Put yet another way, we've replaced inheritance with composition.
        Hide
        Jon Tirsen added a comment -

        Got it! We simply use different naming:

        Pico Leo Simons
        ComponentAdapterFactory <=> ComponentFactorySelectionPolicy
        ComponentAdapter <=> ComponentFactory

        But in fact we have a very similar solution!

        Still don't really like the term ComponentFactory though, it sounds like something that creates components (not instances of components). It's like calling something a ClassFactory, it's something that creates classes, right?
        ComponentAdapter is to be interpreted as adapting different component-styles to PicoContainer, which is a much broader term. In fact the adapter doesn't necessarily have to create instances at all! It could for example look them up using JNDI or JAXR or whatever. Saying that something is a factory in that case constrains the implementation.

        So, simply put, there are no factories that creates instances of components in Pico, there are only adapters. For that reason, I don't think we should fix this issue.

        Show
        Jon Tirsen added a comment - Got it! We simply use different naming: Pico Leo Simons ComponentAdapterFactory <=> ComponentFactorySelectionPolicy ComponentAdapter <=> ComponentFactory But in fact we have a very similar solution! Still don't really like the term ComponentFactory though, it sounds like something that creates components (not instances of components). It's like calling something a ClassFactory, it's something that creates classes, right? ComponentAdapter is to be interpreted as adapting different component-styles to PicoContainer, which is a much broader term. In fact the adapter doesn't necessarily have to create instances at all! It could for example look them up using JNDI or JAXR or whatever. Saying that something is a factory in that case constrains the implementation. So, simply put, there are no factories that creates instances of components in Pico, there are only adapters. For that reason, I don't think we should fix this issue.
        Hide
        Jon Tirsen added a comment -

        Maybe we could rename ComponentAdapterFactory to ComponentAdapterSelectionPolicy though, it's a bit awkward though.

        Show
        Jon Tirsen added a comment - Maybe we could rename ComponentAdapterFactory to ComponentAdapterSelectionPolicy though, it's a bit awkward though.
        Hide
        Leo Simons added a comment -

        Jon wrote: "We simply use different naming <snip> But in fact we have a very similar solution!".

        I (still) don't think so. Implementations of ComponentAdapterFactory are tied to (specific, hard-coded) implementations of ComponentAdapter. Also, current implementations of Container are tied to specific implementations of a single ComponentAdapterFactory. Renaming them to "ComponentFactorySelectionPolicy" or anything else does not change that fact. Here's the terminology mapping as it applies to the code links I sent:

        Pico Me
        ------------------------------------------
        Component Service
        Component instance Component
        Container Container
        ComponentAdapter ComponentAdapter
        ComponentAdapterFactory <<unspecified>>
        <<unspecified>> Selector
        <<unspecified>> ComponentFactory

        There is overlap between "ComponentAdapterFactory" and "Selector", but the latter has a much smaller responsibility. I'll "speak pico" from now on

        Take a look at this code:

        public ComponentAdapter createComponentAdapter(Object componentKey, Class componentImplementation, Parameter[] parameters)
        throws PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException

        { return new CachingComponentAdapter(super.createComponentAdapter(componentKey, componentImplementation, parameters)); }

        I'm saying that should always be written as

        container.registerAdapter(
        new CachingComponentAdapter(
        new SomeOtherComponentAdapter(
        componentKey,
        componentImplementation,
        parameters
        )
        )
        ); // this code works with current MutablePicoContainer

        or better yet

        container.registerAdapter(
        componentKey, // the adapter doesn't need to care about
        // being subject to any kind of mapping
        new CachingComponentAdapter(
        new SomeOtherComponentAdapter(
        componentImplementation,
        parameters
        )
        )
        );

        The difference is relatively small from a user perspective but rather big from an implementation perspective (or from a power user perspective). This is because as a power user I cannot get away with simply writing a custom ComponentAdapter; I need to write a ComponentAdapterFactory as well. Furthermore, it is difficult to mix and match multiple ComponentAdapterFactories (it requires adding scoped/hierarchical containers).

        Truly decoupling ComponentAdapterFactory from ComponentAdapter and removing some of its current responsibilities (removing them from PicoContainer and references alltogether, in fact) is the only way I've thought of so far that changes this.

        "simply put, there are no factories that creates instances of components in Pico, there are only adapters."

        that is a Good Thing. But there's factories that create instances of adapters, and pico assumes that there are. I'm not saying adapters must neccessarily use factories; I'm saying PicoContainer (implementations) shouldn't use them either.

        I hope I'm making at least some kind of sense to you. Maybe Thomas explains it better @

        http://lists.codehaus.org/pipermail/picocontainer-dev/2003-November/001626.html

        Show
        Leo Simons added a comment - Jon wrote: "We simply use different naming <snip> But in fact we have a very similar solution!". I (still) don't think so. Implementations of ComponentAdapterFactory are tied to ( specific , hard-coded ) implementations of ComponentAdapter. Also, current implementations of Container are tied to specific implementations of a single ComponentAdapterFactory. Renaming them to "ComponentFactorySelectionPolicy" or anything else does not change that fact. Here's the terminology mapping as it applies to the code links I sent: Pico Me ------------------------------------------ Component Service Component instance Component Container Container ComponentAdapter ComponentAdapter ComponentAdapterFactory <<unspecified>> <<unspecified>> Selector <<unspecified>> ComponentFactory There is overlap between "ComponentAdapterFactory" and "Selector", but the latter has a much smaller responsibility. I'll "speak pico" from now on Take a look at this code: public ComponentAdapter createComponentAdapter(Object componentKey, Class componentImplementation, Parameter[] parameters) throws PicoIntrospectionException, AssignabilityRegistrationException, NotConcreteRegistrationException { return new CachingComponentAdapter(super.createComponentAdapter(componentKey, componentImplementation, parameters)); } I'm saying that should always be written as container.registerAdapter( new CachingComponentAdapter( new SomeOtherComponentAdapter( componentKey, componentImplementation, parameters ) ) ); // this code works with current MutablePicoContainer or better yet container.registerAdapter( componentKey, // the adapter doesn't need to care about // being subject to any kind of mapping new CachingComponentAdapter( new SomeOtherComponentAdapter( componentImplementation, parameters ) ) ); The difference is relatively small from a user perspective but rather big from an implementation perspective (or from a power user perspective). This is because as a power user I cannot get away with simply writing a custom ComponentAdapter; I need to write a ComponentAdapterFactory as well. Furthermore, it is difficult to mix and match multiple ComponentAdapterFactories (it requires adding scoped/hierarchical containers). Truly decoupling ComponentAdapterFactory from ComponentAdapter and removing some of its current responsibilities (removing them from PicoContainer and references alltogether, in fact) is the only way I've thought of so far that changes this. "simply put, there are no factories that creates instances of components in Pico, there are only adapters." that is a Good Thing. But there's factories that create instances of adapters, and pico assumes that there are. I'm not saying adapters must neccessarily use factories; I'm saying PicoContainer (implementations) shouldn't use them either. I hope I'm making at least some kind of sense to you. Maybe Thomas explains it better @ http://lists.codehaus.org/pipermail/picocontainer-dev/2003-November/001626.html
        Hide
        Jon Tirsen added a comment -

        I understand.

        The reason why we keep around ComponentAdapterFactories for a number of component-adapters is because the casual user should not have to worry about ComponentAdapters. In fact we initially only considered ComponentAdapter to be an implementation detail.

        What you suggest is to remove all registerComponentImplementation and replace them by one single registerComponentAdapter(ComponentAdapter).

        We already have a registerComponentAdapter(ComponentAdapter). It would be possible to remove all other methods, but I think for the non-casual user they make the API easier to use. Maybe we should just keep a small number of ComponentAdapterFactories around and let all power-users use registerComponentAdapter.

        That ComponentAdapter knows what key it has is probably one of the main differences with ComponentFactory and ComponentAdapter, the ComponentAdapter actually keeps it's state around. That the container maps it just a performance detail, an index.

        Show
        Jon Tirsen added a comment - I understand. The reason why we keep around ComponentAdapterFactories for a number of component-adapters is because the casual user should not have to worry about ComponentAdapters. In fact we initially only considered ComponentAdapter to be an implementation detail. What you suggest is to remove all registerComponentImplementation and replace them by one single registerComponentAdapter(ComponentAdapter). We already have a registerComponentAdapter(ComponentAdapter). It would be possible to remove all other methods, but I think for the non-casual user they make the API easier to use. Maybe we should just keep a small number of ComponentAdapterFactories around and let all power-users use registerComponentAdapter. That ComponentAdapter knows what key it has is probably one of the main differences with ComponentFactory and ComponentAdapter, the ComponentAdapter actually keeps it's state around. That the container maps it just a performance detail, an index.
        Hide
        Leo Simons added a comment -

        Jon wrote: "I understand".

        good! And your decision is to keep things as-is to support the more common use cases better by default, right? Okay. Makes sense. Annoying for me, but makes sense

        Show
        Leo Simons added a comment - Jon wrote: "I understand". good! And your decision is to keep things as-is to support the more common use cases better by default, right? Okay. Makes sense. Annoying for me, but makes sense
        Hide
        Jon Tirsen added a comment -

        Yeah, but it's not my decision only.

        I get your point, although it's much to close to a release to do such a massive overhaul. I will try to take a serious look at the other issue though, the Adapter -> Registry thing: PICO-90.

        Show
        Jon Tirsen added a comment - Yeah, but it's not my decision only. I get your point, although it's much to close to a release to do such a massive overhaul. I will try to take a serious look at the other issue though, the Adapter -> Registry thing: PICO-90 .
        Aslak Hellesøy made changes -
        Field Original Value New Value
        Assignee Jon Tirsen [ tirsen ]
        Fix Version/s 2.0 [ 10411 ]
        Hide
        peter royal added a comment -

        Is this closeable?

        I think Leo's use-case can be achieved with MutablePicoContainer.registerComponent( ComponentAdapter ). As for addditional design ideas, at this point in time in Pico's life I'm not sure if its a pragmatic decision.

        Show
        peter royal added a comment - Is this closeable? I think Leo's use-case can be achieved with MutablePicoContainer.registerComponent( ComponentAdapter ). As for addditional design ideas, at this point in time in Pico's life I'm not sure if its a pragmatic decision.
        Hide
        Jörg Schaible added a comment -

        That's the reason for the 2.0 schedule. Here we collect any ideas, that we should consider for the next incarnation.

        Show
        Jörg Schaible added a comment - That's the reason for the 2.0 schedule. Here we collect any ideas, that we should consider for the next incarnation.
        Jörg Schaible made changes -
        Assignee Jon Tirsen [ tirsen ]
        Hide
        Jörg Schaible added a comment -

        I don't think Jon will do anything with it

        Show
        Jörg Schaible added a comment - I don't think Jon will do anything with it
        Michael Rimov made changes -
        Fix Version/s 2.0 [ 10411 ]
        Fix Version/s 2.2 [ 14251 ]
        Michael Rimov made changes -
        Fix Version/s 2.3 [ 14303 ]
        Fix Version/s 2.2 [ 14251 ]
        Michael Rimov made changes -
        Fix Version/s 2.4 [ 14362 ]
        Fix Version/s 2.3 [ 14303 ]
        Michael Rimov made changes -
        Fix Version/s 2.4 [ 14362 ]
        Fix Version/s 2.5 [ 14424 ]
        Paul Hammant made changes -
        Fix Version/s 2.5 [ 14424 ]
        Fix Version/s 3.0 [ 14410 ]

          People

          • Assignee:
            Unassigned
            Reporter:
            Leo Simons
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:

              Time Tracking

              Estimated:
              Original Estimate - 2 days
              2d
              Remaining:
              Remaining Estimate - 2 days
              2d
              Logged:
              Time Spent - Not Specified
              Not Specified