I revisited object-orientated principles via the LinkedIn Learning course Programming Foundations: Object-orientated design. While I have previously used basic OO features of Javascript, most teams I’ve been on favoured a functional style. The course also suggests a formalised, holistic approach to technical planning that is easy to follow and I am interested to try out. I do have concerns that it does not mention product engineering responsibilities, for example asking which bet are we testing with this work and what is the cheapest way to test that bet. The risk being otherwise we may plan an impressive, but possibly pointless object-orientated system if it is not something that users want.
An object is something with properties or attributes that describe its current state. E.g. a mug can be half full of water. It is independent of other instances of the object, e.g. if one mug is half full it does not mean a similar empty mug is also half full of water.
Test to decide if something should be an object:
Classes are code templates that are used to create specific objects. A real world example could be a cookie-cutter:
A class consists of:
Class behaviours are expressed in methods, which are functions that have access to the attributes of the object instance they are called on.
There are many approaches to building software, this describes one approach.
To develop any piece of software you need to do three things:
Analysis and design take place together ahead of programming. These can consist of conceptual diagrams and written descriptions, but not code. Take a 5-step approach:
UML is a standard notation for visualising object-orientated systems, which can be useful when needing to communicate things that are not clear in the technical design. Be wary of over-using or making complicated UML diagrams - they should be a quick communication tool. There are many elements of UML, including structural and behavioural diagrams, here is its approach to a class diagram:
Image credit: LinkedIn Learning
This allows sketching out a class in a format that will work regardless of programming language choice.
Further reading:
FURPS can be used to classify and think of requirements:
FURPS+ extends this with
Further reading:
Places focus on the user and what they want to achieve. They can be written in different ways, but need to contain:
Further reading:
Before writing use cases, it can be useful to brainstorm possible actors - anything that lives outside of your application but interacts with it to accomplish some goal. Sometimes this is straightforward (video game with one actor, the player) and sometimes complex (space microwave with multiple people with different goals). In addition to humans, think about other computer systems that your system needs to interact with.
When thinking about actors, it is their goal in relation to the system that is important. For example you may identify multiple job roles as actors:
Image credit: LinkedIn Learning
But as the job roles share two distinct goals, we can simplify our actors accordingly:
Image credit: LinkedIn Learning
Use cases have primary and secondary actors, primary being the ones that initiated it (but not necessarily the most important in the scenario).
Describe a goal that an actor can accomplish in a single encounter via several steps. Focus on what the user really wants to accomplish.
e.g
Identify primary scenarios first, you can eventually move to unhappy paths or extensions but be wary of going over-the-top. This can include what happens when the system fails, or how the system is monitored, which likely involve different actors.
A simple overview of the relationships between multiple use cases and actors. Useful to communicate to non-technical users and understand if something is missing.
Another written format for describing parts of an application - shorter and simpler than a use case. Describes a single small scenario from a user’s perspective, focussing on their goal and why - rather than focussing on the system. Placeholder for a conversation, whereas use case is a record of conversation - different software development methodologies favour one over the other.
Further reading:
Now that we have performed analysis and understand the problem we are trying to solve we can move to the design of the solution. First step is defining the conceptual model, which is just a fancy phrase for identifying the most important objects in the application and the relationship between them.
To identify objects, read through your use cases and underline the nouns, e.g. “The system spawns enemy spaceship in play area. Spaceship flies towards player asteroid and fires missile at it. Player steers asteroid in direction to avoid missile path. Missile flies past player asteroid and disappears offscreen.” Filtering out duplicates and irrelevant nouns you can a list of potential objects / beginning of conceptual model:
Draw out all your identified objects and draw any relationships between them. E.g. Asteroid exists within an Area, so draw a line between them. You can add text above the line explaining the relationship, e.g. Area “contains” Asteroid. You can add UML multiplicity notations, e.g. a 1 by Area (there is only one Area) and 1* by the Asteroid (an Area contains many Asteroids). Only add this if its interesting or important enough to include.
Image credit: LinkedIn Learning
To identify responsibilities, repeat the task of underlining words from your use cases, this time looking for verbs and verb phrases. E.g. “The system spawns enemy spaceship in play area. Spaceship flies towards player asteroid and fires missile at it. Player steers asteroid in direction to avoid missile path. Missile flies past player asteroid and disappears offscreen.” Filtering out duplicates and irrelevant verbs you can a list of potential responsibilities:
The next step is deciding which objects should have these responsibilities. Use cases are written from the perspective of the object that initiates the responsibility/action, but it shouldn’t necessarily be the owner of it. For example, Player steers Asteroid - Player initiates the action, but an object should be responsible for itself. Therefore, the Asteroid should be responsible for steering itself (changing its inner state), but should allow a method for the Player to tell the Asteroid where it should go. The responsibilities from the use cases will likely need to be renamed and shortened when defined as class responsibilities. Both these steps can be done by drawing out the objects and trying to assign your potential responsibilities to them:
In this example:
Note Player has no responsibilities, but will likely initiate many of the actions. Avoid assigning too many responsibilities to one object (known as a God Object), which will make maintaining and updating the application in the future easier. Sometimes this happens by creating a System object e.g. “system spawns spaceship”, where this should really be “some part of the system spawns spaceship”
CRC stands for Class, Responsibility, Collaboration. The same information as a Conceptual Object Diagram, in a different format; drawn on index cards and are simple, easy to create, hand around and discuss. Each card represents one class:
Class name | |
---|---|
Responsibilities | Collaborators |
For example
Missile | |
---|---|
Fly through space | Spaceship |
Destroy asteroid | Area |
Disappear offscreen | Asteroids |
The responsibilities don’t have to be the official method names, just what describes the behaviour. Collaborators don’t have to directly relate to the responsibility that is on the same line, its just a list of other objects that interact with it, e.g. Missile is fired by Spaceship, it exists in Area and destroys Asteroids”. The idea of using physical index cards is that if you run out of room for responsibilities then you probably need to break that object down to distribute the responsibilities.
Now you have a conceptual model, it’s possible to design the classes. This is where specific object-orientated principles like inheritance and polymorphism come into play. The most common format is UML, which can be used to detail basic information or be very complex. Here we are using just the basics
Class Name |
---|
Attributes |
Behaviours |
For example:
Spaceship |
---|
- callSign: String = “Excelsior” - shieldActive: Boolean - shieldStrength: Integer - position: Coordinate + getShieldStrength(): Integer |
+ reduceShield(Integer) + getPosition(): Coordin + move(Direction) - setPosition(Coordinate) |
For attributes, the text after the colon indicates their type. You don’t need to define the types specific to your language implementation, e.g. just specify String rather than String32. The text after the equals sign indicates a default value.
For behaviours use short verb phrases. It’s common practice to name methods that retrieve and modify attributes as “get” and “set” methods - some languages will automatically create getter and setter methods for you. Parameter types are specified in the parenthesis and return types are specified after the colon.
Plus and minus signs at start indicate public (+) and private (-). Classes may have a lot of internal complexity, but the focus is on public visibility - what are the attributes and responsibilities other classes need to know about? Leave as many things private as possible, in line with principle of encapsulation. E.g. attribute position is kept private so other classes can’t modify it directly, but its value is exposed by getPosition.
The idea behind drawing these diagrams before jumping into code is to focus on the behaviours, as when coding we often start with the attributes, which can end up with classes with few or no behaviours.
After this point we are ready to start implementing the classes in code.