This is kind of a philosophiocal conversation, but does anyone here write scripts super-defensively so that public variables changed at runtime won’t cause unintended behavior?
In normal OOP, you’d have a constructor with starting parameters that then get assigned to private variables and managed internally. You can run checks in the constructor to ensure that the data was well-formatted to start, and you can keep data private to ensure it isn’t changed later. But you can sort of think about public variables in a MonoBehaviour as constructor parameters and instance data all-in-one, which introduces a whole host of theoretical problems.
Someone setting up a scene can assign values through the editor (analogous to supplying values to a constructor) and you could perform checks on that data in the Start() callback (analogous to a constructor), but then those same variables are customarily used throughout the life of the scene and re-referenced whenever necessary, as if they’re immutable.
Obviously most things made in Unity aren’t safety critical and you can pretty much assume that someone isn’t modifying public variables maliciously, but does anyone take this into account just from a ease-of-debugging perspective? Like if you can ensure that your variables aren’t getting changed on you, then you can eliminate that as a possibility when troubleshooting complex code.
The only pattern I can think of to account for this would be to make a private readonly copy of every public variable, and copy everything over on Start(). Or you could give every MonoBehaviour an inner class with a conventional constructor, which obviously seems like overkill.
Very interested to see if anyone has thoughts on this.
private CharacterBase _target;
public CharacterBase Target
{
get => _target;
set
{
if (_target != value)
{
CharacterBase old;
old = _target;
_target = value;
OnTargetChanged?.Invoke(old,value); //old then new
}
}
}
After all you can do a lot with this, you can do a before and after changed event depending on what you want, and you can have the before even block the change if necessary
Yes, I only make public getters for such fields where the consequences are unforeseeable.
About the only good place to use public fields is in DTOs.
ScriptableObject can be considered one, at least it has build-time immutability ie cannot permanently be changed at runtime in builds, but the editor behaviour is the opposite, thus I would absolutely recommend not to use public fields in SOs.
That is the purpose of hiding internals. That, and be able to make changes more easily, and be able to put a breakpoint on any public setter.
Mutable state increases complexity a lot, so you should keep that tightly encapsulated as much as you can inside methods and classes, to avoid that complexity spreading across your codebase and becoming unmanageable.
I mean you can serialize the backing field of a property:
[SerializeField]
private float _someFloat;
public float SomeFloat
{
get => _someFloat;
set => _someFloat = value;
}
Which is effectively the same as what the [field: SerializeField] syntax is doing. But here you can omit the setter and just have it a read-only value.
But I think the only time I ever use public fields is in nested classes/structs that are internal to another object’s function. Otherwise as Sisius notes above, it’s best to keep that tight encapsulated.