“High-level modules should not depend on low-level modules.
Both should depend on abstractions.”
“Abstractions should not depend on details.
Details should depend on abstractions.”
This is a very important architectural principle. Maybe the only one you need to start good software with polymorphism in mind. It states that all implementation packages should be directly connected with others via abstract/interface packages
Here is a high-level overview:
The idea behind this solution is to separate major packages implementations. Separate the runtime instance from its types. Concrete classes should be grouped in highly-cohesive packages separated by the architectural requirements of the layers (User Interface, Application, Domain, Technical Services, etc.). But then any contact between them should be settled through abstract type packages. In the above example, the lower package implements a high-level package interfaces or abstracts, while any concrete layer will communicate only by the abstractions via interface implementations or calls to abstract types only. This architectural distribution helps to create new implementations, loading them at runtime, without requiring any changes by clients.
From the implementation perspective the package with the abstract types should have the instantiation mechanisms of the concrete via configuration files or any external source from the package. That means the use of Factories and Factory methods and dynamic binding of concrete classes.
Under the MVC pattern this architectural model should be applied carefully, separating the UI pattern from the application layer (Interactors [R. Martin], Control [I. Jacobson], Controller [C. Larman]) and the Domain classes layer.