This is the third post in a series of posts about re-motion mixins, and this is the one where I’m finally going to tell you about the core of the re-motion mixin library. Last time, I explained that mixins are about extensibility. They are about taking two pieces of code and integrating them so that the combination makes sense in the context of a new application. The two pieces of code are the target class and the mixin, and the combination is the mixed object.
So, the first two and most important features of re-motion mixins are the following:
- It allows you to configure mixins for target classes.
- It allows you to create mixed objects.
When combining a mixin and a target class, the mixin can add new value to the class in two ways:
- It can extend the public interface of the class.
- It can extend or change the behavior of the class.
In addition, the target class can customize the mixin:
- It can change the behavior of the mixin.
I’ll write a dedicated post on configuration at some point in the future, but let me just enumerate the possibilities, as there are so impressively many:
- 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.
(I’m cheating: The last possibility has actually not been implemented yet. But it might be any time. When someone needs it.)
Creating mixed objects
This part is easy. re-motion provides a class called ObjectFactory, which is used to create mixed objects:
MyTargetClass myMixedObject =
ObjectFactory.Create<MyTargetClass> ().With (x, y, z);
In this sample, myMixedObject is a mixed object, and MyTargetClass is a target class. Oh, names are just wonderful things.
But wait, where are the mixins? And what is that With (x, y, z) method?
The mixins are in the configuration. As I said, I will write a separate post on that topic, but in principle, the Create method will take a look at the current mixin configuration, and it will add all the mixins it finds configured for MyTargetClass, its base classes, or its interfaces.
The With is a wart, unfortunately. Short story: x, y, and z are the constructor parameters for the mixed object, and the With method takes care of their types being respected. (If you are thinking, “Why aren’t they just using a params object?” – because that wouldn’t respect the parameters’ types. I’ll explain another time.)
With will soon be replaced by a ParameterList object or similar, so that it will read somewhat like this:
MyTargetClass myMixedObject =
ObjectFactory.Create<MyTargetClass> (ParameterList.Create(x, y, z));
I’ll probably write something about ObjectFactory in the future, too, and there I will explain about the With thing.
Extending the public interface
A mixin can extend the public interface of its target class, that’s nice! But what does that mean? It means that a mixin can introduce new methods, properties, and events to the mixed object.
Consider an example I gave in my last post on the topic: a reusable implementation of cloning by means of in-memory serialization and deserialization. If you have such a standard implementation of cloning all wrapped up and reusable, you’ll of course need a public API on your mixed object to actually invoke it. You want to say the following:
object clone = myMixedObject.Clone ();
Where Clone is a method added by the mixin.
And re-motion’s mixin implementation lets you do that! Woo!
However… There’s a catch.
You know, since – as I explained above – the ObjectFactory method effectively checks the current mixin configuration when creating the mixed object at runtime, your computer doesn’t know at compilation time what mixins will be integrated into myMixedObject, or what methods they might add. And, since your computer doesn’t know, the compiler doesn’t either. Now, there are a few programming languages where that doesn’t matter – Ruby or Python, for example. Or Boo. But if you were using any of these languages, you probably wouldn’t be reading this blog post.
Unfortunately, to the C# compiler, not knowing whether a method will exist or not matters a lot. There’s really no good way to combine ObjectFactory’s feature of determining the set of mixins at instantiation time with C#’s need of static information at compile time but one: exploit the dynamic parts of C#’s type system.
“Dynamic type stuff in C#?”, I hear you say, “That doesn’t exist!”
But there is an exception from strict static typing in C#: type casts.
Due to the nature of the .NET (and C#) type system, if you have a reference of static type C, the compiler (generally) has no way to tell whether that reference really holds an instance of C or of a derived type. Therefore, you are allowed to try to cast that reference to another type, and the type check will only be made at run-time. (Now, if that isn’t dynamic…)
So, tough luck with trying to call a method introduced by a mixin on the mixed object; but at least we can cast to an interface introduced by the mixin! If the mixin chooses to add the ICloneable interface to the mixed object, we can write the following:
object clone = ((ICloneable) myMixedObject).Clone ();
This renders the compiler happy, and it does what we want – provided that there is a mixin introducing ICloneable configured for the target class at run-time.
(If you want, note that actually it needn’t be our mixin with the serialization-based cloning, it could be any mixin. You know, this is pretty flexible.)
If you want to save on typing, re-motion’s mixin implementation provides a concept called complete interfaces which allows you to get rid of the casts. But that’s an advanced topic, and I’ll write about it another time. On to the next feature…
Extending or changing behavior
So, a mixin can “extend or change the behavior” of its target class. Sounds… interesting. But how? And… what for?
Well, for the same reason you’d always “extend or change” existing behavior. Does method overriding ring a bell?
Let’s again take an example from my recent post on this topic: you have a finished class model which you need to adapt to a specific scenario (eg. a specific client). For simplicity, let’s just assume a single class PowerfulFoo, which needs to be localized for the German market. Localization consists of adapting the result of ToString by looking it up in a resource file. (Well, it is somewhat simplified, this example.)
With re-motion mixins, the mixin can say, “Override target method – perform base call, localize the returned string, and return localized string.” Not within quotes, and with a number of brackets and semicolons inserted, but essentially, it’s just that.
As you can see, even in this simple example, overriding doesn’t make much sense without calling the base method first – that’s the difference between the “change” (ie. replacing) and the “extend” (ie. adjusting) parts in the feature list. re-motion allows mixins to do both.
Since overriding requires a few explanations, we’ll also keep this for another blog post. But let me add that mixins aren’t only allowed to override methods, but also properties and even events. Any member, as long as it is virtual.
Changing the behavior of the mixin
Consider again the example where we implement cloning in a reusable way. But this time, we won’t go for the slow and embarrassingly simple model of in-memory serialization, but instead, we’ll opt for a more efficient idea: the mixin will simply take all the fields of the cloned object, create a new instance of the object’s type, and copy the field values over to the new instance (optionally calling ICloneable.Clone again, if provided). Simple, and more efficient. At least as long as we don’t have to resort to using reflection to get the fields.
Since the cloning implementation in the mixin should be as general as possible, the question is: How do we get those field values from the mixed object when we can’t make any assumptions about the target class?
One answer is: by providing a template method in the mixin. According to the Web, the template method pattern is used to:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
Which is exactly what we need here – although it’s the target class we want to defer the steps to, not a subclass. re-motion enables the mixin to provide a virtual or abstract member for which the target class can say, “Override mixin member – return my field values.” Again, with more brackets and semicolons, but essentially, it’s that.
Why would you make the member virtual? To provide a default implementation, eg. a retrieve the field values via reflection. That way it isn’t really a template method any longer, more a replaceable default method; but the idea is the same.
So, that was my first write-up about the core features of re-motion mixins. What’s up next? Well, I have a lot of topics to cover now: the details of mixin configuration, ObjectFactory and With, member introduction and complete interfaces, target class member overrides, and mixin member overrides. One of those, it will probably be. Stay tuned.