One of the design patterns in programming in Open-Closed Principle. It is part of the standard SOLID principles and takes it place at the ‘O’.
According to Wikipedia,
In object-oriented programming, the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification“; that is, such an entity can allow its behaviour to be extended without modifying its source code.
Okay! So we now understand by open we mean a class should be open for extension, and by closed we mean a class should be closed for modification. Let’s dive in, to make things clearer.
We have this rather beautiful scene where we have this NPC. We will have different kinds of NPCs with different behaviors.
To control NPCs we have a NpcControllerEnum script which will contain an enum of different NPC types and will react according to the type.
Let’s start with 2 NPC types
Now, here’s our controller script which switches to different behaviors based on the enum
So far, so good. We can tweak speeds from the inspector and change enemy type to get desired enemy behavior.
We needed 2 kinds of NPCs and we have them. If you are sure you won’t have any new types of enemy then we are good to go. But that’s rarely the case.
Let’s say we have a new requirements to add a new NPC which can wander around in a certain radius. What can we do? Obviously we can go to the enum and add another enemy type as RandomMover and add another case to our switch statement on NpcControllerEnum. If we do this we immediately break the Open-Closed Principle. Because to add new behaviors we are modifying the class, also modifying the enum. That class should be closed for modifications. What we can do is extend is. But from here extension is something that our switch statements with enums won’t allow us to do. So we have to think differently.
Let’s pause and think for a bit what we are doing here. We are checking the type of npc behavior in update and doing something that’s specific to that type. Essentially, we are “Behaving” according to the “Behavior Type”. Instead of being so specific, can we be more generic?
Let’s create a NpcBehavior abstract class and put an abstract method there named Behave(). By doing this, we are making a generic behavior. Now whenever we want a new behavior, we inherit this class and override the Behave function. Let’s make this inherit from Scriptable Object.
Now we have a generic behavior. From the Controller class, all we have to do now is create a field for NpcBehavior and from update just call npcBehavior.Behave()
Cool! All that switch, enum messy code is gone just like that. What the controller script is doing now is this
Controller: I don’t care what the behavior is! Just run the Behave function of that behavior
Let’s make our first behavior now
Simple and elegant. All we have to do is create a scriptable object instance of this and drag and drop that object in the controller. That’s it. Now we can add as many behaviors like this and just drag and drop its scriptable object instance to the npcBehavior field of the controller . It doesn’t matter how many behaviors we create we won’t have to modify the controller class for it, we just have to extend the NpcBehavior class.
Here is another behavior
I have added one more RandomMove Behavior and the final result looks like this
Here is the github link to the project files