Object Oriented Design principles are very important and are sometimes considered even more important than design patterns. All design patterns are based on one or more of the design principles. Having thorough understanding of the design principles will help you understand the design paterns better and faster, and will even help you solve many design scenarios if you can't find a design pattern fitting that scenario.
Program to interfaces
Program to an interface, not an implementation, as it will prevent any dependency on any specific implementation.
The interface here mean and abstraction and can be either an interface or an abstract class.
Don’t repeat yourself (DRY)
Avoid duplicating code. Move it into a separate function or class.
Coupling is the degree to which one class knows about another class. If the knowledge is less, it is called loosely coupled. If knowledge is more, it is called tightly coupled.
Loose coupling is always preferred as it allow us to build flexible OO systems that can handle change.
For instance, if the knowledge of one class about other class is only through exposed interfaces (data hiding), the system is loosely coupled and easier to change than a system where classes access data members directly.
Encapsulate what varies (EWV)
Identify the aspects of your application that vary, encapsulate them and separate them from what stays the same.
Head first design patterns book gives Duck example to demonstrate this. Consider the parent Duck has a method fly(), but then there are many type of Ducks that doesn't fly. One quick ineffective solution is to override this method in every subclass of Duck that does not fly, to do nothing. This will turn out to be nightmare when more and more Ducks are added. Instead, you can separate this fly behavior into a separate class backed by an interface and then every class that need this behavior can use this class.
Prefer composition over inheritance
Classes may achieve polymorphic behavior and code reuse by containing other classes that implement the desired functionality instead of through inheritance.
You may have an abstract reference type (Program to Interfaces) and allow clients to change behavior at runtime by passing in any implementation.
Using composition over inheritance also reduces code duplication.
Example 1: Stack class in Java is an example. It extends a List and exposes additional list methods which are not meant to be there for a stack. A better approach would have been to contain the list as a member and use it for storing the stack elements.
Example 2: In our previous Duck example (EWV), we can achieve this by having an interface reference for the fly behavior within Duck and based on the required fly behavior we can pass (or even inject) the correct implementation. Instead, if we had extended the Duck class with a fly behavior implementation, further extensions would have been difficult (against OCP). Also, if we had implemented an interface for fly behavior to allow further extensions, then every class would have had to overwrite it leading to code duplication (against DRY principle).
Keep it simple, stupid (KISS)
Simplicity should be a key goal in design and unnecessary complexity should be avoided. Many design patterns try to decompose the system into smaller sub systems to achieve this.
Every class should be testable independently without it invoking any other class.
If class A create and invoke another class B directly, then while unit testing class A, we will be also testing class B and hence technically it cannot be called unit test, but an integration test. If a class needs to create and invoke another class, we can use dependency injection to inject a constructed object (DIP) so that for unit testing you can pass in a dummy mock object.
Note: Latest version of this note can be found @ http://codingarchitect.com/additional-design-principles-and-best-practices.