I think you need to make a clear distinction between application design and system design (devising the architecture of an application).
Application design is, what you see from the outside. It revolves around feature design, user experience design and even marketing decisions (deployment and monetarization strategies). Of course application design cannot be unaware of technical limitations, but taking them into account is the part that almost everybody get's right, while all the rest is, what a lot of people get wrong.
System design is possibly what you mean. It revolves around dissecting an application into independent units (tiers, services and other modules), according to the application design and then reiterating this step again until you are left with manageable units.
The key to flexible system design is low coupling, which is achieved through information hiding, one part being encapsulation (i.e. tying nasty and fragile implementation details up in robust objects) and the other abstraction (i.e. choosing not to depend on a concrete module, but the smallest possible abstraction of it).
For example, if you have a class DataStore, providing access to stored users and pictures, thus acting as IUserStore and as IPictureStore, you want the individual renderers to depend on those abstractions and inject the implementation. Because at some point, you might decide, that you want to keep your user data on your server, but to store your pictures with some 3rd party cloud service, which means dividing the DataStore into two classes might be neccessary. All components that solely depend on abstractions, are unaffected.
What you want is a system, where single components can be modifyied in isolation, without effect to the system.
The only question left for you to answer is, when to consider a component itself as a system and to further modularize it and when to stop. The SRP is a radical rule, but its hard to stick to.
I prefer a more pragmatic approach. As long as a component is easy to grasp, leave it as is. When it grows past a point, where you can understand it within a minute or reimplement it within half a day, it's time to single out and isolate individual responsibilities.
But this is a line to draw for yourself, and it's something that naturaly happens in the field.
So to summarize:
- Do not abstract ahead of requirements. Use abstractions to express requirements clearly and exactly, keeping them simple and obvious.
- Do not modularize ahead of complexity. Naturally keep together what's simple and break down what's complex.
- Do not implement ahead of abstractions. Make no unneccessary assumptions, depend on abstractions, rather than details.