-
Notifications
You must be signed in to change notification settings - Fork 1
Mixins An Overview
Complex software systems often have more than a few thousands or even millions lines of code, but with every additional line, the risk of introducing errors or duplicates will also increase. Reducing the overall code base by restructuring and reusing parts of the code can significantly improve the quality of a software and reduce the error rate (also see the DRY principle)
A fundamental concept for code reuse is already integrated in most object oriented languages: inheritance
The C# language uses a special form of inheritance, single implementation inheritance, let's have a look at this technique in more detail:
When using implementation inheritance, you put the frequently used code in a base class and derived classes can reuse this code by inheriting the functionality from the base class. If necessary, the derived classes can override the base code or add additional functionality:
As we see here, cats and dogs have all the skills a common animal has, and also have some unique skills. To describe this relation, we would say: Cats and dogs are animals.
In most cases, this concept works quite well, but there are some drawbacks if we try to use this method to describe situations that are not based on a clear "is a" relation:
Let's assume we have to model two types of cars that use different engines:
The common functionality is placed in the base class, and every derived class has its engine specific code, so far so good.
Now let's go one step further and say, we don't only want to model cars, but also boats, and they should also have different engines:
Wow, something strange has happened: While our car or boat specific code appears only at one place in the base class, the engine specific code (to keep it simple, we assume that engines in cars and boats work the same way) suddenly has to be duplicated for every combination of vehicle and engine type.
To solve this, we could reorganize our inheritance structure and put the engine code in a base class instead:
Hmmm, but that did not work that well: While our specific engine code is not duplicated anymore, the boat or car specific code has to be duplicated now because there is no base class for them anymore!
The problem will get worse if we would have to add new engine types (like fuel cells) or vehicles (planes for example) to our domain model, and what would we do if someone suddenly wants to change the engine of his car from a gasonline to an electrical one? In that case we would have to throw away his old car instance and create a complete new car of a different type.
How could we solve these issues?
As we have seen, inheritance works best if we have a is a relation between types but it falls short if we have to model relations that could better be described as has a.
We could say, our car has an engine, or also: Our car is composed of an engine (and other parts), which leads us to the concept of Composition:
Instead of putting the engine functionality directly in the car class, we instead separate it into its own class (Engine
) and let the car instance reference it. Now every class has only the functionality that is directly related to its type.
Defining different engines is still possible by simply deriving from the engine class:
Compared to our inheritance example, we already have an advantage:
We could change the engine of a car now by simply changing the cars engine reference, there is no need to destroy and create a complete new car anymore.
Now let's see how our new concept can handle additional vehicle types:
Looks good, we have the same functionality as we used to have in our previous example but there is no need to duplicate any code, and our domain even scales much better in case we want to add new vehicle or engine types.
While compositions can often lead to a clearer architecture than single inheritance would do, they have one drawback:
In our example, if we want to start our car, we have to access our engine through the car by making the following call:
car.Engine.StartEngine()
This process is called method chaining (although in our concrete example, we are chaining a property and a method) because we access one method through another, and exactly here are some problems:
- Implementation details are revealed Normally, our objects should enclose any implementation logic and the caller should not have to bother with them. By providing public access to our compositions the caller gets access to our implementation details.
- It is a bad abstraction of the real world In the real world, you start a car by using your ignition key (or pressing a button), but normally you don't have to open the hood and interact directly with the engine. In the real world, this car - engine composition is hidden.
If we would add more components to our car class, interaction would become more and more complicated (as many of these components could also interact with each other).
So to get the best results, we must combine composition with another technique, called delegation.
To avoid this method chaining we already mentioned, we need to extend our car class with some so called wrapper methods.
In the simplest case, these methods simply forward the calls to our internal objects.
In that way, our class is becoming a Facade because it now controls all interaction with the internal objects (and if necessary, also the interaction between these objects).
As you can see from the picture, the references to our internal objects are private now, but the necessary methods are available in the public interface of the car and will be forwarded to the internal objects.
But the caller does not need to know from which objects our car is composed.
#Mixins# By combining the last two concepts, Composition and Delegation, we have the basic functionality of mixins:
- They provide code reuse by including them into other objects. In that way other objects can still have the base classes they need and the coupling between them and the mixins is quite low since the mixin instance could be changed at runtime.
- The child class where the mixin is included in provides the same interface as the mixin itself, but all methods are forwarded from the child to the mixin, so that implementation details of the child are hidden.
If necessary, additional code could be added before or after the forwarding call, or methods could even be removed from the public interface (if some functionality of the mixin should not be available from outside).
Although mixins are a very useful concept, there are some practical problems when it's going to the implementation stage:
While the C# language has native language support for composition (I can simply create a field and let it point to an instance of another type), the language itself does not have any support for delegation, that means any forwarding code must be manually implemented.
It would be nice to have something language support like the following:
public class Car
{
// would be nice to have:
public mixin Engine;
}
and the compiler would automatically resolve our mixin
keyword and create the required code.
But unfortunately, we have to implement the delegation code on our own:
public class Car
{
private Engine _engine;
// delegation code must be added manually.
public void Start() => _engine.Start();
}
mixinSharp tries to close this gap a bit by autogenerating the required code for you whenever you want to forward any method calls to a composition object within your class.
Please take a look at the Tutorial section to find out more about how you can use mixinSharp in your development.
All uml diagrams on this page were created with yuml.me