re-motion mixins basics -- the 'CompleteInterface' attribute

We can make Chevy Chase' New Shimmer skit even more funny by adding a shaving cream to the mix, giving our civilization a shaving dessert wax:

public interface IShavingCreamMixin
{
  void LubricateBeardStubbles ();
}

[Extends (typeof (DessertTopping))]
public class ShavingCreamMixin : IShavingCreamMixin
{
  public void LubricateBeardStubbles ()
  {
    Console.WriteLine ("For a close, smooth shave with no side-effects!");
  }
}

And, as already mentioned in re-motion mixins basics -- the 'Extends' attribute, it does not end there: our application can mixin super glue, vitamins – even extra calcium! The problem is that you have to cast to an interface for the members of each mixin class. This leads to excessive typing.

Enter the finger-friendly attribute CompleteInterface. You combine the interfaces for all mixins and an interface for the target class with multiple inheritance and attribute that super interface with
CompleteInterface:

// ***** This code is just for illustration, not part of this sample *****
[CompleteInterface (typeof (DessertTopping))]
public interface IUniversalWax: IDessertTopping, IFloorWaxMixin, IShavingCreamMixin, IVitaminsMixin, ICalciumMixin, AromaTherapyMixin { }

Then you pass that interface to the ObjectFactory; out comes and instance that sports all members from all mixin classes and the target class natively – no casts required.

For brevity, we will demonstrate the CompleteInterface attribute for the components implementing

  • IDessertTopping (we introduce an interface for the target class here),
  • IFloorWaxMixin (already introduced in previous sections)
  • IShavingCreamMixin (listed above)

In practice, you can combine as many interfaces as you need.

// NOW we need an interface for the 'DessertTopping' target class
public interface IDessertTopping
{
  void TasteGood ();
}

// 'DessertTopping' implements 'IDessertTopping'
public class DessertTopping : IDessertTopping
{
  public void TasteGood ()
  {
    Console.WriteLine ("Mmmmmm, tastes terrific!");
  }
}

// You know that one already
public interface IFloorWaxMixin
{
  void SealFloor ();
}

// Nothing new here
[Extends (typeof (DessertTopping))]
public class FloorWaxMixin : IFloorWaxMixin
{
  public void SealFloor ()
  {
    Console.WriteLine ("Dirt, grime, even black heel marks -- wipe clean with a damp mop!");
  }
}

// As shown in the previous listing
public interface IShavingCreamMixin
{
  void LubricateBeardStubbles ();
}

Extends (typeof (DessertTopping))]
public class ShavingCreamMixin : IShavingCreamMixin
{
  public void LubricateBeardStubbles ()
  {
    Console.WriteLine ("For a close, smooth shave with no side-effects!");
  }
}

Now you can tell the ObjectFactory that a new interface, IShavingDessertWax combines all the interfaces required for a shaving dessert wax instance, and that this interface and the implementations for its various components together implement the extended DessertTopping target class:

// Enter the 'CompleteInterface' attribute
[CompleteInterface (typeof (DessertTopping))]
public interface IShavingDessertWax : IDessertTopping, IFloorWaxMixin, IShavingCreamMixin { }

This measure comes with two benefits:

  • it makes casting to interfaces obsolete when using various mixed in members in the instance
  • it makes instantiation completely abstract, because we pass an interface to ObjectFactory, not a class

The client code is clean and compact:

 // you must force the .NET runtime to load a reference to the
 // Remotion.dll assembly. Otherwise the compiler will remove the
 // facilities for loading the Remotion.dll assembly and your application
 // will throw a type initializer exception, because it can't load it.
 // The easiest way to touch the assembly is to use the IMixinTarget
 // identifier:
 FrameworkVersion.RetrieveFromType (typeof (IMixinTarget));

// Note that we pass an interface to the factory, the factory knows what
// implements the mixed class from all the attributes we used
var myShavingDessertWax = ObjectFactory.Create<IShavingDessertWax> (ParamList.Empty);

// Now we can save typing, no casts required
myShavingDessertWax.TasteGood (); 
myShavingDessertWax.SealFloor ();                // no cast to 'IFloorWaxMixin'! 
myShavingDessertWax.LubricateBeardStubbles ();   // no cast to 'IShavingCreamMixin'!

Console.ReadLine ();

CompleteInterface for inversion of control?

Decoupling an interface from its implementation is an extremely useful strategy for keeping code maintainable and among the first things aspiring software architects learn. So-called inversion-of-control containers store the knowledge of which implementation should map to which interface, the factory reads in this mapping and returns instances to the application based on this mapping. This design pattern is unrelated to mixins, because it is much broader in scope.

You might feel tempted to use re-motion mixin and its CompleteInterface for this task, because it gives you a way of mapping between implementation classes and interfaces for free. This, however, is not recommended, for the following reasons:

  • dedicated IOC libraries exist, and they are much more appropriate for decoupling interfaces and implementations
  • you can't decouple in such a fashion that it actually makes sense – you would have to modify the interface's source code with the CompleteInterface attribute every time you change the mapping

In other words: use something like the castle project's for inversion of control Windsor Container, not the re-motion mixin library.

Sample code

Find the sample code for this exercise in subversion.