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.


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?

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.


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.

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.


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.

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.

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.


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*.

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.

--
Stefano.



Reply via email to