-
Notifications
You must be signed in to change notification settings - Fork 3
2009 02 02 declare mixins
Published on February 2nd, 2009 at 10:39
This is the fourth in a series posts. The previous items are:
- It provides a mixin mechanism
- What's a re-motion mixin, anyway?
- What can we do for you? (Features of re-motion mixins)
Last time, I briefly mentioned configuration as a feature of the re-motion mixin implementation. In the context of mixins, the term "configuration" refers to what mixins are mixed into what target classes, and with what properties the mixing is performed.
To be honest, the notion of configuration is more a prerequisite than a feature - without the ability of assigning mixins to target classes, a mixin implementation would be rather stupid. Or... not a mixin implementation at all, actually. What is a feature, however, is how many ways of configuration we allow.
I quickly repeat them from my last post:
- A class can declaratively choose to be extended by one or more mixins.
- A mixin can declaratively choose to extend one or more classes.
- An application can declaratively choose to combine mixins and classes.
- A programmer can - at run-time - choose to combine mixins and classes.
- (Any person can - at deployment time - choose to combine mixins and classes.)
--> planned, but not implemented yet
Today, I'll write about the first three topics - the declarative configuration possibilities.
When you write a new class, you can explicitly pull in a number of mixins into that class. This feature is similar to inheritance (where you explicitly derive from a base class), and it enables scenario (1) from my post on usage scenarios: having a class reuse an implementation supplied by a mixin.
Let's look at an example:
[Uses (typeof (MyMixin))]
public class MyClass
{
}
That's quite self-explaining, I think - MyClass
has chosen to use (ie. include) the reusable code provided by MyMixin
.
Now, what would you use that for, and what additional properties can you specify?
You would choose this form of configuration when you have a mixin providing a feature that you want your class to expose. The mixin's implementation actually becomes part of the class's implementation. Think of deriving from a base class, or of aggregating and delegating to another component. For example, you could want to make your class cloneable by reusing a mixin:
[Uses (typeof (CloneabilityMixin))]
public class MyCloneableClass
{
}
And the test:
[Test]
public void MyCloneableClass_IsICloneable ()
{
var instance = ObjectFactory.Create<MyCloneableClass> (ParamList.Empty);
Assert.That (instance, Is.InstanceOfType (typeof (ICloneable)));
}
(We will look at the actual implementation of the CloneabilityMixin
at a later time.)
That way, the class will always support cloning, the implementation supplied by the mixin has become a part of MyClass
's implementation.
The UsesAttribute
supports the following optional parameters:
-
IntroducedMemberVisibility: As you know from my feature post, mixins can add interface implementations to the mixed object. By default, the members added by such an interface implementation are explicit implementations, ie. private. If you need those members to be made public, which might make sense in some reflection scenarios, you can do so by specifying
MemberVisibility.Public
to the attribute. - AdditionalDependencies: I will write about dependencies in another post, but for now it should be enough to say that mixins can have dependencies on other mixins, either directly or indirectly. Dependencies influence the base call order when more than one mixin overrides the same members, and they also define what a mixin can rely on when it accesses the mixed object. Explicit dependencies can be defined via this parameter by passing an array of mixin types to the attribute.
- SuppressedMixins: By default, every target class inherits the mixins configured for its base classes. Sometimes, however, a class might want to replace an inherited mixin with another one - so the new mixin declaration needs to suppress a previous one. Which is what this attribute parameter is for.
Uses is nice, but you could argue that you could replace it simply by having your class hold an instance of the mixin and delegate to it, or even by deriving your class from the mixin. In reality, that "simply" will usually not be so simple at all, because delegation quickly becomes cumbersome and lengthy, and base classes are scarce (only one per class) in the .NET world. Still, let's look at something you can't do that easily with manual delegation or inheritance.
Consider scenario (2) from my usage scenario post: you have a finished class model, but need to adapt it. How do you do that?
That's easy - just create a mixin specifically for those target classes you need to adapt:
[Extends (typeof (MyClass))]
public class MyMixin
{
}
Now, while this might seem to be only an inversion of the example given above, semantically it's much more: It allows a mixin to extend a class from the outside. So, if we follow the base class analogy, this would be like specifying the base of a class from the outside. Try this in C# :)
To give a more realistic example, consider a scenario where you have a pre-implemented file management domain, and you have a client requirement to number files in a specific format. While you could simply derive from the pre-implemented File
class to add that information (and hope your domain uses inversion of control to instantiate its File
objects...), consider that your domain might already contain CarFile
, PersonFile
, and other subclasses of File
. That would quickly result in a subclassing orgy, and all of the subclasses would hold the same delegation code!
So, how to do this, then? Remember, classes automatically inherit mixins from their base classes, so if we implement mixin for this, it should be much more efficient. And it is:
[Extends (typeof (File))]
public class NumberedFileMixin : INumberedFile
{
private readonly string _id = Guid.NewGuid ().ToString();
public string GetFileNumber ()
{
return _id;
}
}
And the test for this:
[Test]
public void File_IsNumbered ()
{
var numberedFile = (INumberedFile) ObjectFactory.Create<File> (ParamList.Empty);
var numberedCarFile = (INumberedFile) ObjectFactory.Create<CarFile> (ParamList.Empty);
var numberedPersonFile = (INumberedFile) ObjectFactory.Create<PersonFile> (ParamList.Empty);
Assert.That (numberedFile.GetFileNumber (), Text.Matches ("........-....-....-....-............"));
Assert.That (numberedCarFile.GetFileNumber (), Text.Matches ("........-....-....-....-............"));
Assert.That (numberedPersonFile.GetFileNumber (), Text.Matches ("........-....-....-....-............"));
}
(Note that I needed to cast to INumberedFile
because the C# compiler can't know that File
now implements INumberedFile
via a mixin. There are ways to avoid the cast, but that will go into a separate blog post...)
The ExtendsAttribute
supports the same optional arguments as the UsesAttribute
does (IntroducedMemberVisibility
, AdditionalDependencies
, and SuppressedMixins
), plus one:
-
MixinTypeArguments: Consider the case where you have a generic mixin (if you can't imagine this, take a look at Stefan's blog post about mixins in domain design). re-motion's mixin implementation has an algorithm that tries to supply the type arguments automatically when the mixin is instantiated, but sometimes, the algorithm won't be able to figure out what you want. In these cases, you'll need to specify the type arguments by hand using this attribute. (Prize question: Why doesn't the
UsesAttribute
need this parameter?)
So, you use the ExtendsAttribute
when you write a mixin for specific target classes, and you use the UsesAttribute
when you write a class using a specific mixin. Can there be anything else?
Well, what if you want both classes and mixins to be fully oblivious of each other?
Say, for example, you reference a DLL with a finished class model, and you also have a catalog of adaptation mixins for different client scenarios. Now, in your application, you just need to combine the relevant mixins with the respective domain classes. re-motion also supports this:
[assembly: Mix (typeof (MyClass), typeof (MyMixin))]
In this case, the programmer has chosen (via an assembly-level attribute) that instances of MyClass
should be mixed with MyMixin
in the context of the application. Let's apply the same to the file numbering sample from above:
[assembly: Mix (typeof (File), typeof (IdentifiedObjectMixin))\]
And the test:
[Test]
public void File_HasID ()
{
var numberedFile = (IIdentifiedObject) ObjectFactory.Create<File> (ParamList.Empty);
var numberedCarFile = (IIdentifiedObject) ObjectFactory.Create<CarFile> (ParamList.Empty);
var numberedPersonFile = (IIdentifiedObject) ObjectFactory.Create<PersonFile> (ParamList.Empty);
Assert.That (numberedFile.GetObjectID(), Text.Matches ("........-....-....-....-............"));
Assert.That (numberedCarFile.GetObjectID(), Text.Matches ("........-....-....-....-............"));
Assert.That (numberedPersonFile.GetObjectID(), Text.Matches ("........-....-....-....-............"));
}
Note that I've renamed the mixin and the interface to denote a more general mixin implementation. This is exactly the point: the MixAttribute
allows you to combine completely general (or "oblivious") classes and mixins.
The attribute accepts the same properties as the UsesAttribute
(IntroducedMemberVisibility
, AdditionalDependencies
, and SuppressedMixins
), plus one:
-
MixinKind: Now, what's that? This parameter takes an Enum value that allows you to choose: do you want to regard the mixin to be extending the target class, or the class to be using the mixin? That's not really important from the point of view of the mixin engine, but there might be user code that needs to differentiate between these two scenarios. And for such user code, you can specify the relationship. (The default is
MixinKind.Extending
, by the way.)
So, to wrap it up, re-motion offers three forms of attribute-based declarative mixin configuration:
- The
UsesAttribute
, by which a class can include mixins, which is similar to inheritance, - the
ExtendsAttribute
, by which a mixin can extend a class, which is good to adapt classes from the outside, and - the
MixAttribute
, by which an application programmer can combine any class with any mixin.
Use them wisely, and you shall prevail.
Next time: Probably something short about mixins and inheritance.
- Fabian
Edit: You can find the code for this blog entry here:
You can find the code for this blog entry here:
https://github.com/re-motion/Framework/tree/v3.0.0/Remotion/Mixins/Samples/Tutorial/T01_Configuration/Core
The tests are here: https://github.com/re-motion/Framework/tree/v3.0.0/Remotion/Mixins/Samples/Tutorial/T01_Configuration/UnitTests
Hi Fabian,
you might want to update your blog post index with a link to this post, because as of now it has been quite cumbersome to find more detailed info on how to use and implement mixins.
This post has been quite helpful, though 🙂
Also, a navigation item linking to an overview of all mixins post, would be handy!
Cheers, Oliver
Hi Oliver,
Thanks for the comment – you can find all of my mixin-related posts by browsing the wiki. I’ve updated the Team Blog post with that link as well.
If you have any specific questions about re-motion mixins (how to use them, why something doesn’t work as you’d expect, etc.), please post them to our Google Group: http://groups.google.com/group/re-motion-users.
Fabian