Some of the best wisdom in OO design is captured by the Gang of Four. Artima has an interview with Erich Gamma where he discusses the core principles from the intro of Design Patterns:
Program to an Interface, not an Implementation
Once you depend on interfaces only, you're decoupled from the implementation. That means the implementation can vary, and that's a healthy dependency relationship.
Rod Johnson expands on this idea in Chapter 4 of the pre-Spring book, and provides some very well-phrased guidelines:
Program to interfaces, not classes. This decouples interfaces from their implementations. Using loose coupling between objects promotes flexibility.
A few of the many advantages of an interface-based approach include:
- The ability to change the implementing class of any application object without affecting calling code. This enables us to parameterize any part of an application without breaking other components.
- Total freedom in implementing interfaces. There's no need to commit to an inheritance hierarchy. However, it's still possible to achieve code reuse by using concrete inheritance in interface implementations.
- The ability to provide simple test implementations and stub implementations...
Turning back to the Gamma interview...
Favor object composition over class inheritance
Composition has a nicer property. The coupling is reduced by just having some smaller things you plug into something bigger... In a subclass you can make assumptions about the internal state of the superclass when the method you override is getting called. When you just plug in some behavior, then it's simpler. That's why you should favor composition.
Mr. Johnson expands:
To clarify the distinction, let's consider what we want to achieve by inheritance. Abstract inheritance enables polymorphism: the substitutability of objects with the same interface at run time. This delivers much of the value of object-oriented design... Concrete inheritance enables both polymorphism and more convenient implementation....Interface inheritance (that is, the implementation of interfaces, rather than inheritance of functionality from concrete classes) is much more flexible than concrete inheritance.
Robert Martin is another thought leader on core principles, has defined several core principles including the following:
[NOTE: Still under development]
The Single Responsibility Principle (SRP)
There should never be more than a single reason to change a class... Changes to one responsibility may impact other responsibilities.
Open Closed Principle (OCP)
A module should be open for extension but closed for modification
Should be able to add behavior without modifying common behavior
Liskov Substitution Principle (LSV)
The Interface Segregation Principle (ISP)
Subclasses should be substitutable for their base classes
Interfaces shouldn't have stuff that their clients don't need... Factor interfaces so that clients can use just relevant members
The Law of Demeter
Only talk to your friends who share your concerns... Never call a method on an object that you got from another call or on a global object.
The Dependency Inversion Principle
Modules should depend on abstractions, not concrete modules... Details [controllers?] should depend on abstractions, not the other way around.
"Inversion" in contrast to structured, procedural thinking.