cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Reinhard Poetz <reinh...@apache.org>
Subject Re: [RT] composition vs. inheritance in blocks
Date Sat, 02 Apr 2005 14:57:47 GMT
Daniel Fagerstrom wrote:

> Reinhard Poetz wrote:
 >
>> I think I got the idea. Personally, I would solve this by composition; 
>> the profiling is just another reference of the block. Of course, as 
>> Stefano said, blocks have to be designed for this. If the application 
>> block hasn't factored out the profile aspect, then you can't replace 
>> implementation A by implementation B:
>>
>> --------------------------------------------------------------------------- 
>>
>> "Application block" (to be reused in many projects)
>> --------------------------------------------------------------------------- 
>>
>> <block xmlns="http://apache.org/cocoon/blocks/cob/1.0"
>>  id="http://mycompany.com/blocks/application-block/1.0.0">
>>   <name>Application block</name>
>>   <requirements>
>>     <requires interface="http://mycompany.com/interfaces/skin/1.0"
>>       name="skin"
>>       default="http://mycompany.com/blocks/myskin/1.0.0"/>
>>     <requires interface="http://cocoon.apache.org/interfaces/profile/1.0"
>>       name="profile"
>>       default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
>>     <requires interface="http://cocoon.apache.org/interfaces/portal/1.0"
>>       name="portal"
>>       default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
>>   </requirements>
>> </block>
> 
> 
> It would work, but to achieve what I did in a few sitemap lines (above) 
> you have to create and deploy a special MyProfile block, and as I just 
> wanted to override a few things in my customized profile I have to 
> extend the original profile from the Portal block in some way. In the 
> sitemap in MyProfile you have to write  a copy of the profiles section 
> in the Portal block:
> 
> MyProfile sitemap
> -----------------
> 
> ...
> <profiles>
> <copletbasedata-load 
> uri="blocks:/profile/load-global-profile?profile=copletbasedata"/>
> <copletdata-global-load 
> uri="blocks:/profile/load-global-profile?profile=copletdata"/>
> <copletdata-role-load 
> uri="blocks:/profile/load-role-profile?profile=copletdata"/>
> <copletdata-user-load 
> uri="blocks:/profile/load-user-profile?profile=copletdata"/>
> <copletinstancedata-global-load 
> uri="blocks:/profile/load-global-profile?profile=copletinstancedata"/>
> <copletinstancedata-role-load 
> uri="blocks:/profile/load-role-profile?profile=copletinstancedata"/>
> <copletinstancedata-user-load 
> uri="blocks:/profile/load-user-profile?profile=copletinstancedata"/>
> <copletinstancedata-user-save 
> uri="blocks:/profile/save-user-profile?profile=copletinstancedata"/>
> <layout-global-load 
> uri="blocks:/profile/load-global-profile?profile=layout"/>
> <layout-role-load uri="blocks:/profile/load-role-profile?profile=layout"/>
> <layout-user-load uri="blocks:/profile/load-user-profile?profile=layout"/>
> <layout-user-save uri="blocks:/profile/save-user-profile?profile=layout"/>
> </profiles>
> ...
> <pipeline>
>  <match pattern="profile/load-user-profile">
>   ...
> </match>
> 
> Here the blocks protocol is supposed to first look in the own sitemap 
> (and find "load-user-profile") before it asks the super block. As you 
> can see you need a copy of the profiles section of the sitemap booth in 
> Portal and in MyProfile. 

No, you don't. The block "portal" only contains pipelines calls which the block 
"profile" provides in its sitemap:

Portal block
------------
- requires "MyProfile" that implements "profile"

<profiles>
  <copletbasedata-load
   uri="blocks:profile:/load-global-profile?profile=copletbasedata"/>
  <copletdata-global-load
   uri="blocks:profile:/load-global-profile?profile=copletdata"/>
   ..
</profiles>

(After reading Stefano's mail I found a mistake in my examples: The block name 
is a sub-protocol and not part of the path.)

You can have one block that provides default pipelines, let's call it 
"profile-default":

"profile-default"
-----------------
- implements "profile"

<map:match pattern="load-global-profile">
  ...
</map:match>


If you need special behaviour in your _custom_ profile block, you extend the 
"profile-default" block and override _only_ those parts that you want to see 
changed. In your custom portal block ("MyPortal") you change the requirement 
from "profile-default" to "MyProfile" and you're done.

"MyProfile"
-----------
- extends "profile-default"


> And you define and deploy small blocks for each 
> aspect you want to override in the original block. The behaviour of an 
> actual application will be spread out in all these small extended 
> blocks. I found it clumsy, complicated and burocratic compared to the 
> simple sitemap based extension mechanism I used in my example.

IIUC your point is that how your application works is spread over to many 
blocks. Maybe you're right but I don't think so, at least the examples we talked 
about here are overly complex using


>> What I do like is <mount uri-prefix="" src="blocks://portal"/> which 
>> makes it explicit what a block exports. I'm not sure about why this 
>> has to be a "two way contract".
> 
> 
> It must be a two way contract, as "extend and overide" is a two way 
> contract. If you have it one way you end up having to copy everything 
> that is an agregate of functionality as in the profile example above.

Yes, if something isn't designed for reusability, then you have to copy it. If 
you design for reusablity, you don't have to copy services.

