cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Peter Hunsberger <peter.hunsber...@gmail.com>
Subject Re: [RT] composition vs. inheritance in blocks
Date Wed, 30 Mar 2005 16:08:40 GMT
On Tue, 29 Mar 2005 19:36:33 -0500, Stefano Mazzocchi
<stefano@apache.org> wrote:
> Peter Hunsberger wrote:
> 
> >>I would go a little further and say that I believe inheritance is useful
> >>only when used as a 'cascading' mechanism, in any other sense is
> >>harmful: composition should be used instead.
> >>
> >>Why so?
> >>
> >>It's extremely hard to design for inheritance, a lot easier to design
> >>for composition.
> >
> >
> > That's FUD: a graph is a graph is a graph.
> 
> BS. All graphs are graphs, but the 'properties' of graphs very a lot
> between them and there is a pretty good literature about how different
> 'local properties' in graph growth yield global properties.

Most of the significant differences are best described as the result
of default node exposure vs. default node hiding.  Composition can
only default to hiding, inheritance can do either.  As such, simply
saying that composition is better than inheritance is FUD, it's how
you traverse the graph that matters and what matters for Cocoon blocks
is that a graph is a graph is a graph.

A good traversal mechanism is going to solve the issues at hand much
better than trying to figure out whether that traversal mechanism
shares more properties with the concepts of inheritance or
composition.

xPath comes to mind...

> > The only question is how to expose or hide child nodes.
> 
> Yep, this is *exactly* the local property I'm talking about above, that
> yields very different dependencies graphs.
> 
> > With composition there are no child
> > nodes until you add them.
> 
> Yep.
> 
> > With inheritance the visibility depends on
> > the language and the implementor.
> 
> Absolutely. And look at what happens with our classes: we call something
> public, just because we want another package to have access to it, and
> this is automatically exposed to somebody that later on wants to extend
> it, even if we did not meant it to be public for that use!!
> 
> Sure, we could keep adding 'visibility' stati to sitemap URLs (we
> already have two: internal and external) but what is the gain?

Composition doesn't solve this problem. If you need public access to a
composed method the problem starts all over again. The problem with
Java is how you traverse the nodes (using public/private/etc.), not
inheritance.

> 
> >>And performing composition thru multiple-inheritance is a really
> >>terrible way to do it. Why? because you need to go very deep in
> >>describing what behaviors get exposed and what are protected, otherwise
> >>things get messy very fast.
> >
> > Also FUD, again it depends on the language and the implementor.
> 
> Again, BS. MI would force us to deal with visibility metadata and back
> compatibility would force us to default to 'external', meaning that only
> those 'aware' of what they are doing will keep things internal unless
> really required to open it up, and the 'public' java identifier problem
> will happen all over again.

No that's not BS: properly used MI doesn't _force_ you to do anything
more than composition. It allows it, but doesn't force it. MI provides
a superset of capabilities WRT composition.  One can construct exactly
the same graphs using a constrained MI as with composition, the
inverse is not true (for normalized structures).
 
> >>Also, multiple-inheritance stops further composition: you seem to
> >>suggest that a block that inherit multiply is a 'leaf block', one that
> >>is your own application and that will not be used by others.
> >>
> >>Well, one of the reasons for block design was to sparkle the creation of
> >>reusable application components without people to think much about them:
> >>multiple-inheritance yields horizontal behavior changes to the inherited
> >>components, which means that if I have block A inherit block B and then
> >>block C that wants to use A but was already using B, but A modified B's
> >>behavior a little with the inheritance, you have a problem!
> >
> >
> > Only if you design things to allow the problem in the first place.  If
> > I'm using both blocks to start with I can specify A.foo() or B.foo()
> > and pick or choose which version I want.  If I start out using only B
> > and later add in A and find out I have a name space collision on foo()
> > then I have an issue.
> >
> > That's where the real problem lies with Cocoon blocks: unless the
> > references are resolved at startup then you have the _potential_ for
> > obscure run time problems as blocks get resolved dynamically.  Since
> > you may not want to do startup resolution on the entire graph, I think
> > the way for Cocoon to support multiple inheritance is for the default
> > behaviour to be to hide all inherited capabilities and require
> > explicit exposure or to always require complete paths to the function.
> 
> Exactly, and this behavior is back incompatible.

There's nothing to be back incompatible with....   Given that the
resultant property exposure can be exactly the same as with
composition you're not making any sense.

> > IOW, in this example , the only way to see the A version of foo()
> > would be an explicit reference to A.foo(), otherwise the reference
> > would fall back to B.
> >
> >
> >>I am strongly against multiple implementation inheritance for blocks,
> >>because what you want to do, if you care about reusability, is really
> >>multiple composition and that is achieved with being allowed to
> >>implement multiple behavioral interfaces.
> >
> >
> > Given that this is more-or-less completely new territory, the terms
> > inheritance and composition seem to be getting in the way.  The real
> > issue is how does the block language expose features from each block.
> > You're describing the language as implementing "multiple behavioral
> > interfaces"; from my backgrounds POV the easiest way to do that is
> > multiple inheritance.
> 
> I completely agree that it's the easiest way. My avalon experience shows
> me that composition yields more separate and much easier reusable
> components.

