In Unity3D, the scripts we write and attach to GameObjects inherit from a base class called MonoBehaviour (and yes, that says Behaviour with a U in it, not the American spelling like Behavior... Just a heads up). MonoBehaviour instances can be attached to GameObjects in code by calling the AddComponent method, which takes a type parameter or type argument, and returns the new instance of the attached MonoBehaviour that it creates.
This API usage means that:
- We cannot attach existing instances of a MonoBehaviour to a GameObject
- Unity3D takes care of instantiating MonoBehaviours for us (thanks Unity!)
- ... We can't pass parameters into the constructor of a MonoBehaviour because Unity3D only handles parameterless constructors (boo Unity!)
The challenge I'm trying to address is that my non-MonoBehaviour classes are all going to be setup to use constructor parameter passing as much as possible but the MonoBehaviour classes cannot. So I'd like to reduce the amount of disjoint coding styles as much as I can and make the MonoBehaviour classes feel like the rest of my stuff!
What Is "Stitching"?
Here's where this little pattern I created called "Stitching" comes into play. Stitching involves using a class referred to as a Stitcher that's single purpose is to take parameters in via a constructor, and wire them up to either public properties or public fields (but I REALLY suggest using properties) on the MonoBehaviour that we instantiate through the GameObject.AddComponent() API.
The code ends up looking something like this:
public sealed class MyComponentStitcher { private readonly IDependency _dependency; public MyComponentStitcher(IDependency dependency) { // take in our dependencies and save them as fields _dependency = dependency; } public MyComponent Stitch(GameObject gameObject) { // create the MonoBehaviour instance using the Unity3D API var componentInstance = gameObject.AddComponent<MyComponent>(); // wire up our dependencies (assign our field to a property on the component) componentInstance.Dependency = _dependency; return componentInstance; } }
Where you can see that:
- We inject dependencies into the Stitcher's constructor
- We call AddComponent() with the component type we want on the object we want to "stitch" to
- We mutate the component
- We return the newly made component
public sealed class SomeClass { private readonly IMyComponentStitcher _stitcher;From this, you can see that:public SomeClass(IMyComponentStitcher stitcher)
public void MyMethod() { // create a new Unity3D game object var gameObject = new GameObject("My Game Object");
// "stitch" our var myComponent = _stitcher.Stitch(gameObject); // we can use some information that would have been injected into the constructor // this should print the injected value Debug.Log(myComponent.InjectedInfo);
} }
- We have a class called MyClass following our constructor parameter passing paradigm
- The method MyMethod()
- Creates a new game object
- Adds a MyComponent instance to our game object by calling the Stitch() method
- Using our imagination and the example above, pretend our Stitcher implementation takes a parameter in its constructor to assign to the InjectedInfo property of of MonoBehaviour
- Logs out the value of the InjectedInfo property found on our newly created instance
By creating a Stitcher, we can register it to our Autofac container. The Autofac container will then resolve any dependencies that our Stitcher requires for us. The net effect of this is that when we Stitch MonoBehaviours to GameObjects, we get what feels like Autofac resolving dependencies for our MonoBeaviours. We don't need to mutate MonoBehaviour fields/properties all over our code to assign the dependencies the script needs to use. Instead, we treat the Stitcher class like a factory for our MonoBehaviour.
So in summary:
- Stitching allows us to leverage Autofac for instantiating MonoBehaviours
- Stitcher classes essentially become a factory class for our MonoBehaviours (with the side effect that they *must* mutate the GameObject that we need to attach the MonoBehaviour to)
- Allows assignment of MonoBehaviour fields/properties for initialization to exist in one spot so we can put the bad object mutating code in one spot that feels hidden