On Thu, 19 Dec 2002 10:23 am, Greg Steuck wrote:
> >>>>> "Adam" == Adam Murdoch <[EMAIL PROTECTED]> writes:
> >> Currently it is very easy to extend AbstractLogEnabled to have
> >> logging established. Can we achieve the same level of convenience
> >> with the new approach?
>
> Adam> Yep, using something like this:
>
> Adam> public abstract class AbstractConfigurable implements
> Adam> Configurable { public final configure( ConfigurationManager
> Adam> configMgr ) { m_configMgr = configMgr; configure(); }
>
> Adam> protected Logger getLogger() { ... } protected
> Adam> Configuration getConfiguration() { ... } protected Context
> Adam> getContext() { ... } ... etc ...
>
> Adam> protected void configure() // Could make this abstract { }
> Adam> }
>
> Adam> Where subclasses implement configure(), rather than
> Adam> configure(ConfigurationManager), if they need to do any
> Adam> configuring.
>
> So I have to keep in mind 2 different contracts in place of 1?
1 contract? Where do you get that from?
There are 5 resource delivery interfaces in framework: LogEnabled,
Contextualizable, Configurable, Parameterizable, Serviceable.
That's 5 contracts, plus on top of that the component writer has to understand
the ordering of each of these lifecycle methods, relative to each other.
Then you've also got whatever contracts your convenience superclass happens to
add. Yes, AbstractLogEnabled *does* impose a second contract on its
subclasses: getLogger() cannot be called until enableLogging() has been
called.
Semantically, the contract that (the example) AbstractConfigurable imposes on
its subclasses is the same as that imposed by AbstractLogEnabled. It's more
general, yes. So, if loggers were the only resource that containers
delivered to components, then, yes, LogEnabled and AbstractLogEnabled would
arguably be a better option.
But loggers aren't the only type of resource. Not by a long shot.
Let's look at what the component writer has to keep track of when the other
resource types are considered:
- A Logger is delivered by LogEnabled.enableLogging(), and is the first
lifecycle method called.
- A Context is delivered by Contextualizable.contextualize(), and is the
second lifecycle method called.
- A ServiceManager is delivered by Serviceable.service(), and is the third
lifecycle method called.
- Configuration is delivered by Configurable.configure(), and is the fourth
lifecycle method called.
- Parameters are delivered by Parameterizable.parameterize(), and is the fifth
lifecycle method called.
Compare this with:
- All resources are delivered by Configurable.configure().
So, how is this second case more complex for a component writer? What benefit
do they get out of having 5 resource delivery methods?
Let's, for argument's sake, add AbstractConfigurable into the mix:
- All resources are delivered by Configurable.configure( configMgr ).
- All resources are available when AbstractConfigurable.configure() is called.
Still comes out ahead. However, consider the fact that second contract says
"all resources". Everything a component needs is available when
AbstractConfigurable.configure() is called. In other words, the component
writer doesn't need to care about the first contract. It can be ignored when
using AbstractConfigurable.
This is *not* true of LogEnabled/AbstractLogEnabled.
> Looking at this from container perspective doesn't make things
> prettier. In place of:
>
> ContainerUtil.enableLogging(component, logger);
> ContainerUtil.contextualize(component, context);
> ContainerUtil.configure(component, configuration);
> ContainerUtil.parameterize(component, parameters);
>
> we will be doing something like:
>
> ConfigurationManager configManager =
> new BundleConfigurationManager(logger, context, configuration,
> parameters); ContainerUtil.megaConfigure(configManager, component);
Indeed. Of course, BundleConfigurationManager would be a convenience
implementation provided by a toolkit (probably the same one providing
ContainerUtil). Or maybe we'd just add a convenience method:
ContainerUtil.configure( component, logger, context, configuration, params );
Honestly, arguments that use the impact on container implementors as a
justification an abstraction in framework, take a very distant second place
to arguments that focus on the impact on the component writer.
There are orders of magnitude more components than there are containers.
Let's figure out how to make it as easy as possible to write a component.
Once that's sorted, then we can start compromising framework for the sake of
the container writers. Let's not do this prematurely.
> The new way seems to be more work for the container since it has no way
> to find out if the component needs any of the objects above.
Why is this a problem? Why can't it be addressed in meta-info *if* it becomes
an issue? We're already looking at meta-info that describes what a component
expects to be exposed via ServiceManager and Context. Argueably the same
will happen for config and params (if not in meta-info, then meta-data).
Given that the component's meta-info describes exactly what resources the
component does and does not need, the container will easily be able to create
only those resources the component will actually use.
> It also
> requires an extra ConfigurationManager instance per component (which was
> not needed at all before).
What's an extra object? If the container is concerned about object counts, it
can use the same object to implement ConfigurationManager and Context (which
is almost always per-component) and/or ServiceManager (which is often
per-component).
> Adam> With this kind of approach, we not only have an equivalent of
> Adam> AbstractLogEnabled, but we also handle AbstractConfigurable,
> Adam> AbstractContextualizable, AbstractParameterizable, etc, all in
> Adam> one.
>
> If I wanted all in one, I would just build an AbstractEverything which
> implements all our current interfaces.
True.
My point was that an 'AbstractEverything' naturally falls out of having a
single resource delivery lifecycle method. There's no need for more than one
abstract convenience class.
> Look at it from a different perspective: I just lost granularity.
> Previously I could build things gradually by implementing standard
> interfaces. Now I have to invent new interfaces to achieve the same
> level of granularity.
What are the new interfaces would you need to invent?
> I guess it boils down to:
> * my feeling that granularity is good
This is SoC.
But you can slice and dice too finely, which gives you Fragmentation of
Concerns. And this is exactly what has happened with framework's resource
delivery (I suspect, I don't know for sure). The real concern - resource
delivery - has been fragmented across 5 artificial concerns - log enabling,
contextualising, configuring, servicing, and parameterising. Artificial
concerns always cause grief. We're seeing this right now.
To put fragmentation of concerns more concretely:
Why is it better, from a component writer's PoV, to implement separate
enableLogging(), contextualize(), configure(), service(), and parameterize()
methods, when a single configure() method does just as good a job?
> * my distaste for complex class hierarchies
Can you do a bit of a compare-and-contrast for us? Perhaps you could
demonstrate the hierarchy when resources are delivered via 5 methods, vs the
hierarchy when resources are delivered via 1 method.
--
Adam
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>