Re: [RT:A5] What are the _real_ concern areas?
Adam Murdoch <[email protected]>
| Newsgroups | gmane.comp.jakarta.avalon.apps.devel,gmane.comp.jakarta.avalon.devel |
|---|---|
| Message-ID | <[email protected]> |
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