Make your system flat instead of deep: big objects that don't talk to each much at all, not a boatload of teeny ones talking to each other. One type of data can often be modified in one central place in a loopy fashion, like
You don't need to disperse that logic across 50 different classes that have to talk to each other and mutate their own little localized state. Doing so will lead to each one being individually simpler, but to reason about what they do physics-wise, will be, as a whole, far more complex due to the spiderweb of interactions that introduces. Just stop and think about breaking that physics system down into 50 different classes and abstract interfaces, however individually simple. Think about what that really does to your ability to comprehend what the physics engine will do, and realize how retarded we can be with OOP, making things exponentially more complicated as a whole in an effort to make each individual one simple.
Look at how game engines do it. They have the right idea.
The notion of a
Car entity contributes barely any complexity to the system, as it is merely a collection of components and contributes no functionality to the system. And the components barely contribute any complexity to the system, since they are just data. The system breaks down into a straightforward flat pipeline of heavyweight, largely independent systems containing the functionality, not a deep spiderweb of interactions between abstract interfaces representing the most granular ideas far divorced from the business requirements. Not only will it substantially reduce the code required for the software, it will also make it easy to break down its design into a handful of systems you need while allowing you to talk to managers and customers about the system organization (which is literally just the organization of these systems) in a way that they can actually comprehend to a far greater degree, like
And it's extensible and maintainable AF, allowing you to tackle whole new unanticipated design ideas of a kind that would break the most carefully designed abstractions, while still only requiring you to change one place in the code without cascading changes.