Is your code Singular? Responsible? Principled?

Share on facebook
Share on google
Share on twitter
Share on linkedin

Is your code Singular? Responsible? Principled?

In this blogpost I’ll be discussing what I’ve learned about the below terms whilst coding a Tic Tac Toe game as a new Apprentice:

  • Single Responsibility Principle
  • Encapsulation
  • Polymorphism
  • Duck Types

Over the past couple of weeks these are terms which I’ve heard mentioned but couldn’t describe yet. I had no language for them or what they meant for my code. However as I’ve worked on building my application and made inevitable mistakes, the importance of these concepts has started to hold together a little more. More notably, how their interaction with each other informs the overall architecture that I should be aiming for in my Tic Tac Toe application.

What follows is my understanding of the four named concepts, each broken down into three sections. As my application is written in Ruby, I will speak about the internal workings of my classes by referring to methods. I also use the example of a class throughout most of my explanations of these terms.

Image by Sebastian Voortman | Accessed at: Pexels.com

Single Responsibility Principle (SRP)

What is it?

This is the idea that within your code, each piece of functionality should have a single focus. Using the example of a class, a class should be clear on its purpose and should only deal with the things specifically aligned to that purpose, e.g. being and acting on a Board. Concerns about the detail of Board’s actions should be neatly severed from the rest of your codebase, so that if the way in which these actions are taken changes, they only need to be updated in one place.

James Ellis-Jones describes this well here:

Any change required of a code system will naturally need changes to the body of the code at a number of different points. If the system is structured following the principle of ‘gather together those things that change for the same reasons’, you will minimise the number of modules (classes) that need to change. This allows you to hide the change as much as possible behind encapsulation boundaries, thus stopping the change cascading out into the rest of the system, and reducing what needs retesting because it might be broken.

Why does it matter?

SRP matters for various reasons, some of which is further explained by my next section on encapsulation. It’s main advantage is that your code is cleanly separated out into logical divides, with each section solely concerned with doing its tasks exceptionally well and knowing exactly how to do them. For a class, this makes it easy to make changes to the internals — data structures, methods, variable names — of your class as needs be, and update your tests without having to go anywhere else in your codebase.

How to achieve it?

As you build your application try to think about what parts it needs in order to achieve its overall aim. Separate these individual aspects out into nouns, which will become your class names. Then, for each method within your class separate them into verbs, which are purposefully aligned actions that your class can perform (also described as behaviours your class has). Again think very carefully about the naming of these methods and what they do. Methods should be singly focused and ideally each able to achieve this focus within a few lines of code.

For reference, there’s a nicely described explanation of SRP here.

Encapsulation

What is it?

Following on from SRP, once you have classes which are singly responsible, each class should also be very security conscious and guarded. Whilst it can play alongside other classes within the application and even exchange messages with them, it’s very private about how it does what it does.

A class will usually have some methods which are private, which help it to do what it needs to from within. It may also have some public methods which other classes know about and can send messages to, and expect a response to be returned. However, they still shouldn’t know anything about how that response is generated, simply that they receive a response which they then know what to do with.

My visual description of encapsulation using a lock and key.

In the above drawing Lock takes care of the steps needed when unlock is called. If your lock then changed to a more complex security lock needing extra steps, only your lock class knows the details of the unlock method, and Key can continue to call unlock with no disruption.

Why does it matter?

Thinking about SRP, by ensuring your class is singly responsible and safely encapsulated, you can be confident that your class has control over it’s internal activity, making it much easier to make minor (or major) changes when needed.

If you decide to change that class’ actions as your application grows either in size or complexity, you should be able to do this safely without the worry that multiple tests outside of that class will break. Classes shouldn’t care about how another class does what it does, just feel safe in the knowledge that they can ask it to do something and it will.

How to achieve it?

Think about how reliant your class is on knowing intricate details about another class in order to achieve its functionality. This may raise questions about whether your classes are safely encapsulated and whether they are indeed singly responsible. If you have the time, test your application by making a change to a data structure, (e.g changing how my board is stored from an array to a hash) and see what happens when you do.

Polymorphism

What is it?

Polymorphism put simply, refers to one action which is performed in different ways. As I’ve come to understand it, polymorphism is generally supported by having parent and child classes. As children of a parent superclass, subclasses inherit behaviours, however how they enact those behaviours differs.

Why does it matter?

Polymorphic classes support encapsulation as they allow multiple child classes to inherit the same properties and behaviour. An external class which needs to interact with these multiple children doesn’t need to know which child it’s interacting with. It simply knows that all of them have a method that it can send the same message to and receive a response from. How that specific child produces that response is of no concern.

If you were building an application for a store which needs to deal with multiple product types but which all have the same properties, e.g. weight, you can have one other class which can call .weight on all these different products easily and receive the weight value.

My visual description of what my class structure for different player types might look like if it were polymorphic.

How to achieve it?

Thinking of the picture below, in a game of chess you might have a parent class ChessPiece. Child classes of ChessPiece would be the various individual pieces such as Pawn orRook which all have a method move_piece. According to the rules for how that piece calculates and makes it’s move, the details of how they produce their move will differ. However our Game will simply be concerned that it can call pawn.move_piece or rook.move_piece, and will receive a response from whichever child move_piece is called on.

Duck types

What is it?

Whilst I’d originally thought that this was exactly the same as polymorphism, it turns out that there are subtle differences between the two.

As I don’t actually have a Player superclass in my application, my Human and Computer are simple duck types which my Game is able to interact with when it calls current_player.get_move. In this way, my Game only cares that the class it’s interacting with has a method name which matches the one it’s looking for and that it gets a response that it can handle.

My visual description of how my Human and Computer classes are duck typed

Why does it matter?

This maintains SRP and encapsulation as Game doesn’t have to be concerned with whether it’s interacting with a duck or not, just that the thing quacks when asked.

How to achieve it?

If you create a method of the same name in different classes, which can be called by a separate class, you have created a duck type. Be careful to ensure that:

  • this is intentional (in all other circumstances you wouldn’t usually have the same method name across classes in your application), and
  • that the return value is one which the external class calling the method can handle
Screenshot of my Game class, which is able to call get_move irrespective of whether the current player is human or is a computer.

Learning about these four areas has already highlighted many ways in which my application can be better coded. I’m prepared for breaking tests whilst I fix these!!

(Note: further explanations about what objects and classes are have been omitted).