The Curious Dev

OO Design Principle

The object-oriented version of spaghetti code is, of course, ’lasagna code’. Too many layers." - Roberto Waltman

Object oriented code means two things to me:

  1. Just because you use classes and interfaces or virtual methods in your code does not automatically make it object oriented.

  2. Code should utilize late-binding polymorphism for extensibility.

We want to write code that is easy to understand, write and test once. Code is read by humans afterall. Programming is the act of telling other programmers what you want the computer to do. In addition, when our requirements change, we want to be able to modify the exisiting behavior or add new changes with minimal effort (without having to change multiple classes for example). Change is risky and introduces instability so it is best to design our classes to be open for extension but closed for modification. This is the open-closed principle.

To achieve this, we need our classes to depend on stable abstractions rather than concrete implementations. We can then extend behavior by passing different concrete implementation of those types in our system polymorphically. This is called the Dependency Inversion Principle.

For instance, a trivial example would be if we had different ways of printing a document. We could print to PDF or LaTex or to a File. Our Printer could vary at runtime but that is a choice the calling code should make. We can implement an abstract interface that the client code depends on.

public interface Printer {
	void print(Document document);
}

public class PDFPrinter implements Printer {
	void print(Document document);
}

public class LatexPrinter implements Printer {
	// There's a reason for the exception. Continue reading
	void print(Document document) throws PrintingException; 
}
...
// client code
Printer printer = new PDFPrinter();
printer.print(document);

In the future, when there is a new way to print (say HTMLPrinter for instance), we simply implement the Printer interface and swap one subtype for another in the client code.

The code above is extendable but it begs a question -

What confidence do we have that swapping one subtype for another subtype will result in the same expected behavior?

In order words, how do I know the new HTMLPrinter honors the contract defined in the Printer interface and ONLY prints? What if the subtype doesn’t even print anything or prints the document and changes the default color of the printer’s ink?

This is where the Liskov Substitution Principle (LSP) comes into play. Barbara H. Liskov and Jeannette M. Wing wrote a paper in 1994 that answers this question - how do we know our extensions are polymorphic? In short, it says if S is a subtype of type T, then any property provable about T must be true for S. Meaning, all subtypes S must honor the contract defined in super type T. In our example above, the LatexPrinter violates LSP because it throws an exception. The Printer contract does not ask the print() method to throw an exception.

This is literally the basics of writing good object oriented code. A piece of code is OO if it

  • models a system or domain
  • uses late-binding polymorphism for extensibility (credits to @kevinburleigh for the definition)

Late-binding enables us to write our business logic first and then worry about the concrete implementation of types. Check out Uncle Bob Martin’s 11 principles of OOD. Yeap, it’s not just the SOLID principles that exist. Wheettttt??? I know, found not too long ago too.