Composer Pattern (Visitor Pattern With A Composer) [& IOC Container Bonus] In .NET & C#

In this post I'm going to introduce a development pattern. I call it the Composer pattern.

I have implemented this pattern with huge success to fill an IOC container with registrations (the container "bootstrapper"). It provides excellent separation of concerns (SOC) and upholds all SOLID principles. While I have only truly employed the Composer pattern in the IOC container, it is easily applied for many other uses (and is not tied to the container in any way).

Here is a simplified diagram:


The pattern is not complicated, is interface driven, and has two basic actors. At first, this pattern does resemble the Visitor pattern; and as you'll see, I feel that this may be viewed as something like a refined or derived Visitor pattern overall: it is like the Participants are visitors, but here, the Composer is central.

The primary actor is the "Composer", and that holds the object of interest; which is termed the "Target". Therefore, the Target is the subject of the Composer's composition, and all actors are involved to "Compose" this Target.

The purpose of the pattern is for the Composer to bring up the Target, and notify other actors to access and then act on it.

The other actors are termed "Participants". Technically there is more than one type, but the primary Participant is termed a "Part Provider". The Composer holds Participants, and simply notifies each when the Target is to be Composed — the Target is then Composed through the Parts provided by each Participant (or any other actions that the Participant could take upon the given Target).

Since the Composer is holding the Target upon which the Participants must act, it also comes to hold the Participants themselves. Participants for a given Target are "Added" to the Composer; and it can then invoke them for the Composition.

The Composer goes through a single "Compose" operation, which fundamentally just invokes the Participants with the Target. The Participants act on the Target, and the Composition is considered complete: the Target has been Composed.

So the overall process needed is like this:

  1. Select the Target and instantiate a Composer for it.
  2. Add Participants to the Composer for this Target.
  3. Invoke the Composition.

IOC Container Example Use Case

In the IOC container example, the Target is the IOC Container, and the Participants make registrations into the container.

Imagine an application holding a root container, which is filled with registrations for components that are implemented through interfaces. I.E. the application defines its' components through interfaces, the component implementations implement the interfaces, and these implementations are then registered into the container.

In my implementation, I use MEF to bring together the parts. Each application component implements its' own given interfaces. Each component then Exports a Composer Participant, and that will register the interface implementations into the container (the Target).

So we have a container bootstrapper that holds the Composer. The bootstrapper locates the component assemblies, discovers the exported Composer Participants, and adds them to the container Composer. When Composed, each Participant makes its' registrations into the container.

Some more details follow, and remember that the central topic is our Composer.

In the following diagram, you can see that the application components are aware of an IContainer abstraction: This is our Target interface; and is a simple interface for an IOC container, with a registration method like RegisterType<TService, TImplementation>(). So each component contains its' implementation, and also provides an Exported IComposerParticipant<IContainer> that will do all the needed work of registering this component's interfaces and implementations into the container.


So again, in my application architecture, it is the job of each component to provide its' implementation, and also to provide an Export — a Composer Participant — that will make this component's registrations into the container.

This frees the container bootstrapper from requiring knowledge of the component interfaces, and needing only to determine for itself how to locate the component Assemblies. And with MEF: the components Export a Composer Participant, and these are simply discovered. [So all the work is handed to each Participant to register into the container. This will happen only once when the container is composed, and the components do not hold onto the container (it is provided only for the Composer Participants to make the registrations).]

This is accomplished using a Composer in our container bootstrapper; and each component exports a Participant for the container's composition. The bootstrapper adds the Participants, and the Composition fully bootstraps the Target container.

The Complete Composer Interface

As mentioned, the pattern includes more than one type of Participant, and in fact the Composer provides for these through a three-milestone Compose Sequence. The actual complete composition sequence proceeds like this (including concrete implementation methods):
  1. [Begin]
    • BeforeCompose
    • GetTarget : TTarget
    • WithNewTarget
  2. ProvideParts
    • [ProvideParts Event Callbacks]
    • WithAllParts
  3. Bootstrap
    • BeforeHandleComposed
  4. HandleComposed
    • AfterComposed
    • RaiseComposed (Event)
The three numbered methods outline the public Composer interface. The italic methods are protected concrete methods for the Composer implementation, and allow instrumentation of the process, and notification. These may be implemented by delegates, and otherwise may have default or empty implementations. In particular, the GetTarget method is responsible for producing the Target for each Composition: the default implementation simply returns a Target that was provided to the Composer when constructed.

The numbered methods invoke the pattern's defined one-method Participants:
  1. IProvideParts<in TTarget> : IComposerParticipant<TTarget>
    • void ProvideParts<T>(ProvidePartsEventArgs<T> eventArgs)
          where T : TTarget;
  2. IBootstrap<in TTarget> : IComposerParticipant<TTarget>
    • void HandleBootstrap<T>(ComposerEventArgs<T> eventArgs)
          where T : TTarget;
  3. IHandleComposed<in TTarget> : IComposerParticipant<TTarget>
    • void HandleComposed<T>(ComposerEventArgs<T> eventArgs)
          where T : TTarget;
