As far as I understand, your code is handling a response from some API, which can be any of "secretary", "head", "security", "teacher" and anyone who will be added in the future. It is acting as a factory that creates an object of specific type based on the data in the response.
What it means to be open-closed? It means, that you can support additional types of responses without updating and rebuilding this piece of code. Actually, the "closed" part of the principle says that the code should actively discourage changes as a means of extension.
The part that prevents extension without modification is the knowledge of the specific types. If you want it open-closed - get rid of this knowledge. You can change it for knowledge about some abstraction that all specific types do satisfy.
For every specific type you can provide a factory with two methods: doesResponseMatches(response)
and createInstance(response)
. Then the responsibility to construct a specific instance from a response will be in hands of this specific type itself.
Next, you code can keep a collection of such individual instance factories. Upon each response it will iterate over this collection and find the one that matches.
How do you get such collection?
In your application there should be some central piece of code that (usually during startup) wires all pieces together. This piece of code knows about all dependencies of the application, about specific implementations and configuration and about the startup sequence. So, it depends both on the abstract piece of code and on specific response types. During the startup it can register all specific type factories with the open-closed code. To fill that collection inside it.
Another way to register those specific "extensions" is to give them the instance of open-closed code and let them register themselves with some method.
So, there are three parties here. The "open-closed" module, the application wiring module and an extension module. You may pick who whether extension modules know about the "open-closed" one, or they don't - and then the wiring module does a little bit more.
But back to my first comment. I question whether you actually need to adhere to the open-closed principle in this code. If you maintain and distribute both all the code in a single artifact and don't expose this "open-closed" part to some other developers for use... I.e. if it is just you internal implementation detail - than your original example is actually simpler and more readable. Because from a single screen you can see right away to which parts of code the control will go next. It is easier to debug and reason about.