Object-Oriented Design Principles

Introduction

When I first learned the object-oriented programming (OOP), I was offered simple examples such as a Dog is an Animal and so does a Cat, or a Car has an Engine and four Wheelss, where the former is an is-a relationship indicating inheritance and the latter is a has-a relationship indicating composition. Although these are perfect examples for teaching OOP concepts, they are far from the principles of designing good OO system. The most important criterion of a good OO design is the flexibility: how troublesome, for example, the number of class files we need to touch, if we want to make a certain change. Object-oriented design principles are several guidelines to help us make our software flexible. In this blog, I would like to introduce several design principles I learned from this book.

Encapsulation

There are several principles are around the concept of Encapsulation. The first one is that “each individual class does only one thing”. This implies that once we need to make a change of the code, we probably only need to make a change at one place. Especially, we would like to encapsulate what might vary in future. The “Open-Closed Principle” states that classes should be open for extension and closed for modification. The closeness is achieved by encapsulation, and the openness is achieved by abstraction (inheritance and composition). A good sign of needing encapsulation is when one has an urge of copy-pasting code.

Abstraction

Abstraction indicates relationships and interactions among classes. Making these relationships and interactions (behaviors) abstract, rather than concrete, can maximize the flexibility of the system. There are three principles:

  • Program to interfaces, not implementation
  • Favor composition over inheritance
  • The Liskov Substitution Principle (LSP)

The “program to interfaces” is easy to understand. If classA needs classB to do something, classA should care the result rather than the implementation of classB. If we find a better approach in classB, we don’t need to change classA at all. A good example of using this principle is the stratergy pattern.

The “favor composition over inheritance” principle is a little subtle. Apparently, we can prevent code duplication either way. The difference is that, composition is dynamic: one can change the behavior of a class at runtime; whereas inheritance is more rigid. Especially, when the base class and the subclass have conflicting behaviors. These might not be very obvious during the first design of the system, but could become a prominent problem when changes happen. These lead to the third principle: the “Liskov Substitution Principle”.

Barbara Liskov received the 2008 Turing Award for her contribution to the design of programming languages. The “substitution principle” states that “when you inherit from a base class, you must be able to substitute your subclass for that base class.” We should always apply this principle whenever we want to make a subclass. Especially, when we change our code to accommodate new requirements, we should double check the substitution principle still hold. Two alternative ways besides inheritance are Delegation and Composition. Delegation is to another classes as it is. Composition allows you to use a family of other classes. The difference between composition and aggregation is very subtle: composition means the host class is responsible of the destruction of the other classes, where the aggregation does not. For example, in the observer pattern, the observer reference in the observable class exists by itself. This is an aggregation.

(TODO: I hope to write how various OO patterns utilize OO principles in my next post.)

Written on January 2, 2018