That suggests to me that avalon didn't have good ways of working with
MI, nothing more...

> It's very true this is unexplored terryrory, but my gut feeling tells me
> this is the case for webapp components as well. And, as I said, I will
> stick to this until proven wrong.

Here's the issue, properly done:

- composition yields a bunch of disjoint graphs and relies on a
grammar to move from one graph to another, and another grammar to move
from one node to another.

- MI builds a single normalized graph and uses a single grammar to
move from one node to another.  A separate grammar is required to
build the normalized graph from disjoint components.

IOW; where do you want to pay the price for the second grammar? At the
time you smash the blocks together, or at the time you want to use the
functions in a block?  Do you smash the blocks together more often
than you use functions across the blocks or vice versa?

Frankly, I don't know the answer to that one.

We do know that Cocoon has mechanisms for aggregation already
existing: smashing a bunch of block descriptors together in a pipeline
is trivial.  Using XSLT  to extract the results is likely somewhat
tricky work.  However, once you've got the results, an in memory DOM
like structure traversed via xpath for feature extraction is straight
forward programming.

> 
> >>If you *don't* care for reusability, then it's true that multiple
> >>implementation inheritnace can serve as a cheaper form of composition.
> >>
> >>But if I had to pick between improving block reusability or ease of
> >>composition, I would go with the first, hoping that tools/syntax-sugar
> >>would help the second (as it happened with Java).
> >
> > Well, that's almost all that it is about in this case: pure syntax.
> > If you think about what's going to have to happen under the covers the
> > end result is the same either way: you need a language to describe
> > which features are visible from one block to another.  If there's a
> > term that is an abstraction for both inheritance and composition then
> > that's what you reallly want...
> 
> that's hiding the problem under the carpet.
> 
> Polymorphic Composition is not the same as multiple inheritance even if
> they might be used to achieve the same results.
> 
> The real issue is another one and you both seem to be missing it
> entirely: multiple inheritance is easier to use, because doesn't force
> the block providers to define (and think about!) the contract between
> you and the services you require to achieve your task.

Only if you let it (default feature exposure)...

> 
> This is both its advantage and its ultimate weakness: the system is way
> more fragile because the contracts between the two block sare 'implitic'.
> 
> So, when you do
> 
>           +-(inherits from)-> block B
>          /
>  block A
>          \
>           +-(inherits from)-> block C
> 
> what you are really stating is
> 
>           +-(requires)-> service 1 <-(implements)- block B
>          /
>  block A
>          \
>           +-(requires)-> service 2 <-(implements)- block C
> 
> in java interfaces at least have method signatures that can be checked
> at compile time, Cocoon blocks won't have anything like that (the first
> that mentions BPEL will be shot!) because it's simply too hard (and
> cumbersome and useless in a local environment) to describe the behavior
> of a (potentially stateful!) service.

Unless you're going to write the language (BPEL or otherwise), how are
you going to get any better contracts with composition? You might have
jumped straight to your second diagram, but so what?  Unless you've
written the "requires" and "implements" contracts you haven't achieved
anything.  If you have written those contracts then they are the nodes
of your graphs and you're back to the issues I give above: how do you
want to smash the nodes of the graph together and how do you want to
traverse them?

> In such a 'weakly typed' environment, allowing people to get away with
> even "defining" what is the service (interface) that a block implements
> in order to allow service composition is suicidal.
> 
> I don't want it to be easy to 'reporpuse' existing blocks (not designed
> to be extended or used as services!!), I want it to be *HARD*.

What about blocks designed to be used as services?  Do you want _two_
different mechanisms for dealing with blocks?  Surely not?

> I want it to be dead easy to write 'leaf blocks', those that you mount
> to the public and you expose as your own stuff, but as for writing
> blocks that are meant to be reused, this is another story and this
> requires you to go to the process of managing that contract between what
> you implement and what you exhibit.
> 
> And you get polymorphism for free if you do that, but polymorphism is
> not the main advantage of this: interfaces, just like in java, make
> programs more reusable and solid because they create 'awareness' of what
> is the contract between your code and the user's.
> 
> If we lose that, we are doomed to have a million blocks, and people that
> keep cutting and pasting between them to achieve what they want and
> avoid depending on somebody elses because the contracts change too fast.
> 
> Sure, unlike java, our dependencies are versioned, so some of that
> problem goes away, but the awareness of contract management is what I
> want and MI kills all that.

Oh, good grief, that's simply not true (and I'm pretty sure you know
it).  Sure you can screw up contract management with MI.  You can also
screw up contracts with composition. You can also make contract
management easier with MI than with composition.

Instead of arguing MI vs. composition figure out what it really means
to do contract discovery and validation: if it's something you need to
do multiple times then you want a normalized graph.  If it's something
you rarely need to do then save the resources required to do
normalization and use disjoint graphs instead.

 -- 
Peter Hunsberger

Mime
View raw message