The principles you are talking about are generally applicable to any OO language. The basic principle here is “free communication”. A class that depends on another class (contains an instance of it and calls methods on it as part of its own work) really depends only on the set of functions provided by the dependency. If a class defines a link to a specific class on which it depends, and then you want to replace the class with another, you will not only need to develop a new class, but also change the dependent class so that it depends on the new type. This is usually bad, because if your class depends on many other classes, you need to change the code in several places, requiring you to test all the uses of these objects to make sure that you have not violated previously working functions.
Interfaces were designed to eliminate this, allowing several classes that are not related to each other's pedigree to be used interchangeably based on a common, forced set of methods that you know the class will implement. If, instead of depending on the class, you depended on the interface, any class that implements the interface would fulfill the dependency. This allows you to write a new class to replace the old one, without a class that uses it, knowing the difference. All you need to change is the code that creates the concrete implementation of the class that fills the dependency.
This presents a predicament; Of course, your Depender class may say that it needs IDoSomething instead of DoerClass, but if Depender knows how to create a DoerClass to use as IDoSomething, you haven’t received anything; if you want to replace DoerClass with BetterDoer, you still have to change the Depender code. The solution is to give responsibility for providing the dependency class to a third party, the Creator. The class selected for this depends on the context. If a class naturally has both a Depender and a DoerClass, this is an obvious choice for combining them. This often happens when you have one class that has two auxiliary dependencies, and one dependency needs another. In other cases, you can create a Factory that exists to provide the caller with an instance of a specific object, preferably with all the dependencies connected.
If you have several interdependent classes or dependencies at many levels, you can consider the IoC framework. IoC containers are factories, because the repositories are for DAO; they know how to get you a fully hydrated instance of the ANY class that requires one or more dependencies, for example, a repository can create any fully hydrated domain object from data in the database. He does this by saying that a particular class should be used to populate dependencies in a specific situation, and when asked to provide a class, he will instantiate that class by providing instances of the necessary dependencies (and dependency dependencies). This may allow patterns in which class A depends on B, which depends on C, but A cannot know about C. The IoC structure knows about all three and will instantiate B, give it a new C, and then give B a new a.