Unity Software Design – Encapsulation


Second in a series of posts on Software Design for Unity. Read the introduction here.

The previous post was about a fairly specific “pattern” (Singletons). The topic this time is more of a general concept. Encapsulation, also known as Information Hiding, refers to designing code modularly, such that each part is as isolated from the others as possible. This doesn’t mean any one technique – it’s a broad engineering principle which should influence almost everything you do.

Why Encapsulate?

As mentioned previously, one of the biggest problems in programming is coupling. When one bit of code (let’s call it A) depends heavily upon the specifics of how another (B) works, the two are said to be tightly or strongly coupled. Modifying B is then likely to have an effect on A. Note that this doesn’t refer to intentional effects on A. For example, if B is a function – called by a method in class A – which calculates something, and B is changed to calculate more accurately, clearly A will be affected.

Coupling refers more to internal changes. For example if again B is a function that isn’t part of A, but directly uses and changes some field of A, then changing how B works could easily cause hard-to-predict changes in A and vice versa.

Encapsulation aims to avoid these kinds of situations by making everything as close to a black box as possible. The principle is that each part of the code should have clearly defined, minimal public inputs and outputs, with the inner workings essentially opaque to all other parts.

Encapsulation in Classes

Perhaps the simplest example is the use of public and private[1]and protected, but I’m ignoring that for the sake of simplicity member variables (fields) of a class. It can be tempting to make all your fields public – when you write a class, you don’t necessarily know exactly what bits of it other parts of the code might need, so why not just leave it all open and you can do what you want with it!

This is likely to cause problems, particularly for large classes and/or large projects. As with singletons, having many places and ways the data in your class is accessed makes bugs a) more likely and b) harder to track down. On the other hand, if you define fewer, stricter ways other classes can interact with this class, bugs are less likely and when they do happen there are fewer places you have to look.

Strict interaction rules make bugs less likely and easier to find

Imagine your player class has a public float for health. Enemies, bullets, healthpacks etc all alter its value directly. Early in the project this might be easy, when things aren’t complex. Later on however, the player’s health must follow more rules (minimum value, maximum value, invulnerability frames…) and all the bits of code accessing it must implement all of those rules. It’s easy to imagine a case where some bit of code doesn’t follow the rules, and causes unexpected behaviour.

Plus, if the player’s “health rules” change, all the bits of code that access the health must also be changed in line with them, making it harder to refactor the internal workings of the player class.

By comparison, if health is private and the player class has methods like Damage(float d) and Heal(float h), all the rules can be written internally. Other classes don’t need to know about the rules, and are therefore free to do their thing without worrying about how the player works internally.

If in doubt, go Private

Of course, there’s a half way point between these scenarios. You could have the Damage and Heal methods, but still have health as a public field. In my opinion, it’s best not to do this, for a couple of reasons. Firstly, strictly making it private forces you to write explicit inputs/outputs for everything you do; you’re more likely to follow the rules! Secondly, in a complex project, if you leave a field public and then need to use it months later, you might have forgotten exactly how that class works and end up using the field directly when you shouldn’t. If you’ve written inputs/outputs for everything the class is designed to do, and only allow access in that way, it’s clear to yourself (and team members) how that class is designed to be used.

Default to private, and only make things public when necessary

With that in mind, a good habit to get into is defaulting to private, then only making things public when necessary. It’s only natural to think the other way around to begin with, but defaulting to private helps you automatically think about classes in terms of how they interface with other code. In fact, C# already “thinks” like this – any field or method without a stated access modifier is private by default.

What about the Inspector?

Given that this is a series on Unity, you may have spotted a problem. Using your MonoBehaviours in Unity depends on having public variables you can set in the inspector. These might be things you never intend other classes to actually access, but as above – if they’re public, you or someone else might forget that later on and do something wrong.

Fortunately, Unity has you covered. The [SerializeField] attribute can be applied to any private (or protected) field[2]note that this is ONLY for fields – properties do not work! to expose it in the inspector.

public class Player {
	[SerializeField]
	private GameObject _bulletPrefab;
}

For the sake of one extra line, it’s almost always worth doing this. Again, the aim is to think in terms of encapsulation by default, not as an afterthought!

Properties

As well as using methods, C# offers another tool for encapsulation, properties. The simplest and probably most common use of properties is to allow external code to read data, but not alter it.

public class Player {
	public float health {get; private set;}
}

This allows you to control access to data, but keep it readable and simple.

Interfaces

Ok, so this is a topic in and of itself, and I’m not getting deep into it here. However, using interfaces is a way of strictly enforcing everything mentioned above. Interfaces are like classes, but without implementation. So they’ll declare a method signature (name, return type, arguments) but not give it a body.

public interface IDamageable {
	void Damage(float d);
}

Note the lack of public against the method signature – everything in an interface is public, or there’d be no point!

A class will then implement an interface:

public class Player : IDamageable {
	public void Damage(float d){
		// Do stuff here
	}
}

Other code can refer to Player as an instance of IDamageable, and has access only to those things declared in the interface. This explicitly separates the internal and external workings of a class. Thus, anything that references instances of IDamageable is insulated from any changes to how the class itself works. Put simply, the interface can remain stable, while the internal workings are free to change. You can even have many classes that implement IDamageable, so not only can implementations change, there might be many of them in your codebase simultaneously. The code that calls Damage() doesn’t know or care whether it’s a player, enemy, exploding barrel or something else, and that’s ideal.

Again, this is just a mention for completeness – interfaces are a complex topic, and I strongly advise not to use them unless you understand them well!

Conclusion

While I’ve mainly explained encapsulation in terms of public and private fields, it’s a general concept that should apply to everything. Good design generally limits the amount each part of your codebase knows about each other part. That influences fine details such as whether a field is public, but also broad questions of the overall structure of your project. Designing robust, stable methods for communicating between separate bits of code takes longer than just exposing everything publicly, but becomes essential as your project gets larger. Doing it from the start minimises the chance you’ll have to go back in and fix everything later!

Encapsulation also helps account for the inevitability of human fallibility. You will break your code. You will write bugs. You will need to optimise. Encapsulated code means you can swap out internal workings with less worry you’ll break something else in doing so.

So it’s worth getting into the habit – it’ll save you and your colleagues time and stress later down the line. The more you do it, the more natural it becomes.

Footnotes   [ + ]

1. and protected, but I’m ignoring that for the sake of simplicity
2. note that this is ONLY for fields – properties do not work!


5 Comments

  1. Great post and good advice. I posted something similar with an example of how I've seen people break their prefabs accidentally when not using proper encapsulation. It's nice to see others recommending good practices for Unity development. Nice work! http://unity3d.college/2016/02/15/editing-unity-variables-encapsulation-serializefield/
  2. Great reading. Anyway I disagree regarding interfaces. They are THE keystone for SOLID principle (highlight responsability, ensure low coupling, ease testing, ...). My advice here as a senior developer would be to stop coding while you don't have a good understanding of those concepts. Best RoI you will ever have. Period.
  3. Pingback: http://www.sigtrapgames.com/unity-software-design-inheritance-and-composition/

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>