rskibbe.Core.Composition – Building better and more flexible objects
Inhaltsverzeichnis
What is rskibbe.Core.Composition?
My NuGet package called „rskibbe.Core.Composition“ is about helping you build more complex objects easier. It provides a common infrastructure for you to design your classes upon. It was private at first, but I’m using it in a lot of my projects, so I thought, why wouldn’t I publish it.
If you need more info on how it’s done behind the scenes, feel free to scroll further down. In a section at the bottom, I’ve provided more detailed explanation on this package. This way you can comprehend the idea behind everything a bit more and maybe even extend on it.
I’ve also added some small class diagram to have everything visually summarized and in one simple spot.
Quick example
Step 1 – Install the package
Open the NuGet Package Manager and execute the following commands (or install it by GUI). This command will install the package to enable you using its interfaces and classes.
Install-Package rskibbe.Core.Composition
Step 2 – Start composing components
Now you are already ready to go and start composing your own components / classes. Please move to the detailed explanation further down to get a better hang of the package.
public class MyComponent : Component { // add different sub-components to your class // extend it e. g. with interfaces delegating their actions to // the corresponding sub-components }
Public Class MyComponent Inherits Component ' add different sub-components to your class ' extend it e. g. with interfaces delegating their actions to ' the corresponding sub-components End Class
Using composition over inheritance is the deal, right?
When creating your classes there can be different scenarios where you sometimes need different approaches to solve designing them. Fore sure, we could like mostly use typical inheritance like „a robot is a machine“ – but what if those machines are so damn (pardon me) complex, that you can’t fit them the right way by inheriting? This is where a typical composition scenario could fit for you!
To get to know more about the „composition over inheritance pattern“, I will write a separate blog post, but for now this post here is about my corresponding NuGet package „rskibbe.Core.Composition“. This way, you will have a common denominator where you can build upon and you don’t have to start from scratch as well.
Detailed explanation
The core conception
When designing or – in other words – composing your object of desire, I found it pretty matching, to call the absolute „base“ actually „IComposable„. It’s something you can almost magically compose out of different objects which then builds a new object – what I’m calling an „IComponent„. So basically the „IComponent“ interface inherits from the „IComspoable“ interface.
To actually do / execute this kind of composition, you will need to be able to dynamically add sub-components to your composable(s). Maybe you want to even dynamically remove the components again? This is why the following methods have been implement in the class „Composable“.
Adding and removing components won’t help much, if you can’t access and use them. So – for sure – and as visually mentioned inside the image above, there are methods to get your sub-components from a „parent“.
Concrete example – A machine
Let’s now start a complete example of our first designed component being composed of composables – what a language-play. Keep in mind, that this actually – just an example, feel free to change it in any way fitting your needs… For this example, we’re taking a machine which could possibly connect many things to itself to use them.
// a base class for some sort of common contextual denominator class Machine : Composable { public Machine() { // instantiated automatically - needs an empty constructor AddComponent<LightComponent>(); // manual instantiation, maybe with DI whatever var giantPowerBattery = new GiantPowerBattery(); var laserBeamComponent = new LaserBeamComponent(giantPowerBattery); AddComponent(laserBeamComponent); } } // component 1 class LightComponent : Component { public void Toggle() { /* some code.. */ } public void TurnOn() { /* some code.. */ } public void TurnOff() { /* some code */ } } // component 2 class LaserBeamComponent: Component { IEnergySource _energySource; // having a ctor with dependency public LaserBeamComponent(IEnergySource energySource) { _energySource = energySource; } public void Shoot() { // some code.. } } // just some helper stuff.. interface IEnergySource { /* .. */ } class GiantPowerBattery : IEnergySource { /* .. */ }
Using the sub-components
Above, we just provided the composition side of things while composing our machine, but we didn’t actually use anything. If a machine wants to „shoot“ with our „LaserBeamComponent“ or if it wants to toggle the lights, it has to resolve the corresponding thing and use it. So we could just write something like this:
class Machine : Composable { // rest of the machine code from above public void ToggleLight() { var light = GetComponent<LightComponent>(); light.Toggle(); } // or shorter public void ToggleLight2() => GetComponent<LightComponent>().Toggle(); // rest of the machine code from above } // somewhere executable Machine machine = new Machine(); machine.ToggleLight(); // maybe add more things? // and call them dynamically?
A word on component types
As you’ve probably seen in the example from above here, concrete types were used (in the constructors), like:
AddComponent<LightComponent>();
AddComponent(Of LightComponent)()
The problem is now, that we would have a more strict dependency on the „LightComponent“. I would prefer to rely on abstractions (interfaces) instead. And this is no probem, as you can just use other overloads of the „AddComponent“ method:
class Machine : Composable { public Machine() { // using an interface alias when calling add AddComponent<LightComponent, ILight> // or with an instance // remember to actually implement the interface here... LightComponent light = new LightComponent(); AddComponent<ILight>(light); } public void ToggleLight() => GetComponent<ILight>().Toggle(); } interface ILight : IComponent { void Toggle(); void TurnOn(); void TurnOff(); }
More reusable functionality with interface traits
Imagine you’re having two objects being able to do pretty much the same. A „light-aware-machine“ – like the one above – could toggle their light, a car could do so and your gaming console as well. Maybe we don’t want to create stuff like „TurnOn“, „TurnOff“, etc. again and again. As we can’t inherit from a base class implementing it, we can just use the – sort of – „Trait“ feature from C#.
I’m talking about default implementations which interfaces can provide since C# version 8. Sadly you can’t have states in interfaces, so properties still need to be implemented. You could potentially define your interface trait like this:
interface ILightTrait : IComponent { public void ToggleLight() => GetComponent<ILight>().Toggle(); public void TurnOnLight() => GetComponent<ILight>().TurnOn(); public void TurnOffLight() => GetComponent<ILight>().TurnOff(); }
After defining that interface, every component – which should have a „Light functionality“ could now just inherit from that interface. That means a machine, a gaming console, whatever could just inherit the functionality from that interface having the possibility to call the defined methods. When having like those 1 liners of methods, it’s no big deal, but as soon as it starts growing, it can make a big difference.
Keep in mind that default implementation style used interfaces require you to use them as interface casted type, not the actual type. Take a look at the following two examples.
Let’s now define two classes which are able to toggle their lights and then use them. Our trait helps us not having to redefine everything from scratch.
class GamingConsole : Composable, ILightTrait { public GamingConsole() { AddComponent<LightComponent, ILightComponent>(); } } class Car : Composable, ILightTrait { public Car() { AddComponent<LightComponent, ILightComponent>(); } } // more...
Now let’s actually use these, care for the warning inside the yellow box from above. You have to cast the object to the interface type to actually use the trait-defined methods.
GamingConsole gamingConsole // won't work due to how default implementations are usable gamingConsole.TurnOnLight(); // need's to look like this instead ((ILightTrait)this).TurnOnLight();
Publishing property notifications
When building your complex objects, it could be useful to be able to publish „INotifyPropertyChanged“ notifications. As both classes (Composable and Component) directly or indirectly inherit from „PropertyChangedBase“, you can do so pretty easily by just calling „NotifyOfPropertyChange“. Just provide the changed property name or let it figure it out itself by using the „CallerMemberNameAttribute“.
This could look like the following property implementation:
bool _isOn; public bool IsOn { get => _isOn; set { if (_isOn == value) return; // will automatically use "IsOn" as property name NotifyOfPropertyChange(); // provide a name manually - but in favor with nameof() // NotifyOfPropertyChange(nameof(IsOn)); // or completely manual // NotifyOfPropertyChange("IsOn"); } }