The IComposerParticipant is an empty root interface for all participants:
  • IComposerParticipant<in TTarget> { }
The root interface simplifies scenarios like the mentioned MEF discovery: the Composer Participants can export this root interface, and it is up to the Composer implementation to reflect the actual Participant for it's defined interfaces.

The IComposer interface is defined this way:

IComposer<out TTarget>
  • IComposer<TTarget> Participate<T>(IComposerParticipant<T> partcipant, bool oneTimeOnly = false)
        where T : TTarget;
  • void Remove<T>(IComposerParticipant<T> partcipant)
        where T : TTarget;
  • TTarget Compose();
  • AggregateComposerException LastComposeErrors { get; }
It simply defines the Participate and Remove methods for participants, the Compose method, and also provides the AggregateComposerException to hold any composition errors.

One Last Participant

There is in fact also one last defined Participant: the IRequestComposition participant is provided for objects that are able to request that the Target is Composed, or re-composed. This interface defines its' own event:

IRequestComposition<TTarget> : IComposerParticipant<TTarget>
  • event EventHandler<RequestCompositionEventArgs<TTarget>> CompositionRequested;
If an IRequestComposition Participant raises its' CompositionRequested event, then the Composer performs a composition immediately. The given RequestCompositionEventArgs also defines properties for the invoker to request actions on the Composer before the composition begins.

Summary

That all basically sums up the pattern:

Prerequisite:
  1. Select the Target and instantiate a Composer for it.
  2. Add Participants to the Composer for this Target.
  3. Invoke the Composition.
Composition:
  1. Provide Parts [A Participant]
  2. Bootstrap [A Participant]
  3. Handle Composed [A Participant]
In our IOC container example:
  1. Target is an empty container
  2. Participants are provided by the components (and will make container registrations)
  3. Composition populates and fully readies the container
  1. Parts participants make registrations
  2. Bootstrappers can start services
  3. When Composed, the container is ready

Some Details

You may notice that the Participant interfaces define their methods with the covariant T : TTarget parameter. This was implemented to allow Participants that define a contravariant type to participate in a composition on a covariant type.

Note also that compositions can be re-invoked if your Composer allows it: your target may be updated, reset, or refreshed by further compositions. That is also why the IComposer Participate method defines the boolean oneTimeOnly parameter: a single participant can be restricted to run only once even if the composition is re-invoked.

Although the Bootstrap phase follows the Parts Providers, it is named "Bootstrap" because it runs before the IHandleComposed Participants, which are intended to handle the fully-composed Target. Bootstrappers can run first, to perform some type of final preparations or finalizations.

All Participant interfaces are invoked with an event argument, which always provides the current Target. This allows for future expansion by augmenting the event argument. And the ProvidePartsEventArgs holds a method that allows attaching a callback to that event. In the composition sequence outlined above, these callbacks are invoked at "[ProvideParts Event Callbacks]", which is invoked before the IBootstrap Participants. This allows a Parts Provider to re-access the Target after all parts have been provided, but before the Bootstrappers run.

Lastly, my implementation is single-threaded: the Compose sequence runs sequentially. It would not be hard to augment that with an option to invoke the Participants in parallel, but it will require hacking into the project to add that (perhaps with some policy to define an option). The implementation is not very complicated overall, and there is one primary method to handle the sequence. Also, the implementation does do some simple reflection to handle the Participants that is not cached (using System.Collections.IEnumerable.OfType<T>() each time), and therefore overall was not implemented with maximum speed as a priority; yet it's fast and reasonably terse. It for sure has release-worthy stability; and has been around for well over a year.

Code And Examples

The complete C# code for my implementation of this pattern is in fact part of a large repository of my own, which was not set up for sharing: i.e. it contains many internal projects all together. I have simply copied out the required projects and included a Visual Studio solution. So, although there are no external dependencies, there are dependencies on my own abstractions and utilities for the implementation. All of the required code is in this repository. The repository is on GitHub, here: https://github.com/steevcoco/Sc.Composer

Hey: Why net45 and not Standard and Core

Oops I almost forgot. This code is all in net45 projects. That is because my business was driven by a primary project, and it was targeting net45. The fact is there will be little to no change to retarget all of the code: all libraries can target Standard; and in fact it was so smooth I once had a simple build switch to switch the target — I.E. it was important.

You will find dependency projects grouped as "Dependencies". The Composer implementation is in the Sc.Composer project. Examples are grouped under "Examples".

I have also included my Sc.Composer.Mef project, which should be noted is NOT used as the actual Container Composer in the IOC Container example, but is used only to gather the Exported Participants there, via some static utility methods that it also provides. (That project provides a Composer implementation that can perform a configuration on a MEF ContainerConfiguration; and in fact, under the hood, the static helpers create a MEF Composer to perform the import.)

I have included two examples: a simple project that includes all of its own classes, called SimpleExample, that just runs through a short Main method. And I have also included an example of the IOC Container scenario, that utilizes MEF discovery as described: it runs from the primary ContainerExample project, and imports Composer Participants implemented by the Component1 and Component2 projects.

Please send feedback!

Comments