At the risk of being “that guy”, this isn’t actually true. Encapsulation does not mean that other objects can’t change the states of an object, just that they can’t do so DIRECTLY. They always have to ask the object to do it for them. So, in this case, there’s nothing in OO rules that says that a bank transaction can’t, as a side effect, change the name of the customer. It only can’t do so by reaching into the instance of the account and setting the “customerName” instance variable itself. It would have to call something like “setCustomerName” or, more reasonably for the sorts of cases you’re talking about, call the “changeCustomerName” method.
This sort of thing is, in fact, what a system with a lot of varied and strange events that can impact a lot of different objects wants to do. First, you don’t want to have to have all of these varied objects actually have to know what the actual logic of changing a customer name is. Not only does this add complexity to them, but it also is a royal pain if you ever have to change that logic. The more places where you might want to change a customer name, the more you want to encapsulate it into an object. Second, OO allows for polymorphism, where anyone who wants to do something can simply call a method on all the objects it can reach and let them figure out what it means. So, to use your explosion example, if you defined at your highest level — potentially even at “Object” — a method called “dealWithExplosion” then all you’d need to do is call that on any relevant object and then go on with your life, letting them figure out what it means to them. You would have to add in all possible relevant information you have — or at least have that available to the object — but ultimately each object decides what that means to them. So if you have objects that don’t explode and never take explosion damage, you can simply have it do nothing when it gets told to deal with an explosion. Third, if you have many objects doing this all at once, you’re going to have to deal with synchronization, so that you don’t end up with negative HP because the heal happened while the damage was happening and everything got screwed up. If you don’t encapsulate the things that need to be synched in an object, then each caller has to remember to do it and release it when done, whereas in an object you can do it all internally without anyone else having to bother with it. Fourth, if you want to have mixed lists of various objects having them encapsulated as per the polymorophism above is the most efficient way, as you can group them together and call the base method on all of them without having to check to see if they support the method or not. Finally, OO here makes bug fixing easier, because while you can still have many, many objects changing the state they all have to go through the class’ state changing methods, meaning you can simply put logs there telling you what’s happening and where it gets screwed up and then trace it out to the caller, whereas without encapsulation you have to find any instance where anything changes at least that state variable to see if it could be causing the issue.
The way massive events tends to be handle in Java is that when something happens to an object it generates an event, passes it up to some sort of EventDispatcher, which then distributes it to any object that cares about that event by registering itself as a Listener. This is relatively clean, but does have a bit of an issue with performance (especially since in the programs I’ve worked with a lot of the time you get duplicate events that all need to be processed). But that’s the general idea for handling these sorts of asynchronous events: generate an event and tell everyone who cares to deal with it themselves, generating events as necessary. Doing that is SO much easier if things are properly encapsulated.
polymorphism
-> polymorphism is a bad abstraction for games
dealWithExplosion So if you have objects that don’t explode and never take explosion damage, you can simply have it do nothing when it gets told to deal with an explosion
-> the problem is you end up with an object with hundreds of those functions -> it's really hard to know whether a method does anything at all
Fourth, if you want to have mixed lists of various objects having them encapsulated as per the polymorophism above is the most efficient way, as you can group them together and call the base method on all of them without having to check to see if they support the method or not
The way massive events tends to be handle in Java is that when something happens to an object it generates an event, passes it up to some sort of EventDispatcher, which then distributes it to any object that cares about that event by registering itself as a Listener.
-> you don't really want to do that in video games, instead you want to do "all X events" then "all Y events"
What you're describing is indeed the common wisdom in any workplace using OOP.
That said, my own experience (and the common wisdom in workplaces using ECS or functional programming) is that the principles you describe are much more situational and limited than they look.
Class-level encapsulation, where the big boundary is between a class's private data and public methods, works great in situations where you're dealing with collections of complex items, where each item's complexity is self-contained and its interactions with the outside world are simple and predictable.
Its great in situations where you have a collection of customer, and customers each have a collection of items, and a customer only needs to care about their items and not the outside world
OOP-style encapsulation becomes a much lousier abstraction of the world in situations where systems are interacting with each other in a non-hierarchical way, and a class's methods have much less power to maintain its invariants because its invariants depend on the outside world
Modern game engines use and ECS model, where data is grouped in entities and components One big list of entities
Very easy to check memory safety and safe parallelism