A Short Overview of Object Oriented Software Design
By: Stanislav Kozlovski
Tags: Software Development, Object Oriented, Software Engineering, Programming, Python

Introduction

Most modern programming languages support and encourage object-oriented programming (OOP). Even though lately we seem to be seeing a slight shift away from this, as people start using languages which are not heavily influenced by OOP (such as Go, Rust, Elixir, Elm, Scala), most still have objects. The design principles we are going to outline here apply to non-OOP languages as well.

To succeed in writing clear, high-quality, maintainable and extendable code you will need to know about design principles that have proven themselves effective over decades of experience.

Disclosure: The example we are going to be going through will be in Python. Examples are there to prove a point and may be sloppy in other, obvious ways.

Object Types

Since we are going to be modelling our code around objects, it would be useful to differentiate between their different responsibilities and variations.

There are three type of objects:

1. Entity Object

This object generally corresponds to some real-world entity in the problem space. Say we’re building a role-playing game (RPG), an entity object would be our simple Hero class:

These objects generally contain properties about themselves (such as health or mana ) and are modifiable through certain rules.

2. Control Object

Control objects (sometimes also called Manager objects) are responsible for the coordination of other objects. These are objects that control and make use of other objects. A great example in our RPG analogy would be the Fight class, which controls two heroes and makes them fight.

Encapsulating the logic for a fight in such a class provides you with multiple benefits: one of which is the easy extensibility of the action. You can very easily pass in a non-player character (NPC) type for the hero to fight, provided it exposes the same API. You can also very easily inherit the class and override some of the functionality to meet your needs.

3. Boundary Object

These are objects which sit at the boundary of your system. Any object which takes input from or produces output to another system — regardless if that system is a User, the internet or a database — can be classified as a boundary object.

These boundary objects are responsible for translating information into and out of our system. In an example where we take User commands, we would need the boundary object to translate a keyboard input (like a spacebar) into a recognizable domain event (such as a character jump).

Bonus: Value Object

Value objects represent a simple value in your domain. They are immutable and have no identity.

If we were to incorporate them into our game, a Money or Damage class would be a great fit. Said objects let us easily distinguish, find and debug related functionality, while the naive approach of using a primitive type — an array of integers or one integer — does not.

They can be classified as a subcategory of Entity objects.

Key Design Principles

Design principles are rules in software design that have proven themselves valuable over the years. Following them strictly will help you ensure your software is of top-notch quality.

Abstraction

Abstraction is the idea of simplifying a concept to its bare essentials in some context. It allows you to better understand the concept by stripping it down to a simplified version.

The examples above illustrate abstraction — look at how the Fight class is structured. The way you use it is as simple as possible — you give it two heroes as arguments in instantiation and call the fight() method. Nothing more, nothing less.

Abstraction in your code should follow the rule of least surprise. Your abstraction should not surprise anybody with needless and unrelated behavior/properties. In other words — it should be intuitive.

Note that our Hero#take_damage() function does not do something unexpected, like delete our character upon death. But we can expect it to kill our character if his health goes below zero.

Encapsulation

Encapsulation can be thought of as putting something inside a capsule — you limit its exposure to the outside world. In software, restricting access to inner objects and properties helps with data integrity.

Encapsulation black-boxes inner logic and makes your classes easier to manage, because you know what part is used by other systems and what isn’t. This means that you can easily rework the inner logic while retaining the public parts and be sure that you have not broken anything. As a side-effect, working with the encapsulated functionality from the outside becomes simpler as you have less things to think about.

In most languages, this is done through the so-called access modifiers (private, protected, and so on). Python is not the best example of this, as it lacks such explicit modifiers built into the runtime, but we use conventions to work around this. The _ prefix to the variables/methods denote them as being private.

For example, imagine we change our Fight#_run_attack method to return a boolean variable that indicates if the fight is over rather than raise an exception. We will know that the only code we might have broken is inside the Fight class, because we made the method private.

Remember, code is more frequently changed than written anew. Being able to change your code with as clear and little repercussions as possible is flexibility you want as a developer.

Decomposition

Decomposition is the action of splitting an object into multiple separate smaller parts. Said parts are easier to understand, maintain and program.

Imagine we wanted to incorporate more RPG features like buffs, inventory, equipment and character attributes on top of our Hero :

Lack of decomposition

I assume you can tell this code is becoming pretty messy. Our Hero object is doing too much stuff at once and this code is becoming pretty brittle as a result of that.

For example, one stamina point is worth 5 health. If we ever want to change this in the future to make it worth 6 health, we’d need to change the implementation in multiple places.