> For another example of two way communication beween a default sitemap 
> and an extending one you can look at Forrest which make good use of 
> allowing user extensions 
> (http://forrest.apache.org/docs/dev/sitemap-ref.html, 
> http://svn.apache.org/viewcvs.cgi/forrest/trunk/main/webapp/sitemap.xmap?view=markup

> e.g.).
> 
> Here is an example:
> 
>    <map:resource name="skinit">
>        <map:select type="exists">
>          <map:when 
> test="{project:skins-dir}{forrest:skin}/xslt/html/{type}.xsl">
>            <map:transform 
> src="{project:skins-dir}{forrest:skin}/xslt/html/{type}.xsl">
>              <map:parameter name="notoc" value="{notoc}"/>
>              <!-- For backwards-compat with 0.2 - 0.4 skins -->
>              <map:parameter name="isfaq" value="{notoc}"/>
>              <map:parameter name="nopdf" value="{nopdf}"/>
>              <map:parameter name="path" value="{path}"/>
>              <map:parameter name="config-file" value="{project:skinconf}"/>
>            </map:transform>
>          </map:when>
>          <map:otherwise>
>            <map:transform 
> src="{forrest:context}/skins/{forrest:skin}/xslt/html/{type}.xsl">
>              <map:parameter name="notoc" value="{notoc}"/>
>              <!-- For backwards-compat with 0.2 - 0.4 skins -->
>              <map:parameter name="isfaq" value="{notoc}"/>
>              <map:parameter name="nopdf" value="{nopdf}"/>
>              <map:parameter name="path" value="{path}"/>
>              <map:parameter name="config-file" value="{project:skinconf}"/>
>            </map:transform>
>          </map:otherwise>
>        </map:select>
>      <map:serialize/>
>    </map:resource>
> 
> if the style sheet exists in the extending directory given by 
> "project:skins-dir" it is used otherwise the default variant from 
> "forrest:context" is used. As you see it is a two way communication 
> there Forrest need explicit info about the loocation of the extending 
> application at deploy time. With the mechanism I propose we would get:
> 
> Forrest block sitemap
> ---------------------
> 
>    <map:resource name="skinit">
>      <map:transform 
> src="blocks:/skins/{forrest:skin}/xslt/html/{type}.xsl">
>      <map:parameter name="notoc" value="{notoc}"/>
>      <!-- For backwards-compat with 0.2 - 0.4 skins -->
>      <map:parameter name="isfaq" value="{notoc}"/>
>      <map:parameter name="nopdf" value="{nopdf}"/>
>      <map:parameter name="path" value="{path}"/>
>      <map:parameter name="config-file" value="{project:skinconf}"/>
>      </map:transform>
>      <map:serialize/>
>    </map:resource>
> 
> instead and could use Forrest from our own app with:
> 
> Own documentation pipeline
> --------------------------
> 
> <pipeline>
>  <match pattern="skins/common/xslt/html/document2html.xsl">
>    ...
>  </match>
> 
>  <mount uri-prefix="" src="blocks://forrest"/>
> </pipeline>
> 
> where mount:/ gives polymorphic lookup. The advantage, except for being 
> terser, with this solution compared to the one Forrest use today for the 
> block context is that the Forrest block doesn't need to know any 
> explicit paths to the extending application wich is especially important 
> if you want to use the same block in several contexts.

The same you reach if everything that should be extensible in Forrest, is moved 
into a "default-block". This can be overridden by a project specific block.

> 
>> If you write <copletdata-role-load 
>> uri="blocks:/load-role-profile?profile=copletdata"/> then it means 
>> that you make  it explicit what can be overriden and what not.
>> I would use <copletdata-role-load 
>> uri="blocks:/profile/load-role-profile?profile=copletdata"/> which 
>> requires another reference. Using the proposed <mount>-mechanism, you 
>> can reuse the "portal"-pipeline.
> 
> 
> I'm not certain that I follow you. I would (possibly) use:
> 
>  blocks://profile/load-profile
> 
> for refering to another block, and:
> 
>  blocks:/load-profile
> 
> for refering to the same block possibly with sub protocols:
> 
>  blocks:polymorph:
> 
> if we want to be explicit about when something can be looked up from an 
> extending sitemap
> 
>  blocks:super:
> 
> for refereing to the extended block.
> 
> A problem with blocks://profile, rather than blocks:profile// is if one 
> want to access the "root" block sitemap from a sub sitemap, but that 
> could maybe be done with blocks://this/.

Sorry, my usage of the blocks protocal was wrong in my former mails. I'm with 
Stefano here that the block is a sub-protocol and additionally we have a "super" 
sub-protocol referencing the parent block.

> 
>>                                        - o -
>>
>> I think for now we shoudn't support these two-way contracts but favour 
>> composition by references (incl. <map:mount uri="[another-block]"/>). 
>> If this gets too bureaucratic, we can still think about alternatives.
>>
>> WDYT?
> 
> 
> I think that it becomes to bureaucratic. But you maybe have some better 
> solutions to the issues I adress above. 

I think we should stop the discussion at this point for now (I don't think that 
anybody can follow it anymore). IIUC everything you proposed can be added in a 
backwards-compatible way to the existing proposal. But I'm confident we don't 
need it ;-)

> Also, I'm intersted in your view 
> about how to actually use the blocks in an application.

I can think of many scenarios where blocks make sense. We already talked abut 
them in this discussion (skinning, profiles, easy deployment, easy development, 
...). Does this answer your question or is it too general.

-- 
Reinhard Pötz           Independent Consultant, Trainer & (IT)-Coach 

{Software Engineering, Open Source, Web Applications, Apache Cocoon}

                                        web(log): http://www.poetz.cc
--------------------------------------------------------------------


Mime
View raw message