Moldable Collection Wrapper

Context

Within your application domain, you not only have to deal with individual domain objects, but also composite entities ( e.g. , a book of notebook pages, a website of web pages), and collections of entities ( e.g. , the result of query).

Problem:

How can you effectively provide custom tools for various kinds of collections of domain entities?

Forces

— Collections are generic data structures with generic views that are not necessarily informative for your domain.

— Collections of domain entities may occur in various forms and contexts, such as the state of a composite object, or the result of a query.

— Providing custom views for each of these can be tedious and lead to much duplicated code.

Solution

Wrap collections of domain objects in a dedicated group wrapper, and give it dedicated views, actions and searches.

Steps

In case there are multiple composite entities or collections that should share the same custom tools, factor these out into a common abstract superclass or trait.

Examples

Below we see that when we navigate to the Pages of a website, we obtain not a raw collection, but a WebPageGroup wrapping the collection, and providing custom tools, such as a map of reachable and unreachable pages. Similarly, a query for pages matching a particular string will also return a wrapped group with dedicated tools.

Furthermore, if the wrapped collection or composite object should also behave like a collection, use a trait to forward collection API requests to the underlying collection. In this example, WebPageGroup Object subclass: #WebPageGroup uses: TGtGroupWithItems + TWebPageGroup instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-WebsiteExplorer-Model' uses the trait TGtGroupWithItems Trait named: #TGtGroupWithItems uses: TGtGroup + TGtGroupItems instanceVariableNames: '' package: 'GToolkit-Utility-System' that forwards all collection requests to the items slot of the wrapper that holds the raw collection.

Consequences

Composite domain objects and collections of domain objects can be molded to support custom tools. Query results can be similarly molded. Wrapped collections can be made plug-compatible with raw collections.

Known Uses

Lifeware uses Moldable Collection Wrappers to wrap SQL query results so the query results can be molded. There are about two dozen usages with GT, wrapping collections of notebook pages, web pages, logging events, and various other kinds of domain entities.

Related patterns

Whenever you develop a Custom Search that returns a collection, consider wrapping it as a Moldable Collection Wrapper.

GT implementation notes

Create a dedicated wrapper for a collection of domain entities. An example is WebPageGroup Object subclass: #WebPageGroup uses: TGtGroupWithItems + TWebPageGroup instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-WebsiteExplorer-Model' , which wraps a collection web pages.

Have the collection wrapper use the TGtGroupWithItems Trait named: #TGtGroupWithItems uses: TGtGroup + TGtGroupItems instanceVariableNames: '' package: 'GToolkit-Utility-System' trait. This trait has an items slot for the wrapped collection, and provides a large number of Collection Object subclass: #Collection instanceVariableNames: '' classVariableNames: '' package: 'Collections-Abstract-Base' methods to make the trait user behave like a Collection.

In case there are other classes that share views, actions and searches with the collection wrapper, consider putting these in a shared trait as well. For example, a website is also a collection of pages. We put the shared views and other behavior in the trait TWebPageGroup Trait named: #TWebPageGroup instanceVariableNames: '' package: 'GToolkit-Demo-WebsiteExplorer-Model' . The trait users are AWebsite AWebLinkGroup subclass: #AWebsite uses: TWebPageGroup instanceVariableNames: 'pages repoDir url linkDict reachable rootPages links repoUrl' classVariableNames: '' package: 'GToolkit-Demo-WebsiteExplorer-Model' and WebPageGroup Object subclass: #WebPageGroup uses: TGtGroupWithItems + TWebPageGroup instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-WebsiteExplorer-Model' .

Wherever you need to show a collection of these objects, just forward the view to the wrapper to avoid duplicating the view code. See TWebPageGroup>>#gtReachablePagesFor: gtReachablePagesFor: aView "<gtView>" self contentAvailable ifFalse: [ ^ aView empty ]. ^ aView forward title: 'Reachable pages'; priority: 60; object: [ self reachable ]; view: #gtPagesFor: .

If you have gtSearches, be sure they return the collection wrapper and not the raw collection, using the sendCategory: message, for example, TWebPageGroup>>#gtSpotterForPageContents: gtSpotterForPageContents: aSearch <gtSearch> ^ aSearch list title: 'Page content'; priority: 20; items: [ self pages items ]; itemsLimit: Float infinity; itemName: #contents; sendCategory: [ :aCategory :aStep :thisSearch | self webPageGroupClass withAll: aCategory items contents ]; filterBySubstring .