Professional Documents
Culture Documents
Mellor A Java Oop Done Right Create Object Oriented Code You
Mellor A Java Oop Done Right Create Object Oriented Code You
Alan Mellor
This book is for sale at http://leanpub.com/javaoopdoneright
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader feedback, pivot until you
have the right book and build traction once you do.
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Clean Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Good Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Design methods around behaviours, not data . . . . . . . . . . . . . . . . . . 12
Hidden data - No getters, no setters . . . . . . . . . . . . . . . . . . . . . . . . 13
Collaboration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Basic Mechanics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Example: Simple Point of Sale . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
What is refactoring? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Rename Method, Rename Variable . . . . . . . . . . . . . . . . . . . . . . . . 87
Extract Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Change Method Signature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Extract Parameter Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Can we refactor anything into anything else? . . . . . . . . . . . . . . . . . 95
CONTENTS
Hexagonal Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
The problems of external systems . . . . . . . . . . . . . . . . . . . . . . . . . 96
The Test Pyramid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Removing external systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
The Hexagonal Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Inversion / Injection: Two sides of the same coin . . . . . . . . . . . . . . . . 103
Programming is all about explaining to another human what the computer happens
to be doing at any given moment. As a by-product, it also tells the computer what to
do.
The reason this is so important is that what the computer actually runs is almost
incomprehensible to anyone.
As an example, if I write this:
1 clearScreen();
you can understand that I want the computer to clear everything off the screen,
leaving it blank.
The computer can’t though. It’s as dumb as rocks. Literally. Silicon chips are made
of Silicon Dioxide - Sand.
We need to pass this code through some kind of translator to turn this into the
language of the computer. One that looks nothing like English.
One of my old computers would need something like this:
Optimise for Clarity 2
Where the assembly language on the left was still a human-readable form. The binary
on the right was what that computer ran on its Z80A microprocessor.
I know which one I like better.
The art of programming is making programs clear. Readable. Easy to skim read. No
tricks.
It is the difference between having to read all that binary and work out that it meant
‘clear the screen’, compared to reading ‘clearScreen()’.
In a working professional team, nobody has the time, money, or desire to do that.
Ever.
This book is all about helping you create code that both you and your colleagues can
understand.
What is an object, anyway?
The best way to think about objects is as people at work. Each one has a specific
job to do. They know how to do their job. They have all the knowledge, tools and
supplies they need to do it.
Think about how a handwritten letter gets sent through the post.
We have a writer. They choose what words go in that letter, then write them down.
This letter is then handed to a postman. The postman drives the letter to a sorting
office.
The sorting office has inside it a big map of postcodes. On that map, each postcode
has a little pin with the another postman’s name, who is responsible for delivering
the letters.
The sorting office hands over the letter to the correct postman, who delivers it to a
reader.
So far, so dull. Just an everyday story about an outdated way of sending mail. But it
contains the two key insights into Object Oriented Programming.
Notice how the writer asks the postman to deliver the letter. The writer does not tell
the postman how to deliver the letter. That’s not the writer’s job. The writer writes.
The postman delivers. They each do their own job without interference from the
other.
The writer also doesn’t ask the postman anything at all about how the delivery will
be done. The postman is free to do that however they see fit. They can even change
how they do the delivery later. The writer will not be affected at all.
This is the essence of OOP. Instead of people, we have objects. Each object knows
how to do its own job.
You’ll notice that this description is quite independent of programming languages.
OOP itself is a design approach that can be done in any language - even assembler
or C.
What is an object, anyway? 4
Languages like Java are called OOP languages because they provide direct support
for writing code that reads like this.
The first design decision is to represent each user as an object. And to do that, we’ll
need to code up a ‘blueprint’ for what all user objects have in common. A Java ‘User’
class:
Before we can do anything with them, we need to create a simple test application.
We’ll use the standard bit of Java boilerplate code to create an Application class with
a ‘static main()’ - That’s a ‘magic method’ that tells the operating system where to
start our Java program.
Running this program shows us ‘Hello, Jake’ and ‘Hello, Katy’. It does this by creating
two, separate user objects and asking them to greet the user they represent.
This code might look underwhelming, but it contains the most important basics of
OOP. Let’s go through the key pieces.
What is an object, anyway? 7
the calling code. This is important. This stops changes from ‘rippling out’ through
the system. That makes the code easier to understand and safer to change.
This is also why the calling code looks so simple. It is not concerned with deep
technical details. It just asks for what it needs doing - user.greet(). It leaves it up
to the object to get the work done.
This is called the Tell Don’t Ask principle
We tell the object what we would like it to do for us. We don’t ask it for any of its
secrets and try to do its job ourselves in the calling code. It is a huge advantage in
simplifying our code.
Our object also has the other secret - data.
For our greet() method to work, it will need to know the user’s name.
We decided upfront that each User object will represent one individual user in the
real world. That user has a name. So our user objects make the perfect place to store
their name. We do this in the private field called ‘name’ (see 3)
Having just one String field can be confusing to OOP beginners. How do you store
the names of all the users without an array or something?
Non-OOP code might well have an array or dictionary to store all the names of the
users. OOP code splits this problem up a different way. As we have one object per
user and only store one name for each user, our object only needs to store one name.
Instead of one array with many names, we have many objects each with only one
name. It’s less to think about and a lot more obvious. Where can we find a user’s
name? In their object.
This idea of self-contained objects makes Object Oriented programs simpler to
understand.
The big idea of a constructor is that it creates an object, loads it up and makes it ready
to use.
For our User objects, the private ‘name’ field (see 3) is set inside the constructor. We
pass each individual name into the constructor as a parameter.
Constructors help keep our private data secret. They provide a way for the calling
code to set up an object without knowing anything about its secrets.
To write a program that knows how to greet a user, all we need to know - and call -
are the two public pieces. We call the constructor to create the object and get it ready
for use. Then we call greet(). It really couldn’t be any easier.
Now, imagine how this helps us as we grow bigger programs. We might have
hundreds or thousands of classes. Maybe tens of millions of lines of code. But with
OOP, we only need to understand what our object’s public interface can do. It
insulates us from the details.
This is the key of OOP done right: the calling code is simple
And if it isn’t … we’re doing it wrong.
The first question is always ‘what does this object need to do?’. We represent that as
a method, using the name to describe the behaviour.
Then, we can add supporting code inside that method to make it work. We might add
calls to private methods to break the work into small chunks. We might need stored
data as fields in the object. We would need a constructor in that case.
Clean Code
Whilst small, the code in our previous example was also ‘clean’. Clean Code simply
means code that is easy to read, easy to work with and safe. Safe here means ‘no
gotchas’ about using the code.
This means happier days, faster development, fewer bugs. If ever there was a secret
sauce to development, then clean code is it.
There are several techniques used here that we should always use in code.
Good Names
Names are absolutely critical in software. Let me give you an example. What does
the class below do:
1 class Z2 {
2 private String aa;
3
4 Z2(String aaa) {
5 aa = aaa;
6 }
7
8 void q() {
9 System.out.println("Hello, " + aa)
10 }
11 }
Z2 class. It has a string called aa which gets set, and we can call the q method, which
… hang on … says “Hello” … is this like a greeting or something?”
The only text here that gave us a clue was “Hello”. In our better named User class,
we had the words user, greet, name and hello which helped us understand what that
class was about. We didn’t need to understand every detail of the code.
Choosing intention revealing names massively speeds up your team’s ability to read
your code. Not a little bit. Massively. And it helps you - when you come back in six
months to code you’ve long forgotten about.
is ‘what behaviours should the class have?’. Answer that before deciding what data
those behaviours might need.
In the past, I have viewed an object as being a stucture of data with some functions
wrapped around that. I have learned that designing behaviours first, without any
knowledge of how they will work internally, leads to more stable OO designs. I
recommend you do the same.
Designing around behaviours is key to doing OOP right.
This is an Aggregate class. It is called that because it aggregates more than one User
object into a single Users object.
The nice thing about it is that it follows the same principles as before:
Aggregates: More than one 15
Internally, it uses some features found in Java 8 and above, which you will be using
by now.
I’ve always thought that this is just too low-level for me. I used to program in C and
assembler language, where this is your only choice. It is so much better to have the
collection classes available.
Here is the problem with low-level array use: You have to know in advance how
many things you want to store; ten in the example above.
With an array, adding them to the end of the list is awkward. You have to track the
last index you wrote to, then write to the next one, using the users[currentIndex]
syntax.
Once you reach the limit of the array size, because it cannot grow, you have to create
a new array, copy over all the contents, then copy over the ‘last written to index’.
It’s almost as if there should be an object to encapsulate all of that work for you, isn’t
it?
And, of course, that’s exactly what the ArrayList class does.
So use it. Do not write your own.
One reason people complain that Java is ‘verbose’ is when they see novice code
littered with raw array management - when the collections should have been used.
Is our Users class verbose? Really? I don’t think it is.
In our case, an internal iterator loops over the list of User objects. For each user object,
it will call the greet() method on that object.
We made that happen by the odd syntax ‘User::greet’, called a ‘method reference’.
This is a Java 8 shorthand for ‘use the greet() method on the User class for each
object’.
There are four different kinds of method references you can use. All are useful.
The key point is how direct the code reads. ‘For each of our users, call greet()’. It is
simple, clear and concise.
What I really like is how this compares to using an index based loop over a raw array.
Let’s compare that:
In our example, the greet() method in the aggregate Users will greet all the Users.
The greet() method in individual class User only greets that user.
Common themes
Some common ideas for aggregate methods are:
Basic Mechanics
Java gives us two basic ways to get objects to work together. We can pass an object
as a parameter, or we can have an object as a field.
Let’s see them both in code, so we know what they look like. We’ll use two classes,
Dog and Cat. The Dog wants to chase the Cat. Perhaps not ‘collaboration’ between
the two pets, but let’s see how we could do it using objects.
We’ll make class Cat have a chase() method:
1 class Cat {
2 public void chase() {
3 System.out.println("I'm off up that tree");
4 }
5 }
The first way to give the Dog class access to a cat object is to hold it in a private field:
Collaboration 20
1 class Dog {
2 private final Cat cat ;
3
4 Dog( final Cat c ) {
5 this.cat = c;
6 }
7
8 public void chase() {
9 cat.chase();
10 }
11 }
Dog objects then have a reference to a Cat object, which they can then use in any of
the Dog methods.
Another way is to pass the Cat object as a method parameter:
1 class Dog {
2 public void chase( Cat c ) {
3 c.chase();
4 }
5 }
This allows the chase method on a Dog object to access the Cat object.
The fact that we can call methods on the object passed in confused me the first time
I saw it. I had only ever seen primitive values like numbers and strings passed into
methods before. You just consume them. But when you pass an object in, you can
call any available method on that object.
It’s important to note that both objects - Dog and Cat, here - have the same equal
standing. Cat is not an “output parameter”, as you might see in other languages. It is
a fully-fledged object, with ‘equal rights’ as Dog. See this method signature as a true
collaboration between equals.
Collaboration 21
• If you need the same collaborator throughout the lifetime of the using object,
choose field
• If you need different collaborators on each call, choose parameter
The idea here is that by passing in a Cat object as a parameter, you could - if needed
- pass in a different Cat object on each call to Dog.chase(). Using a parameter tells
the reader of your code that this is what you wanted to allow. This is short-term
collaboration.
Using the field approach tells your readers that you are fixing on a single Cat for that
Dog object’s entire lifetime. This is a long-term collaboration.
Behaviours first
Collaboration 22
1 class Item {
2 public void print() {
3 // todo
4 }
5 }
First things first, we’ll start with an Item that can print itself.
That’s already making me think about the design. What should print actually do?
We’ve gone for a void/void signature for now, as we’re exploring the solution. But
it’s going to need access to something that can print in some way - unless it can do
the job without help.
The way to decide is to have a clear vision of what we decide each object should be
doing. That’s our decision as software engineers.
Later chapters covering Test Driven Development and the SOLID principles will
make this decision easier to make. But for now, let’s decide that Item only knows
what should be printed on the receipt and not how to print it.
This means that we need another object that does know how to print things in a bit
more detail. Let’s design the behaviours on that:
1 class Printer {
2 public void print( String text ) {
3 // todo
4 }
5 }
For now, let’s assume all we need is the ability to print a String value. We can leave
the implementation as ‘todo’ and come back to this later.
We can give our Item a way to print itself now:
Collaboration 23
1 class Item {
2 public void print( Printer p ) {
3 // todo
4 }
5 }
That’s progress.
We are expressing the high level design and structure of our code to our readers. We
are telling them that we have an Item object that can print itself. To do that, it will
collaborate with a Printer object.
I want to stop at this point and underline the thinking here. All we have to show for
our work is two classes of five lines each, where every method says ‘todo’. I’m not
expecting much bonus at this year’s Annual Pay Review so far.
But this is the nature of OOP done right. It is why it is brilliant. We are designing the
behaviours of the system first as abstractions. Abstraction means ‘the general idea’
or ‘the thing that is always true, however you do it’.
We avoid getting bogged down in details of coding. Instead, we spend the time
expressing what our system does, where each big idea splits apart and not how it
is done. The how is, of course, very important. It is just less important than the
structure of a program. Adding the ‘how’ can be done later.
Anyway, tea break over: back to the coding. We have only one Item on our receipt.
Let’s create an aggregate class that represents our full receipt:
1 class Receipt {
2 public void print () {
3 // todo
4 }
5 }
1 class Receipt {
2 private final Printer printer ;
3
4 public Receipt( Printer p ) {
5 this.printer = p;
6 }
7
8 public void print () {
9 // todo - using field 'printer'
10 }
11 }
1 class Receipt {
2 private final Printer printer ;
3
4 public Receipt( Printer p ) {
5 this.printer = p;
6 }
7
8 public void add(String description, Money price) {
9 // todo
10 }
11
12 public void print () {
13 // todo - using field 'printer'
14 }
15 }
1 class Money {
2 // TODO
3 }
We’ll create a list of Item objects inside our receipt and add to them using add():
Collaboration 26
1 class Receipt {
2 private final Printer printer ;
3 private final List<Item> items = new ArrayList<>();
4
5 public Receipt( Printer p ) {
6 this.printer = p;
7 }
8
9 public void add(String description, Money price) {
10 items.add( new Item(description, price) );
11 }
12
13 public void print () {
14 // todo - using field 'printer'
15 }
16 }
There’s another design decision here. When we create an Item object, it now needs
a constructor. We’ll pass description and price into that, giving our Item object
knowledge of what it needs to print:
1 class Item {
2 private final String description;
3 private final Money price ;
4
5 public Item( String description, Money price ) {
6 this.description = description;
7 this.price = price;
8 }
9
10 public void print( Printer p ) {
11 // todo
12 }
13 }
After adding the constructor, I added the fields to hold the knowledge of description
and price. I marked them as final to make this an ‘immutable value’ object. Immutable
Collaboration 27
means the value will not change. That makes systems easier to reason about - and
safer to use in concurrent environments like web servers.
Working backwards like this, you give your IDE like IntelliJ all the clues it needs to
offer to write the code.
From typing ‘new Item(description, price)’ in the Receipt class, it will offer to create
a constructor in Item with the correct parameter names and types. From here, the
IDE will offer to add the correct fields with correct names and types in Item.
Take full advantage of that IDE help! I always do.
We’re getting there now. The next job is to fill in some details on this printing. Let’s
start outside-in, so we stick with big picture ideas first:
1 class Receipt {
2 private final Printer printer ;
3 private final List<Item> items = new ArrayList<>();
4
5 public Receipt( Printer p ) {
6 this.printer = p;
7 }
8
9 public void add(String description, Money price) {
10 items.add( new Item(description, price) );
11 }
12
13 public void print () {
14 items.forEach(item -> item.print(printer));
15 }
16 }
We’ve added a Java 8 lambda style line to print out all items. It uses the ‘Tell Don’t
Ask’ style to go through all Item objects and ask each one to print itself. We pass into
that print method the Printer object we supplied in the constructor of Receipt.
Collaboration 28
1 class Item {
2 private final String description;
3 private final Money price ;
4
5 public Item( String description, Money price ) {
6 this.description = description;
7 this.price = price;
8 }
9
10 public void print( Printer p ) {
11 p.print(description);
12 p.print(" ");
13 price.print(p);
14
15 p.newline();
16 }
Collaboration 29
17 }
The real heart of OOP collaboration design is this. So far, it’s been all plain sailing.
But concerns like this - that don’t easily map onto any existing single object - are the
ones where we have to stop and think.
There are two options at this point. We can just press on. Leave it as it is and revisit
the choice later. Or we can improve the structure now.
Collaboration 30
1 class Item {
2 private final String description;
3 private final Money price ;
4
5 public Item( String description, Money price ) {
6 this.description = description;
7 this.price = price;
8 }
9
10 public void print( Printer p ) {
11 new ItemFormat(description, price).print(p);
12 }
13 }
We’ve introduced an object responsible for formatting an Item object for print. We’ve
given it all the information it needs to do that job. As ever, we’re leaving the details
until later. The first cut of the object will be a simple refactor. We will put the insides
of the previous Item print method into the ItemFormat print method. We’ll then
make those detailed decisions about who-does-what between description, Printer and
Money later.
It’s time to build out a small test app for this. First, we need to make our Printer class
do something. We’ll add that newline method. We will also decide to use console
output:
Collaboration 31
1 class Printer {
2 public void print(String text) {
3 System.out.print(text);
4 }
5
6 public void newline() {
7 System.out.println();
8 }
9 }
Fill in that Money class to include a print() behaviour plus supporting ‘secret
knowledge’:
1 class Money {
2 private final String amount ;
3 private final String currency ;
4
5 public Money( String amount, String currency ) {
6 this.amount = amount;
7 this.currency = currency;
8 }
9
10 public void print( Printer p ) {
11 p.print(currency);
12 p.print(amount);
13 }
14 }
A real Money class in Java will store the amount as BigDecimal. This is a fixed
precision, accurate way to represent decimal fractions - pounds and pence. The float
and double types are never used as they trade size for precision. The higher the
amount in a float, the less accurate the value stored is. Very bad for banks.
Our first stab at ItemFormat is from our simple refactor:
Collaboration 32
1 class ItemFormat {
2 private final Money price ;
3 private final String description ;
4
5 public ItemFormat( String description, Money price ) {
6 this.description = description ;
7 this.price = price ;
8 }
9
10 public void print( Printer p ) {
11 p.print(description);
12 p.print(" ");
13 price.print(p);
14 }
15 }
1 class ReceiptDemo {
2 public static void main( String[] commandLineArgs ) {
3 new ReceiptDemo().run();
4 }
5
6 private void run() {
7 Receipt r = new Receipt( new Printer() );
8
9 // Nice cheese and wine evening
10 r.add("Brie", new Money("1.95", "GBP"));
11 r.add("Tiger Bread", new Money("0.95", "GBP"));
12 r.add("Merlot", new Money("7.95", "GBP"));
13
14 r.print();
15 }
16 }
1 Brie GBP1.95
2 Tiger Bread GBP0.95
3 Merlot GBP7.95
• As a customer
• I want to see the total price
• So that I know what I need to pay
If our design is hard to set up or hard to use, we’ll see that in hard to read test code.
Java projects use the wonderful JUnit test framework to help out with this. I like to
add AssertJ to help with the checking part.
Let’s use TDD to build a little calculator that can add up our restaurant bill. As you
follow along, notice the rhythm of TDD - write a bit of test, see a failure, fix it with
a bit of code, repeat.
Our calculator class should do two things:
We start by writing an empty test. We want this test to prove something that will be
true of our BillCalculator class as soon as we create it. If we create this object and
don’t add any items to it, the total must be zero.
Let’s start to write a test for that.
We’ve got a test class and a test harness method all named for what they do.
Test Driven Development 37
Now we do a tiny step of design for our object. We want to be able to access the
latest running total. For this, despite all my bleating in previous chapters, a getTotal()
method seems like the simplest, most clear design.
1 class BillCalculatorTest {
2 @Test
3 public void totalStartsAtZero() {
4 // Act
5 float total = calculator.getTotal();
6 }
7 }
The compiler will be loudly complaining at this point, as indeed might you if this is
your first TDD session: “There isn’t any object yet!”
Quite right. Let’s fix that.
Using your IDE shortcuts, create a new Class called BillCalculator:
1 class BillCalculator {
2 }
Now, we can fix the first of our compiler’s many complaints and create a calculator
object:
Test Driven Development 38
1 class BillCalculatorTest {
2 @Test
3 public void totalStartsAtZero() {
4 // Arrange
5 var calculator = new BillCalculator();
6
7 // Act
8 float total = calculator.getTotal();
9 }
10 }
I’m using the ‘var’ keyword from recent Java editions (don’t ask me which one; I
never was a language lawyer kind of guy). I think this reads very clearly here.
The compiler will grudgingly accept we are now not complete idiots and remove the
red X from under ‘calculator’. It will then move on to its next complaint. We need to
add the getTotal() method.
Let the IDE shortcuts do this, because the IDE has all the information it needs to get
it right:
1 class BillCalculator {
2 public float getTotal() {
3 return 0.0;
4 }
5 }
I love working backwards like this. The IDE has enough information to do most of
the grunt work and not make as many typing mistakes as I do. It’s also a very fast
way to work once you get into the rhythm of it. You even look (to the clueless) like
a 10x mega ninja rock star programmer.
Anyway. The compiler will be happy, allowing us to add our check that everything
is ok. We’ll use the wonderful ‘AssertJ’¹ library to provide the ‘assertThat’ method.
¹https://assertj.github.io/doc/
Test Driven Development 39
1 class BillCalculatorTest {
2 @Test
3 public void totalStartsAtZero() {
4 // Arrange
5 var calculator = new BillCalculator();
6
7 // Act
8 float total = calculator.getTotal();
9
10 // Assert
11 assertThat(total).isZero();
12 }
13 }
We can run this test - and it will pass! Our calculator object has passed its first and
only test. It just so happens that the only thing we are testing is that the total is zero
and the IDE generated code always returns zero.
1 class BillCalculatorTest {
2 @Test
3 public void correctTotalForOneItem() {
4 // Arrange
5 var calculator = new BillCalculator();
6
7 // Act
8 calculator.add(12.95);
9 float total = calculator.getTotal();
10
11 // Assert
12 assertThat(total).isEqualTo(12.95);
13 }
14 }
1 class BillCalculator {
2 private float total ;
3
4 public void add(float itemPrice) {
5 total = itemPrice; // 1. We need to talk about this...
6 }
7
8 public float getTotal() {
9 return total;
10 }
11 }
I did strict TDD above, to show how it’s done. I made the test pass by writing the
simplest code that came to mind to do it. The normal trick is to then tidy up anything
we don’t like in the refactor step.
But this is something different. It’s about how much progress to do in one step.
The implementation at (1) assigns the price to the total. It overwrites the existing
total, rather than adding it up.
This means that the zero case will pass, and a single item case will pass. Adding a
second item will fail.
Strict TDD always does the bare minimum at each step. This is called the ‘YAGNI’
principle.
But just like we have the Yin and the Yang, we have the Yagni and the Yaya. Two
opposing forces in development. Do we keep things bare minimum all the way, just
to avoid over-complicating things? Or, given we know what’s coming next, is this
over-complicating things?
You have to decide on a case by case basis.
But one thing is for sure. Our next test is going to flush out that assignment to total
and replace it with an addition:
1 class BillTest {
2 @Test
3 public void correctTotalForTwoItems() {
4 // Arrange
5 var bill = new Bill();
6
7 // Act
8 bill.add(12.95);
9 bill.add(2.05)
10 float total = bill.getTotal();
11
12 // Assert
13 assertThat(total).isEqualTo(12.95 + 2.05)
14 }
15 }
and we make the single-character change to our production code to make it pass:
Test Driven Development 46
1 class Bill {
2 private float total ;
3
4 public void add(float itemPrice) {
5 total += itemPrice;
6 }
7
8 public float getTotal() {
9 return total;
10 }
11 }
You can see where the assignment has been changed to addition.
I’ll leave it to you to decide if we could have done that in one step instead of two. Or
if it even really matters.
1 class BillTest {
2 @Test
3 public void totalAmountIsZero() {
4 // Arrange, Act, Assert goes here
5 }
6 }
Test Driven Development 47
My advice is to drop noise words from the start of the phrase. It’s a test case, so we
don’t need to say things like ‘shouldCalculateTotalAsZero’, ‘returnsTotalAsZero’ nor
‘checkThatTotalIsZero’.
We definitely do not need to write clunky test names like
1 @Test
2 public void BillTest_calculateTotal_isZero() {
3 // JUST SAY NO!!!!!
4 }
Why?
For me, this goes back to the whole idea of methods being little abstractions that
make our code less complicated.
We’re not describing how the test is built. We are not even describing how the method
we’re testing is implemented. We are describing what we want the method we are
testing to actually do.
We should use concise, outcome-driven test names. A reader should be able to skim
read what the test expects the production code to do. This improves readability. It
simplifies refactoring the production code internals later.
Being agile means that we can change our mind about the secrets our objects hide
from callers. We reflect this same thinking in our test names.
We’ve looked at how YAGNI (and its corner-cutting cousin YAYA) help us prevent
over-complicating code. We’ve seen how we can change our mind about the code,
taking the trouble to improve it as we go. We’ve also seen how TDD helps us grow
out a ‘right-sized’ design, one test at a time.
These ideas are at the heart of Extreme Programming (XP). This ‘shifts left’ (i.e.
reveals earlier) any problems with logic, design, and code clarity.
Real-world TDD
Before moving on, it’s worth sharing some experience with TDD in real projects. As
you would expect, everything is not quite as rosy as I’ve just painted it.
Test Driven Development 49
The Good
I’m a big fan of TDD. It really speeds up development time (yes, really). The
development cycle is way quicker than spinning up three services, a Dockerised
database, Node plus a React UI and then clicking through five screens to test that
your Bill can add up two items.
The regression testing you get on each code change is worth actual money, in terms
of decreased spend on bug fixing and customer reputation.
TDD gives me a lot of confidence in my work. It lets me split things up into small
chunks that my limited brainpower can cope with. I used to write hundreds of lines
at a time before running code. I have literally zero clue how I did that now.
The Bad
Some teams dislike TDD and refuse to use it.
TDD only works with clean designs that are split up to allow the tests access to the
code. Legacy code often cannot use TDD. You cannot get the access you need in the
giant ball of spaghetti that passes for code.
I’ve had somebody jealous of my use of TDD, who made life hard for me in a
team. They seemed to feel it somehow showed them to be inferior and that this
was something I intended. It’s best to get everyone on the same page.
TDD doesn’t work at all when you cannot decide upfront what a method should do.
Whilst that sounds obvious, a lot of real-world development is experimental.
You’re tinkering around trying to figure out what code might work. You might be
wiring up libraries you don’t understand for a feature that isn’t fully specified.
The solution here is to do a spike. Forget tests: Hack. Make notes on the things that
worked and the rough shape of the code that did it. Revert the spike then use TDD
to follow your notes and engineer it properly.
The Ugly
TDD can create a truly colossal mess in the wrong hands. Think of toddler-with-
Sharpie kinds of mess.
Test Driven Development 50
The problem is that TDD does not - and cannot - design your code for you. Which
is why I put it after OOP ideas in this book. It just can’t.
Instead, TDD gives you design feedback. Here are the three classic test code smells:
• Messy Arrange Step: Your object is over coupled to other objects. Split it.
• Messy Act Step: Your object usage pattern is too complex: Simplify it.
• Messy Assert Step: The results are hard to use. Rethink it.
What often happens is that these test smells get ignored. Time pressure, lack of
leadership, or perhaps lack of knowledge all lead to this.
Whatever. The hapless coder copies and pastes like it were Groupon vouchers for free
pizza and digs a deeper hole. The tests then actually lock-in the bad design. It gets
hard to change - as all these bad tests get in the way!
A very similar thing happens when the tests are too tightly coupled to the object’s
inner secrets. We learned to avoid this by naming tests well and letting that guide
our thinking.
But if we don’t, our tests prevent our objects from changing. The tests force our future
code to be a kludgey series of workarounds.
That isn’t the fault of TDD. It’s the fault of not doing TDD right.
But be aware of it, because it happens in the wild.
Polymorphism - The Jewel in the
OOP Crown
By far, the most useful tool in Object Oriented Programming is polymorphism. It is
the key to everything that follows.
It is all about making objects that can be swapped.
Polymorphism gives us a nice alternative to conditional logic. It allows us to split up
our program in really interesting ways. We can test code in isolation, decouple code
from external systems and reduce if / switch statements.
There are a couple of ways of using this technique in Java code. Let’s look into the
clearest approach with an example: Interfaces.
1 interface Shape {
2 void draw();
3 }
This is a class that knows how to draw a circle. Just for this example, that means it
writes some text to the console.
This snippet makes a list of objects that implement Shape. It adds instances of Circle
and Square to that list. It uses the Java 8 forEach method to go through each object
in the list and call the draw() method on it.
Because draw() is polymorphic, the Java runtime routes the call to the code in the
implementing class. Even though the calling code appears to call the same draw()
method each time, it doesn’t. The runtime routes it down to whatever actual class it
finds.
The output looks like
Let’s talk about that magic I mentioned. You’ll notice a couple of brilliant things
about this.
Polymorphism - The Jewel in the OOP Crown 54
• No if or switch statements
• No instanceof statements (ugh! Just, no)
• The loop code does not know what kinds of shapes are in the list
• We can add new shapes without affecting any others
You can see how code like that grows and grows as new shapes get added. The code
itself is complex, as all conditionals are. They have extra possible execution paths
through the code. Each one needs testing. Each one increases the chance of coding
mistakes.
The polymorphic approach completely separates those pieces of code. The Circle
class has no connection at all to the Square class. That would not be true in our
‘giant switch statement’ case. So changes to one do not affect the other.
The other result that follows is that adding a new shape is easy.
Polymorphism - The Jewel in the OOP Crown 55
The draw loop code is completely unchanged - but can now draw triangles
To add in our triangle, we only have to make an addition to our object creation code:
This is an example of the Open/Closed Principle (OCP) which we will cover in the
chapter on SOLID. It is the “O” of the acronym. It is important because it means we
can keep code open for adding new features to - but closed against changing existing
code that we know works.
The SOLID Principles
As object oriented programs grow large in size, it gets more essential to split that
system apart into little pieces.
But what does ‘split apart’ mean? And what is a ‘little piece’, exactly?
The five design principles known as SOLID came about as solutions to this. They
have some pretty indecipherable names but are easy enough - once you understand
what problems they solve.
There’s nothing remarkable about it, at first sight. We see the three sections to fetch
input, convert, display.
But notice that new keyword. That’s a problem.
Because of this new, the method has a direct dependency on how the keyboard is
read and how the output is displayed.
It also calls System.out.println() for output, which has the same problem. We are
tightly bound to a global method. But let’s just consider input for now.
that. But the function does not need to know anything at all about how that input is
obtained.
This is where TDD really forces a good decision early.
How could we write a unit test for our function? Spoiler: We couldn’t.
We would have to admit defeat. We don’t have a way for the test to make keyboard
input at this level. We are locked-in to using a Scanner object.
But let’s re-phrase that question and not give up: How could we write a unit test?
What would we have to change?
1 interface Input {
2 String fetch();
3 }
This is an abstraction. It says ‘you can ask me for an input string, but leave it to me
to figure out where it will come from’.
It replaces the two lines of code relating to the scanner in our example code.
The SOLID Principles 61
I’ve also removed the comment. The interface makes it clear enough without.
As it stands, this would not compile.
We need to make two more changes:
We’ve copied-and-pasted the Scanner code into a new class that implements the
interface. It is called ‘KeyboardInput’ because this class is allowed to know exactly
where the input comes from.
The SOLID Principles 62
1 class TextConversion {
2 private Input input ;
3
4 // This is where we inject the dependency
5 public TextConversion(final Input input) {
6 this.input = input;
7 }
8
9 public void showInputInUpperCase() {
10 String inputText = input.fetch();
11
12 // Convert
13 String upperCaseText = inputText.toUpperCase();
14
15 // Display
16 System.out.println(upperCaseText);
17 }
18 }
Running this, we get our original behaviour. This has been a refactoring exercise, not
a feature change.
Can you see how we have externalised the details of keyboard input from the actual
text conversion?
You can see nothing has changed except for the class name we create with new.
The SOLID Principles 64
1 interface Output {
2 void display( String toDisplay );
3 }
1 class TextConversion {
2 private Input input ;
3 private Output output ;
4
5 public TextConversion(final Input input, final Output output){
6 this.input = input;
7 this.output = output;
8 }
9
10 public void showInputInUpperCase() {
11 String inputText = input.fetch();
12 String upperCaseText = inputText.toUpperCase();
13 output.display(upperCaseText);
14 }
15 }
The SOLID Principles 65
That allows us to swap to any input or display source. We only change the objects
that get created with new. Nothing else.
And that helps us with our unit testing. We can swap the input for something we
can control and swap the output for something that captures anything sent to it.
We’ll see how this works in the chapter on ‘TDD and Test Doubles’
Substitutability
Intuitively - No. Of course it won’t work as expected. CardDealer does not fit in with
our expectation of what Shape.draw() should do.
The whole setup of Shape.draw() and Circle and so on suggests that we expect our
classes to draw geometric shapes. Not draw cards from a deck.
This was the insight Barbara Liskov had. She went on to formalise this intuition
mathematically. It is that formalism which is known as the Liskov Substitution
Principle (LSP).
LSP reminds us that classes implementing interfaces are only useful if they can be
substituted for that interface in all circumstances.
Specifically, a class meeting this must:
In short, you can swap out any of the implementing classes without causing the code
to break.
Liskov gave a rigorous definition as:
The SOLID Principles 68
Practically, I always end up asking that simple question: “Are there any cases where
this class would break the interface contract?”
LSP also applies to inheritance in general - classes and subclasses, abstract classes
and so on. It is not restricted to interfaces and implementations.
The only reason I describe it in those terms is that I find interface/implementation to
be the simplest, safest usage of inheritance in OOP. It is ‘the happy path’.
1 class Shapes {
2 private final List<Shape> shapes = new ArrayList<>();
3
4 public void add (Shape s) {
5 shapes.add(s);
6 }
7
8 public void draw() {
9 shapes.forEach(Shape::draw);
10 }
11 }
This aggregate class allows you to add shapes to an inner list then draw them.
The SOLID Principles 69
Where OCP comes in is that you can add any LSP compatible Shape implementation
using the add(shape) method. You can then change the behaviour of Shapes.draw()
without changing anything inside that class. The change of behaviour is done outside
of the Shapes class, not inside.
This is what is meant by Shapes is Open for extension (you make it do different
things), but closed for modification.
OCP is essentially Dependency Inversion in disguise.
1 interface Shape {
2 void draw();
3 void apply(Filter f);
4 }
5
6 class Shapes {
7 private final List<Shape> shapes = new ArrayList<>();
8
9 public void add (Shape s) {
10 shapes.add(s);
11 }
12
13 public void draw() {
14 shapes.forEach(Shape::draw);
15 }
The SOLID Principles 70
16
17 public void blur() {
18 shapes.forEach(s -> s.apply(new BlurFilter()));
19 }
20 }
1 class Shapes {
2 private final List<Shape> shapes = new ArrayList<>();
3
4 public void add (Shape s) {
5 shapes.add(s);
6 }
7
8 public void draw() {
9 shapes.forEach(Shape::draw);
The SOLID Principles 71
10 }
11
12 public void filter(Filter filter) {
13 shapes.forEach( s -> filter.applyTo(s) );
14 }
15 }
Here, the filter() method injects a class implementing the Filter interface into the
method. It then runs it across each of the shapes.
Filter is a Strategy Pattern. It defines the strategy for how we filter a Shape. It
externalises behaviour.
Our Shapes.filter() method has no knowledge of how exactly we process each Shape.
It takes what it is given from outside the class and applies it to each Shape.
In the same way that Shapes.draw() does not know how to draw any shape,
Shapes.filter() does not know how to filter any Shape. The Strategy pattern formed
by our Filter interface pushes that variable behaviour outside the class, leaving it
open for extension but closed for further modification.
Any future new processing will be done by creating a new class that implements
Filter. This will be done outside of the Shapes class and will require no changes to it.
The Strategy pattern is a powerful way of parameterising behaviour. We use the
power of polymorphism to leave our choice of behaviour open-ended.
1 interface Control {
2 void on();
3 void off();
4 void channelUp();
5 void channelDown();
6 }
We can stub that out for testing our remote control adapter and create a production
class that drives the TV hardware.
All good.
A couple of weeks later, the team tell us they have got some improved Smart TV
hardware. We will be launching two products, one low-cost, entry-level version and
our high-end Smart TV.
It features digital programme recording as well as YouTube and Netflix internet TV.
We might decide to add the extra features into our existing Control interface:
1 interface Control {
2 // entry level TV features
3 void on();
4 void off();
5 void channelUp();
6 void channelDown();
7
8 // Smart TV Only features
9 void startRecording();
10 void endRecording();
The SOLID Principles 73
11 void launchYoutube();
12 void launchNetflix();
13 }
We start coding our class to drive the TV hardware, and we want to keep the same
codebase, rather than fork it per-product.
Ah.
If we keep a single class for both entry-level and Smart TVs, we soon find a problem
with the Smart TV methods on the entry-level product: they don’t work.
Initial ideas are to add if (isSmartTV) statements everywhere needed. That itself is
an OOP code smell. We should probably replace that with polymorphism, using two
separate classes that implement that Control interface. Call them BasicControl and
SmartTvControl.
This gets rid of the if statements. More specifically, it pushes them out to the edge
of the system where we create the appropriate one of those two classes. But it still
leaves us with a problem. What should BasicControl do with all the Smart-TV only
methods?
Typically, these will either be no-operation (no code in there) or throw an Unsup-
portedOperationException.
The root cause of the problem is that we have violated ISP. We have forced the
BasicControl implementer of the interface into using a bunch of methods that make
absolutely no sense to it.
1 interface BasicFeatures {
2 void on();
3 void off();
4 void channelUp();
5 void channelDown();
6 }
7
8 interface AdvancedFeatures {
9 void startRecording();
10 void endRecording();
11 void launchYoutube();
12 void launchNetflix();
13 }
1 interface Command {
2 void execute();
3 }
You can see that a Command interface is a level of abstraction higher than we were
using. It knows nothing at all about our specific application. It only knows that any
objects that implement Command can be told to ‘execute()’ - to run their code that
makes that command happen.
So we would see classes like
We would have one class for each of our original methods in our ‘big interface’.
Then, we would map each command object against something we got from the TV
Remote control. Suppose our hardware team gave us a remote control driver that
delivered us a number between 0 and 15 inclusive to represent each command. We
would write a class that knew this mapping and could route the command number
accordingly:
The SOLID Principles 76
1 class Commands {
2 private final Map<Integer, Command> commandsByNumber =
3 new HashMap<Integer, Command>();
4
5 public initialise( boolean isSmartTv ) {
6 register(0, new On());
7 register(1, new Off());
8 register(2, new ChannelUp());
9 register(3, new ChannelDown());
10
11 if (isSmartTv) {
12 register(4, new StartRecording());
13 // ... register the others
14 }
15 }
16
17 public void execute(int commandNumber) {
18 Command c = commandsByNumber.get( commandNumber );
19
20 if ( c != null ) {
21 c.execute();
22 }
23 }
24
25 private void register( int commandNumber, final Command c ){
26 commandsByNumber.put( commandNumber, c );
27 }
28 }
Using this, we get our ‘dynamic interface’ effect. The Smart TV has more commands
available to it. We don’t have any ISP violations, because we have designed that out.
ISP is not possible here.
We initialise this class knowing what kind of TV product we have. Then we can pass
our commandNumber to execute() and have this class do the right thing, Tell-Don’t-
Ask style.
The SOLID Principles 77
1 class TextConversion {
2 private Input input ;
3 private Output output ;
4
5 public TextConversion(final Input input, final Output output){
6 this.input = input;
7 this.output = output;
8 }
9
10 public void showInputInUpperCase() {
11 String inputText = input.fetch();
12 String upperCaseText = inputText.toUpperCase();
13 output.display(upperCaseText);
14 }
15 }
A simple getter seems to do the job of exposing the captured data well here. You could
make the field public; you could provide a method ‘boolean hasExpectedValue( String
expectedValue )’ if you like. Despite my general dislike of getters in OOP, it seems
the right tool for the job here.
We can now use these test doubles to write a fully automated unit test:
1 class TextConversionTest {
2
3 @Test
4 public void displaysUpperCasedInput() {
5 // Arrange
6 var in = new StubInput("abcde123");
7 var out = new MockOutput();
8
9 var tc = new TextConversion( in, out );
10
11 // Act
12 tc.showInputInUpperCase();
13
14 // Assert
15 assertThat(out.getActual()).isEqualTo("ABCDE123");
16 }
17 }
TDD and Test Doubles 82
This test uses our stub and mock objects and follows the usual Arrange, Act, Assert
pattern. It uses the well-known JUnit test framework. The @Test annotation marks
this out as a test that JUnit can run.
Follow the code through, and you can see that we:
You can see now why I chose to pass the stub data into the StubInput class. It’s
all about locality of reference. The test method above clearly shows an input of
‘abcde123’ and a few lines later clearly shows the expected output of ‘ABCDE123’.
This is optimised for clarity. It is easier to skim read that test than it would be if you
had to click through into StubInput to see what the stubbed value was.
Mocking libraries
Now that we understand how Dependency Inversion allows us to write test doubles,
we can simplify our lives.
Writing a lot of test doubles by hand is pretty tiresome, as you can see. It is classic
boilerplate code.
As you would expect, libraries can help us.
The most popular in Java is Mockito. It is a library that allows us to generate mocks
and stubs using only annotations.
Our unit test above would become this, using Mockito:
TDD and Test Doubles 83
1 class TextConversionTest {
2 @Mock
3 private Input input ;
4
5 @Mock
6 private Output output ;
7
8 @Before
9 public void beforeEachTest() {
10 MockitoAnnotations.openMocks(this);
11 }
12
13 @Test
14 public void displayUpperCasedInput() {
15 // Arrange
16 Mockito.when(input.fetch()).thenReturn("abcde123");
17 var tc = new TextConversion(input, output);
18
19 // Act
20 tc.showInputInUpperCase();
21
22 // Assert
23 Mockito.verify(output).display("ABCDE123");
24 }
25 }
25 // Assert
26 assertThat(actualOutput).isEqualTo( "ABCDE123" );
27 }
28 }
The test class acts as its own test doubles. The fields input and output make the intent
easier to understand.
I worked on one project where this style was the only one accepted. I liked it; but
overall, it’s a bit weird. I’ve not seen it used elsewhere.
But it’s a nice tool to have in the toolbox.
Refactoring
Every technique so far has been about structuring code for the first time.
We have worked hard to make our code readable and simple. To help us do that, we
have designed and tested from the outside in, choosing to make what we are solving
more prominent than how we are solving it.
But what about when we haven’t got it right? What happens when we learn a new
insight into our problem? What about when we have thrown down ‘the simplest
possible code’ to make a test pass, but now we want clean code?
What is refactoring?
Refactoring is the name given to changing the structure of our code whilst keeping
its behaviour the same. Re-structuring our code, but without breaking anything.
The name comes from factoring, which comes from algebra. Factoring is the basic
idea of splitting something up into smaller parts. Refactoring is simply ‘doing it
again’.
I am always refactoring.
I’ll refactor as part of the TDD process, as is standard. I will write a failing test, add
the simplest production code to make it pass, then refactor.
When faced with new code that’s hard to understand, I’ll refactor it first. That way,
I capture any insights I get and preserve this hard-won understanding in the code.
That’s why refactoring is useful. But how do we do it?
Martin Fowler’s book Refactoring is the reference on this subject. The first edition
is all Java-based and the one I recommend for Java devs. The second edition uses
JavaScript ES6 with class support for the examples. It’s okay, but not as direct for us
Java developers.
Here are the refactoring steps I use all the time - and why.
Refactoring 87
This User class will print out ‘Welcome back, Alan’ after creating the object passing
in the name Alan.
The name is stored in a field called ‘text’. Hmm. It is text - but that’s not really telling
me what that field is used for. It is being used to store the User’s name that we want
to put into the welcome message.
The fix is to use Refactor > Rename of that field. Call it ‘name’.
In the same vein, method ‘printout()’ isn’t very helpful in understanding the code,
either. Yes, the method will print something out. We can read that. But that isn’t what
it is doing, that’s how. We want our methods to explain why they are there and what
their outcome is.
We can use Refactor > Rename on the method name to give it a descriptive name.
Let’s choose ‘welcome()’. This better explains what is being done.
Refactoring 88
1 class User {
2 private final String name ;
3
4 public User( String name ) {
5 this.name = name ;
6 }
7
8 public void welcome() {
9 System.out.println("Welcome back, " + name);
10 }
11 }
These two refactorings - changing the name of a field or method - are the ones I use
all the time. Literally. To the point where I no longer stress out about thinking up
‘the best’ names. I just code, come up with a first stab at a name, then come back and
refactor it when I get a better idea.
I find my brain works like that. Something happens in the background as I am
working away. I’ll get a better insight a little later.
Use the tools. It is much faster. You will introduce fewer bugs.
Extract Method
Another favourite that I actually use for several purposes:
Refactoring 89
1 class SpaceInvaders {
2 private final List<Invader> invaders = new ArrayList<>();
3 private final Display display ;
4
5 // constructor and other methods omitted
6
7 public void update() {
8 // Move invaders
9 for ( Invader i : invaders ) {
10 i.updatePosition();
11 }
12
13 // Draw invaders
14 for ( Invader i : invaders ) {
15 i.draw( display );
16 }
17 }
18 }
My first refactor step would be to break up the method. Not because it’s too long in
this case, but so I can turn those comments into methods.
Refactoring 90
1 class SpaceInvaders {
2 private final List<Invader> invaders = new ArralyList<>();
3 private final Display display ;
4
5 // constructor and other methods omitted
6
7 public void update() {
8 moveInvaders();
9 drawInvaders();
10 }
11
12 private void moveInvaders() {
13 for ( Invader i : invaders ) {
14 i.updatePosition();
15 }
16 }
17
18 private void drawInvaders() {
19 for ( Invader i : invaders ) {
20 i.draw( display );
21 }
22 }
23 }
This has extracted the two for loop blocks into private methods. The comment names
were then turned into method names.
Doing this is really helpful. It makes the code more self-documenting. Best of all, it
stops comments going out of date.
A comment is effectively duplication in the code. It says in English what a block of
code is doing. By turning this block into a method, we get the chance to add a method
name to explain what that code is doing. If the code changes, you are more likely to
change the method name than to change both the method name and a comment.
As a matter of style, as the class is called SpaceInvaders, I would probably shorten
the names to move() and draw(). We already know the class is all about Invaders.
The shorter names are clear because we have context from the class name.
Refactoring 91
The refactored public method now becomes a high-level overview of what it does
and not how it does it. Again, this is all about bringing the problem domain to the
fore, and hiding implementation details.
We had a simple string to represent the name of a user. This was passed into methods
as above. We used this everywhere. It was one of those early design decisions you
make so you can just get moving. A String might not be the best long-term decision,
but anything else seemed over-engineered at the time.
This worked well. But we had two problems in the end.
One issue was that we needed to change the code so that any given user was homed
on a particular server. The design later changed so that we needed both username
and home server information to be passed around. It was tough to change, because
all we had were Strings, everywhere. Search and replace just turned up thousands of
Strings, as you would expect. They were not always consistently named ‘username’,
either.
The other issue was simply that a String based username leaves you guessing
what format the String should be in. What exactly do you pass in? ‘almellor’?
‘[email protected]’? ‘1172397f’? It is impossible to know.
The answer was to Refactor > Change Method Signature for each method like this.
Instead of accepting a String, it would accept a User object:
Refactoring 92
1 class Chat {
2 public void sendMessage( User recipient ) {
3 // code ...
4 }
5 }
This clarified the code, prevented a whole class of bugs and gave us the freedom to
add the home server information into that User object.
As you can see, the whole ‘abstraction level’ has been raised again. We can now
clearly see that a Chat can sendMessage to a User. It’s written in plain English, right
there in the code. We see the ‘what’ more prominently than the ‘how’. We see ‘User’
and not ‘String’.
1 class InvadersGame {
2 public void draw( int x, int y, Image currentFrame,
3 int deltaX, int deltaY) {
4 // code
5 }
6 }
1 class InvadersGame {
2 void draw( Invader i ) {
3 // code
4 }
5 }
6
7 class Invader {
8 private int x;
9 private int y;
10 private Image currentFrame ;
11 private int deltaX;
12 private int deltaY;
13
14 public int getX() {...}
15 public int getY() {...}
16 public Image getCurrentFrame() {...}
17 public int getDeltaX() {...}
18 public int getDeltaY() {...}
19 }
I’ve omitted the code inside those getters, fields and constructors for the Invader
object. The structure is important here, not the detail.
We have taken all those parameters in the original draw() method and put them into a
new class - called a ‘parameter object’. The existing draw() method will then replace
each use of the original parameters with i.getX(), i.getY() and so on.
Invader is not yet an OOP object with behaviours. It’s a pure data structure.
Here is where it gets interesting. If we move the code from the original draw() method
into a draw() method on the Invader class, we can get rid of all those getters:
Refactoring 94
1 class InvadersGame {
2 void draw( Invader i ) {
3 i.draw();
4 }
5 }
6
7 class Invader {
8 private int x;
9 private int y;
10 private Image currentFrame ;
11 private int deltaX;
12 private int deltaY;
13
14 public void draw() {
15 // code can use x, y and so on. It's an object!
16 }
17 }
You can see this is less procedural and more like the OOP we have been learning.
Can you also see that it is starting to look ‘back to front’ - and more descriptive?
Our old code would have called draw() with a load of primitive values from
somewhere.
This refactored code creates an Invader object that knows how to draw itself
and move around. This has made the InvadersGame.draw() method shorter, more
obviously about an Invader - and redundant.
Extract Parameter Object is such a powerful refactoring that it turns procedural
designs inside out to become Object Oriented designs. You can expect the changes
to ripple out, dragging more and more objects into play.
By the end, your code will have that simplicity of objects collaborating together, with
hidden ‘secrets’ and meaningful names.
Refactoring 95
Unit tests aren’t the only game in town. There are other kinds of tests.
Integration tests take several pieces of code, join them up and test them as a whole.
Generally, some of these pieces will be external systems. So you might test some
business logic against a running database.
End-to-end tests expand on that idea. You wire up the entire system and test it just
as a user would. You drive the user interfaces and run against actual databases and
real web services. The whole thing is as ‘live-like’ as possible.
Contract tests are interesting. These split a test into two directions. They are used to
break apart testing against external service.
With these, you test twice. First, you test that your application logic can call a stub for
the service correctly. That’s what ‘testing the contract’ means - the contract between
your application and whatever it talks to.
Second, you test the contract between the code that talks to the external service and
make sure that it, in fact, does talk correctly.
There are browser tests, which are a form of end-to-end testing, that use a special
kind of web browser. You can script mouse movements, taps and keystrokes as if you
were a real user. It enables capture of the HTML and data sent to the browser for
checking.
Each level of testing gets more problematic. An integration test is slower than a unit
test, as it has to set up and control the external system. End-to-end tests are slower
still. They are somewhat harder to write.
The worst problem is about test repeatability. We want tests to either always pass
when the code is right or fail when it’s not.
Sadly, higher-level tests tend to be ‘flaky’. The code may be right, but something in
the external system might still cause the test to fail. It is often simple things, like
having the wrong data in a database, or a slow response.
Flaky tests can also be caused by intermittent problems. Things like network failures
and database connection limits are all problems here.
By way of example, one particularly tricky one to diagnose was an integration test
against a search web service. The service was behind a load balancer.
Hexagonal Architecture 98
In the test environment, the load balancer was set to round-robin mode. We had two
test instances of the service. The load balancer would route requests to service 1,
then the next request to service 2. At least it did - until one service was switched off
without anybody telling us. Oops.
All the integration tests passed then failed, passed then failed like clockwork.
It was purely down to the 50/50 routing of requests from the tests to one good service
and one down service.
You can also imagine that there might be test duplication amongst these different
levels of tests.
A small unit test that confirms that some kind of user option is available might be
duplicated by a check box’s browser test.
As with all duplication, this is a problem. It makes things harder to change and harder
to understand.
Having live-like test environments to support higher-level tests has its problems.
Hosting costs go up, as we need dedicated test machines. We may have data
protection and privacy issues if our test data relates to a real person. And the slow
running speed hampers development.
The Test Pyramid is a simple visual aid. It reminds us to use mostly fast unit tests.
Back those up with slower, flakier integration tests where we must. But not as many.
Then fewer still browser and end-to-end tests.
By minimising the amount of slow, potentially intermittently failing tests in our
system, we maximise our ability to test. We increase velocity and ship sooner.
So if the slower tests are less than ideal, the obvious solution is to avoid the problem
and use more FIRST unit tests.
If only it weren’t for those pesky external systems …
But we can eliminate those details from the bulk of our code. We already know how
to do this. We can use the Dependency Inversion Principle.
Let’s look at removing a database dependency from our ‘NotFaceBook’ app.
Our NotFaceBook system needs to lookup a User profile, based on a logged in user
ID. We can display a profile page based on whatever the user stored.
At first sight, this needs database code. It must do. The user stores their profile data
in the database: name, profile selfie, and favourite cat photo. Our code must fish it
out from there.
That’s the how of how we do this: the implementation details.
But what are we actually doing, at a high level?
We are finding a User object by its user id. We can assume that User object knows
how to supply profile information as its secret knowledge.
We can code that up:
1 interface UserRepository {
2 User findUserById( String userId );
3 }
UserRepository is an interface that allows our code to access the User and its profile
information, without knowing anything about databases.
We can write different implementations. We can just as easily code an implementa-
tion that talks to a file as one that talks to a Postgres database. Or MongoDB. We can
even code a test stub, returning fixed, known data.
We’re using the word Repository here as the most general way of describing
“Anything that stores, generates, and obtains User objects, somehow”. We’re using it
to suggest that the precise storage details are irrelevant.
Maybe it is not even stored. For example, in some games, the userID value could
be used to generate that profile information on the fly - not store it. That is still a
‘repository’.
We can use it in calling code like this:
Hexagonal Architecture 100
1 class NotFaceBook {
2 private final UserRepository users ;
3 private final Display display ;
4
5 public NotFaceBook( UserRepository users, Display display ) {
6 this.users = users ;
7 this.display = display;
8 }
9
10 public void showProfile() {
11 String userId = "almellor_19019";
12
13 User u = users.findUserById( userId );
14
15 u.displayProfile(display);
16 }
17 }
• Your test code accidentally connects to the real database. Oh dear. Catastrophic
when you test ‘deleteAllUsers()’
Hexagonal Architecture 101
• The test database does not have a User for the UserId you supply. The test will
fail as a false negative. The code was right, but the data was wrong.
• Testing addition of data fails the second time. The first time, ‘User1’ does not
exist and gets inserted. The second time, the database rejects the insert: ‘User1
already exists. Primary key conflict: UserId’
By using a simple in-memory stub, all of those problems go away. Instead, we get:
Hexagonal Architecture
• The Adapter Layer implements interfaces and uses classes from the Domain
Layer. It may depend on the domain layer.
• The Domain Layer MUST NOT use classes from the Adapter Layer.
The domain layer knows nothing about the adapter layer. There are no dependencies
at all on external systems. They simply do not exist in our domain layer. There is not
a single reference to a database, web server or anything else. It knows only about
business logic - the core of our problem.
This aids readability and testing.
Hexagonal Architecture 103
The adapter layer is the only place where details of the external systems appear. This
is the place for complicated SQL statements, HTTP calls, HTML templating and the
like.
This split helps us limit what code needs to change if we change technology in an
external system.
Want to change a SQL database like Postgres for a NoSQL one like Mongo? You write
a new adapter class and inject it in. Your domain layer stays unchanged. The domain
layer tests stay unchanged. This is a huge benefit.
Hexagonal Architecture relies on polymorphism and the SOLID principles to work.
The Adapter Layer implements interfaces that are fully substitutable because they
obey LSP. Each adapter class does only one thing, following SRP. The Domain Layer
is then open to changes in external systems without needing modification. It obeys
OCP.
Examples of each kind would be ‘User typed wrong number’ (ask again), ‘Disk drive
has crashed and lost all data’ (oops. Bad luck), ‘Temperature sensor not working,
when temperature feature is off’ (ignore).
Detecting an error is one thing. But then we must let our software know about it. We
need a mechanism to do that.
Java and OOP have several ways available. Each has its own trade-offs.
Let’s use a running example of our UserRepository. The User findUserById(id)
method should always return one and only one User object. The User object has
one method showProfile(), which displays profile information - on a good day.
Handling Errors 105
To keep code samples small, we’ll only show the method in isolation, and it will only
return the error case. Don’t go looking for the ‘happy path’ code, because there isn’t
any.
How can we tell calling code that something went wrong?
We’ve all seen this. It is baked deep into Java since Java 1.0.
The concept of null was invented by Computer Science luminary Tony Hoare. He
also invented Quicksort, which was a good idea. Unlike null.
Tony described null as his ‘billion-dollar mistake’.
Returning null is ok, in theory. The method either returns a valid User object if one
is available or returns null.
Calling code must check for the null return and decide what to do.
The problems with this include cluttering up the code with error paths, all those extra
if statement paths to write and test - and the chance of forgetting the test.
If findUserById(id) returns null, any further access will spectacularly fail:
Returning null complicates calling code with two paths. It is an accident waiting to
happen. You will forget to check for null at some point.
You will. You know you will. I know you will. I know I have!
Everything that follows is a rather desperate attempt to design-out the bugs caused
by unhandled null references.
Handling Errors 106
NullUser is fully (LSP) substitutable for User. We can do this either by having User
as an interface and creating RealUser and NullUser implementations. Or, we can
simplify a bit and create a User class with a NullUser subclass.
Either way, the NullUser class has a ‘no action’ method for showProfile:
When the calling code runs, it will be given either a User object, or a NullUser object
if there were errors. It then calls showProfile() on whichever object it got.
It is safe to do this because:
The NullObject pattern is very effective. It designs-out null reference bugs by not
using null. It simplifies calling code - our big goal in this book - by eliminating a
conditional branch. That simplifies TDD testing. All of that speeds up reading and
understanding this code.
Zombie object
There’s something a bit grubby about NullUser, though: That whole subclass thing.
I’ve heard complaints from pairs that “We only need one class and now you’re adding
two or three! Come on, Al - keep it simple!”
It’s a fair point. Although you can argue that you don’t only need one class. You are
representing two completely separate ideas - a User that exists and one that doesn’t.
But arguing with your pair is all a bit tawdry, really. So let’s go and make a nice hot
cup of tea each, and have another think.
What else could we do?
The ‘Zombie object’ is so named because the way it represents null is to be ‘the
walking dead’: The outside of the object looks normal, but the insides are bad.
We must modify our User class to add a ‘zombie detection’ method:
1 class User {
2 boolean isValid() {...}
3
4 void showProfile() {...}
5 }
The new isValid() method tells us whether the object has valid data or not. If isValid()
returns false, you must not use the object. You must check this first - every time you
use the object.
Calling code has the conditional just like the null check. But it reads nicer:
Handling Errors 108
1 User u = findUserById("alan0127");
2
3 if ( u.isValid() ) {
4 u.showProfile();
5 }
This is a judgement call. Is this any better than null and null checks?
My view is “sometimes, yes”. The minor improvement in readability can be worth
having. Using ‘Optimise for Clarity’ as our guiding principle, sometimes this makes
the calling code more descriptive.
You’ll have to decide for yourself. But if you see code like this written by others, this
is why. That distinctive pattern of if (object.isValid()) { /* use object */ }
is the signature of a Zombie object.
1 class ExceptionsDemo {
2 public void demonstrateExceptions() {
3 try {
4 System.out.println("This will print out 1");
5 methodWhichThrowsAnException();
6
7 // Line below will never run ...
8 System.out.println("This code will never be run 2");
9 }
10 catch( RuntimeException e ) {
11 System.out.println("You will see this - exception caught");
12
Handling Errors 109
The big idea here is that the keyword ‘throw’ when it runs changes the execution
flow. It stops running code in sequence at that point. The code will immediately exit
whatever method it is in, without executing any more lines of that function. It is a
form of early return statement.
Significantly, if the method was supposed to return something, that will not happen.
Nothing is returned. Not null. Nothing at all.
The throw keyword is designed to be paired up with the try and catch keywords, in
a construct called a try-catch block.
If some method throws an exception inside a try block, then code execution will
resume starting at the first line of the matching catch block.
The throw keyword has a parameter. This parameter should be an object instance that
either is - or extends - one of the two built-in classes Exception or RuntimeException.
The catch keyword has a matching parameter that receives the object used in the
throw statement. It is a way of passing information with the exception object, to
give diagnostics information to the catch block code.
In the example, the text passed into the RuntimeException object’s constructor will
be displayed by code in the catch block.
You can see how exceptions allow a method to be simply terminated early and control
passed to a catch block - called an exception handler.
Handling Errors 110
Exceptions are a powerful way of signalling ‘There was an error. I could not continue
executing that method. Here’s why’.
But they are controversial.
In practice, there are three ways of using exceptions.
1 try {
2 User u = findUserById( "alan0127" );
3
4 // if we reach here, we are guaranteed User u is valid
5 // due to our contract
6 u.showProfile();
7 }
8 catch( Exception e ) {
9 // if we reach here, we know the User object was invalid
10 // we can handle that
11 }
Seen in this way, exceptions clean up calling code. They eliminate null passing. They
eliminate conditionals. Indeed they design-out the idea that a returned object might
be invalid. That is not possible. You either get your result or an exception.
Handling Errors 111
Java fully supports this. It even provides an extra tool. If exception classes are made
to extend Exception, the Java compiler will enforce that the exception is either caught
at the call site, or is declared as being thrown up the call stack.
By contrast, anything extending RuntimeException doesn’t have as much scrutiny.
It’s actually the preferred way, these days, of using exceptions.
But you need to ideally pick one approach and stick to it. Otherwise, people get very
tetchy as they end up converting between approaches.
Anyway - now we can get back to our missing user problem.
NullPointerException
We’ve touched on this earlier. If you return a null, then attempt to call a method on
that null, Java goes boom.
To be fair, Java goes a lot less boom than C or C++ do. It is actually fairly civilised. It
throws a NullPointerException.
One approach to indicate that we cannot return a user object is simply to return null.
We can omit the null check and catch the exception instead:
1 try {
2 User u = findUserById("alan0127");
3 u.showProfile();
4 }
5 catch( NullPointerException npe ) {
6 // handle missing user
7 }
It’s similar in nature to the return null with a null check. You’ll see this in the wild.
I’m not sure I really like it - even though I’ve used it myself.
1 try {
2 User u = findUserById("alan0127");
3 u.showProfile();
4 }
5 catch( UserNotFoundException unfe ) {
6 // handle missing user
7 }
Error object
There’s something a bit off about exceptions and Object Oriented Programming.
OOP is all about behaviours and objects collaborating. Exceptions are all about
disturbing the control flow in a very procedural sense. Those two things don’t really
match.
Error objects are very different to exceptions. They are normal objects that will be
returned out of methods. Those methods will run and complete normally.
They work by using polymorphism and tell don’t ask. Twice, as we’ll see.
Handling Errors 115
Let’s go back to our findUserById(id) running example. It can either return a valid
User or tell the calling code that there isn’t one.
Using OOP, we can define each case as a separate object:
1 class ValidUser {
2 private final User user ;
3
4 public ValidUser(User u){
5 this.user = u;
6 }
7 }
8
9 class InvalidUser {
10 // Nothing to represent ...
11 }
Which of course sounds like a wonderful application of SRP, until we ask ‘How do
we return them?”
Ah.
Java functions can only return two separate objects if there is some kind of
inheritance relationship - extends or implements - between them. That’s not the case
here.
We can fix that by simply making them both implement an interface, of course.
Let’s make findUserById(id) return a UserResult interface:
1 interface UserResult {
2 // empty
3 // ... we'll come back to this ...
4 }
and the inside of that method can return either of our objects, if we make them
implement our empty interface:
1 interface UserResult {
2 void applyTo(); // ... we'll come back to this...
3 }
That’s better. Our calling code now no longer needs to know about which concrete
class we returned. It only has to call ‘applyTo()’ then the relevant object will apply
itself.
Ah. What will it apply itself to, exactly?
We’re missing another interface:
1 interface UserConsumer {
2 void validUser( User u );
3 void invalidUser();
4 }
1 interface UserResult {
2 void applyTo( UserConsumer consumer );
3 }
We have to update our ValidUser and InvalidUser with the new method:
12
13 class InvalidUser implements UserResult {
14
15 public void applyTo( UserConsumer uc ) {
16 uc.invalidUser();
17 }
18 }
Finally, this makes sense. We tell our result objects to apply themselves, but this time
to some new consumer object we will make.
This new consumer object will have two methods validUser, passing the User object
as a parameter and invalidUser. In each case, the consumer object is guaranteed to
either be given a valid User object or that no such object exists.
The idea here is that findUserById(id) now returns an object. We can tell that to
applyTo() some other object we have created to handle results. That object knows
how to handle either a valid or an invalid object.
This is a ‘push’ solution. We use Tell Don’t Ask across two objects to resolve the
valid/invalid behaviour.
If you think back to a null check solution, you would have two blocks of code that
ran; one for null, the other for not null. Now, we have two different methods that get
called for each case.
As an example, lets create a simple object that will print the profile of a valid User
and write an error message to the console for an invalid one:
This approach combines pure functions into a pipeline. You’ll see that this doesn’t
really live in the world of OOP. Typically, classes are abused as pure data structures -
getters and setters, no real behaviours, no encapsulation. The external functions add
behaviour onto that raw data.
Handling Errors 120
As a result, Java needed a new way to handle errors inside these chains of functions.
In the snippet above, what happens when there are no users whose name starts with
Al?
That method simply could not work as written. There needs to be some way to signal
to that function chain that ‘the data was not found’.
Java 8 borrowed from other functional languages to give us the concept of an optional
value. Just like it says, this is a value that may, or may not, contain the data we want.
At its simplest, we can make our findUserById(id) method return an optional User:
Inside the method, we can use the construct return Optional.of( user ); to return
a valid User object, or return Optional.empty();, to indicate that no valid User
exists.
The calling code is where this gets interesting. We can choose to handle these cases
either with a simple if statement, or by supplying a ‘lambda function’ to act on our
valid User.
The if statement resembles a null check:
Technically, there is no improvement over a null check. But I do think the readability
of this is much better. It is intentional. We can clearly read that this code is all about
an optional value and code that handles the value if it is present.
But Java 8 provides its own way of eliminating the if statement:
Handling Errors 121
1 findUserById("alan0127")
2 .ifPresent( user -> user.showProfile() )
3 .orElse( System.out.println("Sorry, we could not find that user"));
This is a simple refactoring of the if statement above, but using the ifPresent() method
of Optional. This takes a lambda function reference as an argument - we pass in some
code to execute if the value is present. The chained method orElse() similarly takes
in a lambda function reference to run if the value is not present.
This has a readability advantage - once we are familiar with the syntax of method
references. It also has all the usual advantages of avoiding null checks.
This style is gaining popularity, as newer (Java 8 and onwards) libraries adopt
Optional<>.
in more advanced ways, like adding ‘error( ErrorInfo reason )’ kinds of methods, in
addition to valid() and invalid() methods.
Exceptions are a fairly clean approach. Detractors say they are ‘a glorified goto’.
That’s fair. They point out that exceptions should - in their view - only be used
for things truly unrecoverable, errors that nobody was expecting. I hope I’ve shown
you that Bertrand Meyer’s ‘Design by Contract’ shows an alternative viewpoint.
Zombie objects are surprisingly useful. Combining minimal plumbing with enhanced
readability, they form a ‘cheap and cheerful’ error object.
When deciding, you need to think through the trade-offs given your existing code,
your existing developers and the feelings towards the various approaches.
You’ll often find it is the ‘feelings’ part that is the deciding factor.
Design Patterns
It can be hard work, this mapping behaviours to different objects. Just like it is hard
re-inventing a house from scratch, for every house on a street.
By the mid-1990s, the object oriented world had discovered the same thing. A lot of
objects had the same kind of ‘shape’. Just like houses on a street.
To understand what a pattern is, let’s come at this in reverse. Imagine you’ve written
an object that has three data fields:
• A name
• A timestamp
• An amount
These are the secrets of your class. Without overthinking, what’s the first thing that
comes into your head that this represents? No cheating, now! 3, 2, 1 … Go!
I get:
Thinking back to our User.greet() example, we might have a Customer class with a
welcome() method. It’s a different class name, and a different method - but the same
basic idea.
This is known as a Design Pattern.
Objects form patterns at the design level. Groups of objects jump out as having a
similar behaviours in one subject area as they do in another.
A pattern gives us a jump-start on our designs. We can start with something similar
and tailor it to our needs. We’re buying an off the peg, mass-produced object design
and tweaking it, rather than cutting cloth ourselves.
And mangling our metaphors. I do apologise.
The 1990s jumped on this idea with vigour. It gave rise to several books, papers,
journals and conferences. Some were good, some were seminal, some were rather
forgettable.
The rest of this chapter is my take on patterns. These are the ones I use professionally,
with my explanations of how I understand them.
Before we dive in, I want to raise one important distinction about where patterns fit.
It’s related to mechanism and domain.
This distinction is important when studying design patterns because patterns exist
at both levels. They solve different problems.
Design Patterns 125
Earlier patterns solved mechanism issues and came up with things like Iterators and
Strategy patterns. Later attempts looked at solving domain problems, coming up with
things like ItemDescription-Item and Order-OrderLineItem patterns.
Like the rest of this book, the split is about how things work versus what they achieve.
Here are the patterns I have found most useful. I’ll use some initials to say which
book I found them in first:
Strategy
[GoF] Gives you pluggable behaviour
Strategy is used when you have an object that needs a way to change its behaviour.
Depending on configuration, or in response to external events, it needs to do some
processing differently. You could do this with conditionals like switch statements.
But you want to respect OCP and keep that object unmodified.
Injecting a Strategy object enables this.
Design Patterns 126
As an example, employees can have different bonus schemes as they hit different
targets.
Here is our Strategy pattern interface. It will allow us to make various implementa-
tions:
1 interface BonusScheme {
2 void applyTo(Money pay);
3 }
1 class Employee {
2 private BonusScheme bonus ;
3
4 public Employee(BonusScheme bonus) {
5 this.bonus = bonus;
6 }
7
8 Money totalPay() {
9 Money pay = calculateBasePay();
10
11 // Use the strategy
12 bonus.applyTo(pay);
13
14 return pay;
15 }
16
17 private Money calculateBasePay() {
18 // ... code
19 }
20 }
This Employee object is now open for behaviour changes in its bonus scheme. But it
is closed to modification. We don’t need to change the insides just to change a bonus
scheme.
Let’s demonstrate that with two simple schemes - NoBonus and BonusTwenty
Design Patterns 127
When we create employee objects, we inject whichever bonus scheme concrete object
we want to use.
Real payment systems are more complex and would have logic in the bonus scheme.
That would collaborate with other objects, like Targets perhaps, to see if we qualify.
The BonusScheme objects might even get Strategy pattern objects of their own.
Strategy crops up everywhere you want to vary behaviour. In the same way a variable
handles variable data, the Strategy pattern handles variable behaviour. It’s as simple
as that.
Strategy can be a low-level mechanism technique. At a higher level, it can express
domain ideas that have changing behaviour in the real world. The classic being
Employee objects that start out as juniors but then get promoted to managers.
Strategy makes for a direct model of that.
Examples: Tax Calculation, Payment schedule, Data source selection, Graphics filters,
Plugins of all kinds, Extension points, Customisations, Skins, Complex rules.
Observer
[GoF] Triggers behaviour somewhere else
Observer is a decoupling technique that splits apart a ‘thing that happened’ from its
response.
Design Patterns 128
The classic use of Observer is in GUI toolkits. You have a Button object that can
be clicked on. The Button class should be completely reusable, but you need some
way for it to trigger your application code. We clearly can’t put our application logic
inside the reusable Button code. It wouldn’t be reusable then.
We need a solution. We need to tell the Button code which part of of our application
code we want to run. We can do that with the Observer pattern.
We make the Button ‘observable’, which means that we can attach a list of observers.
These ‘observers’ will contain our application code.
When we click the Button, this will execute a method on all the observers that we
register. We will call the interface ‘Actionable’, because that’s what it provides:
1 interface Actionable {
2 void doAction();
3 }
4
5 class Button {
6 private final List<Actionable> observers = new ArrayList<>();
7
8 void register(Actionable a) {
9 observers.add(a);
10 }
11
12 void onClick() {
13 observers.forEach(Actionable::doAction);
14 }
15 }
That class is part of our GUI toolkit. It has methods not shown here. Typically
these will allow a Button to draw itself, and to receive a click notification from the
operating system.
When a click happens, method onClick() in Button is called. That calls the doAction
method of each registered Actionable observer.
Our application code can then make classes that implement Actionable. These classes
are fully decoupled from the Button class itself. The Button lives in the GUI toolkit
Design Patterns 129
and has zero knowledge of your application code. Your application code only needs
to know about Actionable and that it can receive a call to the doAction() method.
What your code needs to do in response to the click lives there.
Observers are so good at decoupling ‘what happens’ from ‘what caused it’ that they
crop up everywhere. GUI toolkits, web frameworks, message queue systems, process
pipelines.
The Observer pattern is a low level mechanism pattern in general. Obviously, that
can ‘scale up’ to a domain concept if it fits. A PayrollRun object can easily have
observers of BankTransfer and PayslipPrinter.
Systems like this often end up as event driven architectures.
Examples: GUI frameworks, Event driven systems, Interrupt handlers, Communica-
tion channels - TCP handlers, JMS messaging.
Adapter
[Gof] Adapt an interface for use by something else
We’ve seen this pattern in Hexagonal Architecture, where it is a perfect fit.
Adapter solves the problem where we have calling code expecting one kind of
interface, and a supplier that has another. We need to bridge that gap somehow. The
way to do it without changing existing code is to write new code that knows how to
talk to both.
We say this new code adapts one interface to another.
The example we used earlier was an interface to find a user:
1 interface UserRepository {
2 User findUserById( String userId );
3 }
Suppose we had a small in-house SQL database library. It has class Database that has
a query method that returns a Rows object given an SqlQuery object:
1 class Database {
2 Rows executeQuery( SqlQuery query ) {
3 /// ... code
4 }
5 }
Please forgive the SQL Injection vulnerability and the fact it will most likely crash if
there is not exactly one user of that userId (ahem).
The gist is that we search our database - using SQL - for the relevant row in our Users
table. We pull columns out of that row and use them to create a User domain object.
We return that object.
Design Patterns 131
We have not altered any of the calling code in our domain that depends on
UserRepository to know anything about the SQL database library. We have not
altered any of our in-house SQL database library to know anything about the domain
and its User class.
We have written code that adapts what our domain model wants into what our
existing libraries can provide. We have respected SRP and OCP whilst doing this.
Adapters are really powerful. They are fundamental to Hexagonal Architecture -
there is a whole layer of them, after all. Adapters see much wider use.
Any time we have an object that needs something from another, but their methods
don’t quite line up, we can use an Adapter.
The GoF book lists a related pattern called Bridge. It has an interesting section on
the similarities and differences to Adapter. I never really saw them as being different.
They both make one thing work with another thing that wasn’t designed to work
with it. I tend to take a simple view of things.
Examples: Hexagonal Adapter layer, Converting data formats, converting presenta-
tion formats, linking different versions of published APIs.
Command
[GoF] Gives us a self-contained action
A lot of time we’re responding to events in programs. Things happen; a user submits
a form. A timer counts down to zero. A loyalty card is scanned. Each one of these
events would need some action from our system.
A command object contains the code for that action. It presents a uniform interface
to calling code, so that all actions can be called in the same way. This allows us to
build libraries and frameworks, where our calling code can pass Command objects
into the framework to be run at the right time.
Design Patterns 132
1 interface Command {
2 void execute();
3 }
The basis of a Command object is an interface with the single method execute. We
tell the concrete class to execute whatever action code it contains.
As Commands are good at handling events, a reasonable example is to see what our
GUI Button class would look like with one:
1 class Button {
2 private final Command action ;
3
4 public Button( Command action ) {
5 this.action = action;
6 }
7
8 public void onClick() {
9 action.execute();
10 }
11 }
Variations include:
• Passing the Button into the execute method to identify the cause
• Maintaining a list of actions, each triggered in order
• Adding an ‘undo()’ method to the Command interface to reverse the action
Composite
[GoF] Makes many things look like one thing
Composite is a kind of adapter class. It adapts a collection of multiple things into the
same interface as just one thing. This makes it a nice pattern you can use in addition
to others.
Going back to our Shape.draw() example, the Composite pattern gives us one very
useful extra Shape: ShapeGroup:
Design Patterns 133
This class can be used anywhere that a Shape interface is expected. It is fully
swappable for a single Shape. It allows any number of Shape objects to be added
using add() then will draw all of them, when its draw() method is called.
Composite objects like this have uses everywhere. The classic use is when some
existing code you cannot change expects a single object - like a single Shape above -
but you need it to work with multiple objects.
Simply wrap up a collection of objects into a Composite and we’re good to go.
Facade
[GoF] Simplifies how we call a complex group of objects
The term Facade comes from buildings architecture. It describes how a ‘fake front’ is
built out of different materials than the rest of the building to improve its appearance.
We use a Facade in OOP when we want a simple interface to our calling code, but
the objects needed are harder to work with.
A web service might be made up of a large number of complex objects. A Tax
calculation engine might have many objects representing tax bands, allowances,
exemptions, thresholds and so on.
Having our application code create each one of these objects, wire them all together
and configure them would be quite hard. It would expose a lot of needless detail.
By writing a simple Facade that does all this for us, we can simplify the calling code:
Design Patterns 134
1 class TaxEngine {
2 public TaxEngine( Configuration c ) {
3 // ... Code to create objects, configure, wire up
4 }
5
6 public Money calculateTaxLiability(DateRange taxPeriod) {
7 // ... code
8 }
9 }
This TaxEngine facade hides all the detail of wiring and configuring the many objects
required. It presents a simple interface to the caller.
Builder
[GoF] Makes complex objects easier to construct
When objects get data-heavy as part of the secrets they hide, they grow large
constructors. When parts of that data are optional, then the calling code often ends
up supplying null and empty parameters. Messy.
One way to improve this is to make multiple different constructors. Each one only
lists non-empty parameters, then uses this() to call other constructors. We push the
defaults into the constructor in this approach.
This works, but the number of constructors needed gets out of hand quite quickly.
Without an IDE to help, it’s hard to know which parameter is which. What does new
User ("alan", 3, 198736543, "red") mean, anyway?
Builder is a pattern to help with this. It provides a number of named methods which
describe the purpose of each piece of data supplied. It contains the default and empty
values to be used. And it only requires a single, ‘full’ constructor for the object being
built.
We might have three optional fields in our User class :
Design Patterns 135
1 class User {
2 private final String userId ;
3 private final Date dateOfBirth ;
4 private final User linkedAccount ;
5
6 public User( String userId, Date dateOfBirth, User linkedAccount) {
7 this.userId = userId;
8 this.dateOfBirth = dateOfBirth;
9 this.linkedAccount = linkedAccount;
10 }
11 }
We could create a User with default dateOfBirth and linkedAccount by the calling
code:
Where User.NOBODY is a public static constant null object signifying ‘no user’.
This works reasonably well for three parameters, but it does obscure which ones are
the genuine parameters and which are defaults. As parameter lists grow, the situation
gets worse.
A UserBuilder would look like this:
1 class UserBuilder {
2 private String userId = "";
3 private Date dateOfBirth = new Date();
4 private User linkedAccount = new NullUser() ;
5
6 public User build() {
7 return new User( userId, dateOfBirth, linkedAccount );
8 }
9
10 public void userId( String userId ) {
11 this.userId = userId ;
12 }
Design Patterns 136
13
14 public void dateOfBirth( Date dateOfBirth ) {
15 this.dateOfBirth = dateOfBirth;
16 }
17
18 public void linkedAccount( User linkedAccount ) {
19 this.linkedAccount = linkedAccount ;
20 }
21 }
You can see that the default values are built into the Builder. We can change them
with the ‘setter’ methods provided. Once we have set all we need, we create a User
object using build().
I prefer to put the build method at the top, just as a matter of style. I think it is the
‘most important’ method and the one that helps you understand the class.
A Java library exists to automate writing builders like this, which is useful:
Project Lombok at https://projectlombok.org/features/Builder
It’s worth noting that large parameter lists are often a code smell in OOP code.
It’s usually a sign that the object should be broken down further. Do consider that
before creating a Builder.
Repository
[DDD] Represents stored objects, without saying how they are stored
Another foundational pattern. We have some data in some kind of storage that we
want to work with. But we don’t want our application to know anything about that
storage technology. We do want to get that data back out and use it to create objects.
Repository allows us to create an abstraction of stored data, in our domain terms.
We’ve seen this earlier. We’ve used it to store User objects as an example of the
Design Patterns 137
Adapter pattern. Repository focusses on the interface that is called, rather than the
adapter class implementation.
To show how useful Repository is, let’s add a few common methods:
1 interface UserRepository {
2 User findUserById( String userId );
3 Users findUsersWhoseNameStartWith( String startOfName );
4 Users listAllUsers();
5
6 Users execute( Query q );
7 }
The final method is really interesting. It returns all User objects matching a specified
Query, passed in as a parameter object.
Query is itself an abstraction. It is not an SQL ‘SELECT’ query. It is not a Mongo
query. It is a group of pure Java objects that represent a query. You would see things
like:
Design Patterns 138
1 Query q =
2 new QueryBuilder()
3 .attributeMatches( "name", "Al*")
4 .and()
5 .timeAttributeBefore( "creationPoint", new Time("2020-01-05T11.59.00Z\
6 ") )
7 .build();
Which would build an Abstract Syntax Tree (AST) describing a query that matches all
Users whose name begins with “Al” and whose creationPoint timestamp was before
11:59 on Nov 5th 2020.
The Query object has a translate( QueryDialect dialect ) method on it. This gets
passed a concrete object that knows how to generate a specific query for a specific
technology - like MySqlQueryDialect. Often, this is done using the Visitor pattern. It
is probably the canonical example of why you would use Visitor.
This maps the generic query to be mapped into something useful to an implementa-
tion. It also means the domain code query above does not change when the storage
technology changes.
Examples: Relational data access, NoSQL data access, REST data store clients, caches
Query
[PoEAA] Describes what to search for, but not how it will be done
Our applications need to be able to find objects once they are created.
In memory, once we create an object using new, we receive a reference to that object.
We keep hold of that reference and use it to access the object.
For objects we stored in the Repository earlier, we don’t have this reference. To find
our object(s) again, we need a Query. Some way of specifying ‘look for the object(s)
best matching these search terms.
Queries take all forms. We’ve seen one frequently in out findUserById(userId)
method. Here, the search criteria “by user ID” is baked into the method name.
Design Patterns 139
It’s easier to understand once we realise all queries can be written in English. “Show
me all the users who joined yesterday” is an example. That’s the role of the Query
object pattern. The Query object expresses things in pure domain terms. A translator
object then converts this into whatever form is needed.
1 class Query {
2 private String name ;
3 private Object value ;
4
5 public void attributeEquals(String name, Object value) {
6 this.name = name ;
7 this.value = value ;
8 }
9
10 public void translate(Translator t) {
11 t.generate(name, value);
12 }
13 }
14
15 interface Translator {
16 void generate(String name, Object value);
17 }
This bare-bones Query object shows the basic split of work. We can represent just
the one query - ‘is <attribute> equal to <value>?’. We can call translate() passing in
some Translator object to generate an implementation-specific query.
Here is a simple SqlTranslator object that will create a SELECT statement for us, then
display it - just for this example. It would normally link up to a Database object. This
would manage a JDBC connection, run the SQL, then return a User object.
You can see how the details of SQL are hidden in the SqlTranslator object. The details
of what we want to search for are hidden in the Query object itself.
Significantly, they are expressed in domain terms - not SQL or database terms. We
could write a MongoTranslator or FileTranslator and make those work.
You can also see the comment: it only works for String values.
More complete Query objects need to convert between Java data types and whatever
type the storage technology needs.
In fact, Query objects and translators get complex, fast.
The full-fat version builds an Abstract Syntax Tree of Criteria objects, complete with
operators such as AND, OR, LIKE, NOT, EQUALS, BETWEEN. They will convert
Design Patterns 142
between all the data types and use the Visitor pattern to translate the Criteria graph
into the implementation version.
I have actually built one of those for a real project. It was a system where a user could
build their own queries to report on a fleet of printers in an enterprise network. The
actual implementation was intended to use a number of backend database systems
to store a snapshot of that fleet’s status.
The best reference on complex Query objects is Patterns of Enterprise Application
Architecture by Martin Fowler [PoEAA]. Remember you can start simple and still
get the benefits for many applications.
CollectingParameter
[C2WIKI] An object that collects results
How do we use tell-don’t-ask style coding with an aggregate object when we want
to produce some kind of summary or total result?
A Collecting Parameter is an object we can pass into a method. The same collecting
parameter will be passed to a number of different objects, so that each object can add
its contribution:
1 class UserMetrics {
2 void ageLessThan25();
3 void age25To35()
4 void age35Up();
5
6 void premiumSubscriber();
7 void inactive90days();
8
9 void display( Display d );
10 }
1 class Users {
2 private final users List<User> users = new ArrayList<>();
3
4 public void add( User u ) {
5 users.add( u );
6 }
7
8 public void collect(UserMetrics metrics) {
9 users.forEach(user->user.reportMetrics(metrics));
10 }
11 }
Item-Item Description
[Coad] Keeps a Thing and its description separate
This useful pattern crops up in e-commerce a lot.
You have a Thing - product, service, offering, movie, song - whatever. The Thing
needs a description so that it can go in the catalogue.
The Thing is logically different than its description. You can talk about how many
copies of ‘The Andromeda Strain’ movie you have to buy. You can also talk about
Design Patterns 144
who directed that film and who wrote it. The two are different views, so it makes
sense to separate them out.
1 class MovieDescription {
2 void displayTitle(Display d){...}
3 void displaySynopsis(Display d){...}
4 void chargePriceToAccount(Account a){...}
5 }
6
7 class Movie {
8 private final MovieDescription description ;
9
10 public Movie(MovieDescription description) {
11 this.description = description;
12 }
13
14 void buy(Account a){...}
15 }
Moment-Interval
[Coad] Captures a period of time, beginning to end
Moment-Interval represents a period of time defined by a start time and an end
time. Behaviours can either be general-purpose - like boolean isAfter(time), boolean
wasBefore(time) - or application specific.
Time ranges are important in many domains. Rental agreements, leases, validity
checks, access control windows are all subject to time limits. Moment-Interval gives
us a clean way to model them.
Design Patterns 145
1 class SubscriptionPeriod {
2 private final Date start ;
3 private final Date end ;
4
5 public SubscriptionPeriod( Date start, Date end ) {
6 this.start = start;
7 this.end = end;
8 }
9
10 public void applyTo( Subscription s, Date now ) {
11 if ( now.isAfter(start) && end.isAfter(now) ) {
12 s.subscriptionActive();
13 }
14 else {
15 s.subscriptionExpired();
16 }
17 }
18 }
19
20 class Subscription {
21 private SubscriptionPeriod period ;
22
23 public void renew(Period p) {
24 this.period = p ;
25 }
26
27 public void provideService( Date now ) {
28 period.applyTo( this );
29 }
30
31 public void subscriptionExpired() {
32 // action when expired
33 }
34
35 public void subscriptionActive() {
36 // action when active
Design Patterns 146
37 }
38 }
Software-as-a-service (SaaS) businesses rent out access to their offering for a fixed
period of time. The period you can use them ranges from the moment your payment
is received until some point in the future, often based on how much you pay.
The Subscription above would represent a time-bounded service. Calling method
provideService() with the current time would delegate to the Period object.
This is our Moment-Interval pattern object.
This would check the time now against its limits. If it is within those limits, it will
call back on the Subscription active, triggering the ‘subscriptionActive()’ method.
This would provide the paid-for service.
If the subscription has lapsed, then Period will call back to ‘subscriptionExpired()’,
which can then attempt to get the user to renew their subscription. If they do,
Subscription method renew() would be called, with an updated Period.
Clock
[Own] Provides a way to represent current time that can be stubbed
Time driven functions need to know the actual real-world time now. Writing this, it
is the 11 Nov 2020, 22:54:23 BST.
All usable systems provide a way to get real-world time. Java provides the older
new Date() syntax to access the system clock. The Java 8 Joda-time inspired update
provides newer features and a less zany syntax.
But both systems share the same problem. If you use them directly in your code,
you are stuck with the actual time, right now, in the real world. This makes testing
either hard or impossible. Recreating a production fault from logs captured earlier is
impossible, too.
In both cases, we need a way to force the time to a test value.
The Clock pattern is a simple abstraction of the system clock. Using the older Date
syntax for simplicity, it looks like this:
Design Patterns 147
1 interface Clock {
2 Date now();
3 }
This is a Dependency Inversion (DIP). Our code now depends on only this interface
for its source of the current time, using the now() method.
For production, we define a SystemClock class:
We Dependency Inject this SystemClock class to everywhere that needs to know the
time. Often, this comes from a Config class that runs at application startup.
For testing, we inject a stub class:
18 }
19 }
This stub has features to set to a specific time, then move to an hour later or earlier.
You would change these to your specific test requirements. The idea is that you are
creating a Domain Specific Language (DSL) about test times, to make your test code
into readable documentation.
This is one of those insanely useful patterns that it is actually muscle memory for
me.
1 class TaxBand {
2 private final Money lowerLimit ;
3 private final Money upperLimit ;
4 private final BigDecimal percentage ;
5
6 public TaxBand(Money lowerLimit,
7 Money upperLimit,
8 BigDecimal percentage) {
9 this.lowerLimit = lowerLimit ;
10 this.upperLimit = upperLimit ;
11 this.percentage = percentage ;
12 }
13
14 public void apply( Money amount, Money totalTax ) {
15 if ( amount.atOrAbove(threshold) ) {
16 totalTax.add( calculateTax(amount) );
17 }
18 }
19
20 private Money calculateTax( Money amount ) {
21 Money taxableAmount = amount.subtract( lowerLimit );
22 return taxableAmount.multiply( percentage );
23 }
24 }
This is a Rules object. You call apply() with the amount to calculate tax on and pass
in a CollectingParameter to collect the total tax.
Each TaxBand object is constructed with the secrets it needs to apply that specific
rule.
In this design, TaxBand objects are chainable. We could either add a method
chain(TaxBand next) - internally forming a linked list of Tax Bands - or create an
Aggregate object called TaxBands. Both read well in this case, so take your pick. I
have a slight preference for the Aggregate approach. It separates the concerns of ‘one’
and ‘many’ more fully.
In many systems, rules are configurable. A rule to limit screen time might be
Design Patterns 150
configured with a maximum duration. This is often done as a simple variable set
by configuration.
Rules objects are really useful when the behaviour itself has to be configured, rather
than just some threshold value.
Examples: Access Control, retention policies, business process rules
Aggregate
[DDD] Represents a group of objects and the behaviours that apply to all
Aggregate objects were covered in an earlier chapter. Class User represents one user.
Class Users represents a group of them.
They wrap a collection of single objects. Behaviours are the ones common to group
operations. Methods operate on all objects in the collection.
Cache
Speeds access to an object that is slow or expensive to fetch
Many systems end up fetching data from somewhere else, often a database or a
web service. These things live at the other end of a network connection. That makes
fetching data five or six orders of magnitude slower than it would be if it was inside
a local object.
We can’t simply avoid calling that remote data. That’s where the data actually lives,
like it or not.
What we can do is make that remote call as infrequently as possible. We do this by
keeping an up-to-date copy of it as a local object.
Cache is the name we give to this idea. A simple cache is a map of objects most
recently used:
Design Patterns 151
1 interface UserRepository {
2 User findUserById( String userId );
3 }
4
5 class UserCache implements UserRepository {
6 private final Map<String, User> cache = new HashMap<>();
7 private final UserRepository repository ;
8
9 public UserCache( UserRepository r ) {
10 this.repository = r
11 }
12
13 public User findUserById( String userId ) {
14 if ( cache.containsKey( userId ) ) {
15 return cache.get( userId );
16 }
17
18 User user = repository.findByUserId( userId );
19 cache.put( userId, user );
20 return user;
21 }
22 }
This is actually a ‘buy one get two free’ of design patterns: a Repository, Decorator
and a Cache. We even have some LSP thrown in. Let’s explain those one at a time to
see why they are a perfect match here.
The Cache part is the HashMap called cache.
This is used to store users in a simple lookup table, against their userId. Given a
userId, we can check the cache variable to see if we already have the relevant User
object stored in there. The first three lines of method findUserById() do that. If the
User exists in our cache, we return that.
This is the key benefit of the cache - we return straight away with a local object. We
avoid that slow round trip to the UserRepository.
If the User is not present in the cache, that must mean we haven’t asked for that
Design Patterns 152
userId before. So, we go off to the UserRepository. We fetch the User object, store it
into the cache, then return it. The next time we ask for this userId, it will be returned
from the cache.
Caches are very commonly used to speed up slow operations. Repositories and
External Systems typically benefit from a Cache.
That said, they are difficult to design right.
Our simple example has two serious problems:
These two issues are part of cache invalidation, the act of marking a cached item as
no longer valid.
Real caches get increasingly complex to handle this in production.
You often see an associated Rules pattern object to describe when to clear out the
cache. Popular choices are Least Recently Used (LRU) which requires a timestamp
being stored alongside the cached object and the simpler Most Recent cache, where
we only make space to cache the last N items.
They say there are four hard things in computing: Concurrency, Cache Invalidation
and off-by-one errors …
As for the other two patterns, we are using Repository to abstract away the details
of how we store User objects. We’ve covered this before.
We are also using the Decorator pattern, first mentioned in the [GoF] Gang of Four
Design Patterns book. That’s a useful pattern worth breaking out into its own section.
Let’s do that next.
Decorator
[GoF] Add extra features to an object in a substitutable way
Decorator gives us a way to add features to a class without altering that class. It is
a wrapper for that class, similar to an Adapter. It differs from Adapter because the
new class can be substituted for the original one it wraps. It is fully LSP compliant.
Design Patterns 153
This means that calling code can be fed either the original class or the decorated one
and not have to change.
In the explanation of the Cache pattern, we used a Decorator around a Repository
object for this very reason:
1 interface UserRepository {
2 User findUserById( String userId );
3 }
4
5 class UserCache implements UserRepository {
6 private final UserRepository repository ;
7
8 public UserCache( UserRepository r ) {
9 this.repository = r
10 }
11
12 public User findUserById(String userId) {
13 // Extra functionality goes here
14
15 User user = repository.findByUserId( userId );
16
17 // Extra functionality goes here
18 return user;
19 }
20 }
This is the same code example but with just the parts relating to Decorator left in.
The functions relating to the Cache have been removed as they are not part of the
Decorator pattern itself. That’s much simpler.
The tell-tale signs of this being a Decorator are the constructor which takes a
UserRepository object, and the class which implements the UserRepository.
It is both substitutable for a UserRepository and yet has access to one itself - always
a different object instance. Here, you could imagine we have MySqlUserRepository
as an instance to pass into UserCache. The end repository is itself substitutable.
Design Patterns 154
We covered this fully in the chapter on Hexagonal Architecture. This is the pattern
behind it.
An External System object is powerful because it decouples us from the details of
that system. This gives us several possibilities. We can use a stub for testing. We can
create simple Fakes to help us split up development across our team. We can wrap
the system with things like a Cache, or logging or monitoring.
Design Patterns 155
Specifically, an external system object represents only what we need of that system.
Google Maps, as one example, has a large number of useful features. But if all we
needed was the ability to get the name of the nearest town to a point, then our
interface will be:
1 interface Geography {
2 String getNameOfTownNearestTo( float lat, float long );
3 }
Astute readers (which is, of course, all of you lovely lot) will notice that this is
exactly what we have been doing this whole book with OOP. Just swap ‘object’ for
‘server’ and we’re done: Consumer Driven Contracts.
I do tend to view a lot of software engineering like this. It’s a handful of core
concepts that crop up all over the place. BDD, Tell Don’t Ask, Consumer Driven
Contracts - it’s all the same idea. The jargon sometimes makes it sound harder than
it really is.
The original pattern I saw was Proxy in [GoF]. That was a more general kind of
object. It had the same interface as some other object and stood in place of it. In my
work, Proxy’s overwhelming use has been to represent an external system, so I prefer
to think of it as above.
Design Patterns 156
Configuration
[Own] Gives a way to change how the system is set up
Once an Object Oriented program starts doing useful work, it will need various
things setting up, both at start up and throughout its runtime.
At start up, it will need all its objects creating as instances of classes. All the various
dependencies need injecting. URLs of external web services need to be provided. The
connection string and address of our databases need providing.
These kinds of things are often specific to the environment we run in. The database
string will be different when we are using the test database as to when we run in
production. The web service URL might point to a fake server made with WireMock,
rather than the real service on the web.
Even the kinds of object may change. We might have a setting in a file to use either an
Oracle database or Microsoft SQL Server. A different object might need to be created
in each case, then injecting into all its callers.
We call this Configuration. It includes setting data values like URLs, creating specific
objects and ‘object wiring’ - inject dependencies between objects.
The best place to put all this is in a Configuration class:
1 class Configuration {
2 private boolean useOracle;
3 private String jdbcConnectionString;
4 private boolean useDarkMode;
5 private Date offerExpiryDate ;
6
7 public void load() {
8 // Code to read a configuration file, or server
9 }
10
11 public UserRepository userRepository() {
12 if ( useOracle ) {
13 return new OracleUserRepository(jdbcConnectionString);
14 }
Design Patterns 157
15
16 log(WARN, "Using STUB User Repository - TEST/QA ONLY");
17 return new StubUserRepository();
18 }
19
20 public UiStyleScheme styleScheme() {
21 return useDarkMode?
22 new UiStyleDark() :
23 new UiStyleLight() ;
24 }
25
26 public Date getOfferExpiryDate() {
27 return offerEXpiryDate ;
28 }
29 }
Gives the basic idea. Configuration is held somewhere then read in. Based on this
data, the right kinds of objects are created for use elsewhere. The rest of the code
refers to this Configuration object to get its dependencies and values.
The actual configuration data is stored typically a ‘config.json’ kind of file, or perhaps
a database at a fixed location, or a web service. Some providers like Spring Cloud use
network broadcasts to discover the configuration server at run time.
Once read in, the methods on the Configuration object can be used to create the
specific objects needed. They can be initialised with the required values of things
like URLs, timeouts and connection strings.
Often, whole objects can be created here. That leads to nice clean code. But I have
added a getter to return a pure data value. Configuration classes are one place where
that can often be a good idea, resulting in the clearest code.
As the application grows, it makes sense to split into multiple Configuration classes.
These can align with your Hexagonal Architecture so that each subsystem gets its
own Configuration object. You will probably end up having a Configuration interface
in your domain layer, then writing a specific Adapter for it to link it up to your actual
source.
Design Patterns 158
I’ve also shown a common idea where we normally use the Oracle database in
production, but we can enable a test stub in our configuration file.
This simplifies development across a team. The QAs can do their manual exploratory
testing of new UI changes without needing a database connection. The other devs
who are not working on the Oracle Repository can get on with their work.
This particular piece of design is as much about team leadership and management as
it is about clean code.
Order-OrderLineItem
[Coad] Gives structure to a list of items on an order
Orders crop up everywhere in commercial systems. You’ll routinely see point of sale
receipts, invoices, bills of materials and e-commerce customer orders.
Each one has the same basic pattern.
There is a line item describing the single thing that has been ordered. Typically, it
knows about some combination of values: quantity, part code, description, price, tax,
location. This, plus suitable behaviours, go into the OrderLineItem object.
As orders often have more than one line item, we aggregate these into an Order
object, describing the total order. That often knows about the time it was placed,
delivery and billing details, related accounts. Typical behaviours on Order are display,
takePayment and place, which moves the order onto fulfilment.
Order-OrderLineItem is simply a specific use case for the more general Aggregate
object idea. But it’s so common it’s worth pointing out.
I first saw it in the excellent [Coad] book, which lists a toolbox of common business
processes and patterns that model them.
Examples: Point of Sale receipts, invoices, bills of materials
Request-Service-Response
[Own] Describes a flow to get a response from a service
Design Patterns 159
1 class FindUserByIdRequest {
2 private final String userId ;
3
4 public FindUserByIdRequest( String userId ) {
5 this.userId = userId;
6 }
7 }
So far, this hides away the userId we extracted. Next up, we need to find this user
from our repository. We can do this because we’re entirely inside the inner hexagon
now - the pure domain model. Our UserRepository lives there:
1 interface UserRepository {
2 User findUserById( String userId );
3 }
4
5 class FindUserByIdRequest {
6 private final String userId ;
7
8 public FindUserByIdRequest( String userId ) {
9 this.userId = userId;
10 }
11
12 public User execute( UserRepository users ) {
13 return users.findUserById( userId );
Design Patterns 160
14 }
15 }
Anti-Patterns
Design patterns are powerful, but they are easily overused. There are a couple of
anti-patterns to avoid.
Unneeded Flexibility
Don’t over-design objects.
You might add a pattern ‘just in case for future extension’. Perhaps our simple User
object could sprout an Observer pattern, allowing future code to listen for changes
to the user name.
Let’s just take that out, shall we?
Leave the future to itself. Our crystal balls aren’t good enough. We can’t know what
code will be best in the future.
Even if the requirement lands, we often find that when it does, things have moved
on. The assumptions we made in our original guess at future code were wrong. The
code is now cruft that we have to write a workaround for.
Just write the code for today. Let tomorrow take care of itself. We will refactor as
required.
Mechanism Madness
Don’t over-emphasise how something works.
We’re going back to basics here. Good design highlights what something gives you,
rather than what it is made from.
Rather than copy-paste the patterns directly, blend them in.
If our User object did need an Observer pattern for name changes, let’s not call it
that. Let’s create an interface called NameChanged rather than Observer.
Patterns are meant to be used blended in like this. Otherwise, they would simply be
a library of fixed code. Then they would lose their value.
OOP Mistakes - OOP oops!
So far, we’ve covered ways to use OOP well.
We’ve seen a lot of techniques that make code readable, easy to test and compact.
Remember how easy it was to combine Repository with Cache with Decorator and
get a pluggable speed boost?
It turns out that not everybody agrees that OOP is a good thing.
In part, of course, they are right. Plenty of things just are not suited to OOP. I
can think of simple batch scripts, build scripts and Terraform scripts that set up
infrastructure. We also have ‘Serverless Designs’, which use many small functions
to do their work. OOP often gets in the way there.
Another driver of non-OOP designs has been processing power limitations.
For decades, CPUs followed Moore’s Law. Computing power doubled every two
years. But then it hit a physical limit. CPUs had as many transistors on them as
could be fitted. “I cannae change the laws o’ physics!” said Scottie in Star Trek; So
it is with photolithography. A transistor can be made only so small, but no smaller.
Then physics fights back.
To get around this, CPUs created multiple cores - multiple copies of a CPU design on
the same chip.
This impacted software design. Suddenly, we needed parallel processing rather than
sequential. The kinds of state that lived inside one object of OOP was not accessible to
other CPU cores. OOP became less useful as a model of communicating state across
CPU cores. Functional Programming - “stateless” programming - is useful here.
However, none of that was what gave OOP a bad name. What gave it a bad name
was OOP done wrong.
So, what are the common mistakes?
OOP Mistakes - OOP oops! 163
1 class User {
2 private String name ;
3
4 // This is painful to type ...
5 public String getName() {
6 return name ;
7 }
8
9 // ... so is this
10 public void setName( String name ) {
11 this.name = name;
12 }
13 }
14
15 class UserGreeter {
16
17 // I swear fairies and kittens are dying right now
18 public void greet( User u ) {
19 System.out.prinltn( u.getName() );
20 }
21 }
This caught on with developers who had understood procedural programming but
hadn’t yet learned OO. They hadn’t learned about objects exposing behaviour and
hiding secrets.
When you think in that way, every problem looks like getters-and-setters. Object
secrets are made un-secret. Code that should be a method on the User object now
appears in some redundant “class” UserGreeter. It’s not really a class, because it
doesn’t really have any real secret. It has stolen a secret from User.
It’s just procedural programming, plain and simple.
Procedural designs simply do not gain the benefits of OOP. But they do have the
words ‘class’ and ‘private’ in them. To the uninformed, they look like an OO design.
But they are not.
When people criticise OOP for not delivering on its promises - but base it on code
like this - it is obvious where the fault lies: This one is on them.
If you don’t even realise your code is not OOP, then don’t criticise OOP for your
code!
Broken Inheritance
The darling of the early 1990s, inheritance was going to be magic! You whacked a
few classes together with inheritance and Boom! Your code was done.
But it wasn’t done. Instead, it was just a mess.
There were several failings with inheritance. They are the ones that appear in “OOP
is dead” blog posts these days.
Let’s learn from the classics of that particular genre.
Here’s how the problem unfolds. We start with an Animal base class with methods
eat() and makeNoise(). We add a subclass of Dog. Then we add a subclass of Bird
adding a fly() method. Then two subclasses of Bird, traditionally Sparrow and Dodo.
The Dodo cannot fly, so we maybe throw an exception or do nothing in Dodo.fly().
The whole mess looks like this:
31 }
32 }
33
34
35 class Dodo extends Bird {
36 void eat() {
37 System.out.println("Nothing, extinct");
38 }
39
40 void makeNoise() {
41 System.out.println("No sound, extinct");
42 }
43
44 void fly() {
45 throw new UnsupportedOperationException("error: cannot fly");
46 }
47 }
There are a couple of things wrong with this, which the calling code would reveal
quickly. Oddly enough, the “OO is dead” examples never include unit tests or calling
code, do they? Funny, that …
The key issue is the broken contract of Bird.fly() when we inherit that method in
Dodo.
A Dodo cannot fly, so the expected contract “make it fly” cannot be met.
That’s a misuse of inheritance - technically a misuse of a static type to incorrectly
model a behaviour that does not exist. It fails LSP. We know we should not use
inheritance when LSP is going to be broken. It is not a good fit.
That’s a problem, yet the bigger point is usually missed entirely in these articles. Why
does Bird even derive from Animal? They don’t have many use cases in common.
Recall how we do this right. We start outside-in and design behaviours we want our
objects to have. If we are going to treat our objects polymorphically, then we make
sure that all the objects can respond to the same methods.
How about in this case? No way is it going to work like that.
OOP Mistakes - OOP oops! 167
Think about what the calling code needs for polymorphism. It wants some Base
classes to use. If it uses Animal, then Bird does not exist - the calling code will never
see Bird, just Animal. You cannot call fly() on an Animal. If it uses Bird, then Animal
does not need to exist. Bird simply has three methods eat(), makeNoise() and fly().
You could simply put those into a Bird interface, and the calling code has no clue
whether an Animal is involved or not.
Neither Animal nor Bird makes a good base class for this. Yet here we are, proudly
showing an inheritance tree of useless base classes, in an article saying OOP doesn’t
work. Good job. Loving your work …
The fundamental problem missed is that you cannot call any of this in a Tell-Don’t-
Ask polymorphic style. You have to know whether you have only an Animal, or a
Bird, or a Dodo - and then remember never to call fly().
The entire advantage of polymorphism in OOP has been missed
Inheritance is only useful when classes are LSP substitutable in calling code. If that
calling code has to grub about with instanceof to work out what class it is dealing
with, you haven’t done OOP. It’s no better than using a bunch of if statements and
a big procedure.
The usual hack is to store things as Animals and use instanceof to work out if they
are the different abstraction of Bird. Ugh. No thanks.
So, how might we sort this mess out?
We simply accept that we jumped the wrong way with this abstraction.
Back to basics time. The object behaviours that we are trying to model are eat(),
makeNoise() and fly(). Different kinds of Animal have these behaviours in different
combinations.
Lets start by creating interfaces for each behaviour.
OOP Mistakes - OOP oops! 168
1 interface Flying {
2 void fly();
3 }
4
5 interface Feedable {
6 void eat();
7 }
8
9 interface Audible {
10 void makeNoise();
11 }
We’ve fixed the kludgy exception for Dodo.fly() by not inheriting the behaviour that
Dodo does not have in the first place. Avoiding problems: always smart.
We can do the same thing with Dog and Sparrow. Note that Animal and Bird simply
vanish in this scheme, because they were the wrong abstraction.
This improves our calling code somewhat. If we pass Dodo to a method that wants
a Feedable thing, it will work. It is LSP substitutable for that. Dodo will never bomb
out on fly() because it does not have that method in the first place.
However, our animals are still not very polymorphic. This might be fine. It means
the calling code needs to know which interfaces it needs.
How could we make a fully polymorphic Animal, that we could tell-don’t-ask to do
the right thing?
OOP Mistakes - OOP oops! 169
1 interface Behaviour {
2 void do();
3 }
4
5 class Animal {
6 private final List<Behaviour> behaviours = new ArrayList<>();
7
8 public void add(Behaviour b) {
9 behaviours.add(b);
10 }
11
12 public void behave() {
13 behaviours.forEach(Behaviour::do);
14 }
15 }
Then we configure each Animal with the behaviours we want - using composition,
not inheritance:
OOP Mistakes - OOP oops! 171
1 class Animals {
2 private List<Animal> animals = new ArrayList<>();
3
4 void add(Animal a) {
5 animals.add(a);
6 }
7
8 void behave() {
9 animals.forEach(Animal::behave);
10 }
11 }
We can add all our animals and make them all do their thing:
We now have a set of pluggable animal behaviours that benefit from OO polymor-
phism. We have used objects to encapsulate data, hide secrets and separate concerns.
The general idea here is to prefer composition over inheritance.
OOP Mistakes - OOP oops! 172
Our original problem was that inheritance and static types had been used to model
dynamic behaviours. That cannot work. We found the correct way to model this. It
is not how the common example does it.
I think it’s time to declare “OO is dead” articles dead.
As dead as our Dodo.
1 class Graphics {
2 // Coordinates (0,0) top left of screen
3 void line( int x1, int y1, int x2, int y2 );
4 }
5
6 interface Shape {
7 void draw(Graphics g);
8 }
9
10 class Rectangle implements Shape {
OOP Mistakes - OOP oops! 173
Huzzah! We’re done. That Square class is all you need to draw a square, provided
OOP Mistakes - OOP oops! 174
you call setSize() with the length of each side you want.
That’s the problem, though. In this case, nothing particularly bad happens. But we
have laid a trap for later. ‘Future Us’ isn’t going to be best pleased.
Suppose we needed an extra method that had a different behaviour for a Square
as compared to a Rectangle. Let’s say it was to power a dashboard, interested in
counting how many squares and rectangles we had:
1 interface Shape {
2 void draw( Graphics g );
3 void report( Dashboard d );
4 }
5
6 class Rectangle implements Shape {
7 // ... other code as above ...
8
9 void report( Dashboard d ) {
10 d.reportRectangle();
11 }
12 }
13
14 class Square extends Rectangle {
15 // ... other code as above ...
16
17 void report( Dashboard d ) {
18 d.reportSquare();
19 }
20 }
This would work well - until you had a Rectangle object and called setWidth(5) and
setHeight(5) on it.
At this point, we have an object of type Rectangle. We have set its width and height
to be the same. So now, it will draw as a square. It IS-A square, mathematically. It
should report to the dashboard that it is a Square.
But it isn’t and it won’t. It IS-NOT-A Square in Java. It is a Rectangle with equal
sides. In Java, that is not the same thing.
OOP Mistakes - OOP oops! 175
It boils down to this idea that in Java, a class or interface represents a fixed type. The
kind of class of an object is set when you create it. It cannot change according to
data values of fields inside it.
In this example, mathematically, a square is a rectangle. That is in no doubt. But Java
classes are unable to represent that change of type dynamically. That is not how Java
was designed. It is not the model of OOP that Java supports. Other languages may
do this. But not Java.
You have to work with what you have. Respect the limitations.
Inheriting implementation
The last of our three ugly ducklings is using inheritance just to pull in some extra
code, without thought to IS-A relationships, Liskov LSP or anything in the domain:
1 class MySqlDatabase {
2 void insertSql(...) {...}
3
4 // ... other database methods ...
5 }
6 class User extends MySqlDatabase {
7 private String name ;
8
9 void setName( String name ) {
10 this.name = name;
11
12 insertSql("USER_TABLE", "NAME", name);
13 }
14 }
OOP Mistakes - OOP oops! 176
1 class Counter {
2 private int total;
3
4 void oneMore() {
5 total++;
6 }
7
8 void display(Display d) {
9 d.print( "Total so far is: ");
10 d.println( total );
11 }
12 }
We can create a new Counter, call oneMore() as often as we like, and display the total,
using some Display object.
OOP Mistakes - OOP oops! 177
The criticism comes from concurrent systems, like multi-threaded Java Servlets or
multi-core CPUs.
If you create one Counter, then call oneMore() from different threads you get strange
failures. What happens is that sometimes Thread A is partway through adding one
to the total, when Thread B comes along, takes the partial result it finds, adds one to
that and updates total.
The value gets corrupted. That’s not the best news to get that day.
OOP critics treat this as meaning that OOP cannot be used in concurrent systems.
Or it is difficult to use. Or that Functional Programming (FP) is the only thing that
makes sense.
The truth is, there are plenty of techniques that allow safe, simple use of objects in
concurrent systems.
The simplest is to make objects immutable. If you do not share state between threads,
you have no problem.
For the cases where you do need to share that state, in most code, that state lives in
a database. A Repository pattern object will access that database. The database will
manage concurrent access for you. Job done.
Where you have no database, Java provides primitives (Thread, synchronized) and
some powerful high level objects (java.util.concurrent package) to simplify this.
Frameworks like Akka provide Actor objects, which is a way to treat each object
as its own, concurrent thing.
It’s got nothing to do with OOP really. FP avoids the problem by pushing state out
to the edges of the system. But we can do that anyway, using immutable objects, if
we so choose. Or add synchronisation. Or use the objects with built-in concurrency.
If you are going to write methods like that, it really does not matter a fig how good
your OO design is. This was a real “What were they thinking?” moment.
The reality is that you can get paid to write very low-quality code. That code will get
reviewed by peers who also get paid to write very low quality code. That decision
will be rubber-stamped by non-technical management who do not know what code
quality is, nor care, so long as the deadline was met.
Hopefully, we can agree that we can do better than this.
We can, can’t we?
Data Structures and Pure
Functions
We’ve looked at how object oriented designs are driven outside-in by behaviours
with hidden secrets. We’ve also covered how getters and setters are the ultimate evil.
Well, ok, maybe I didn’t quite put it like that. But you know what I mean.
This chapter is about setting the balance straight.
Sometimes, the perfect solution to a problem is a pure data structure. Yes, a full-on,
old-skool class that has only getters and setters.
Let’s look at where they add value and why.
System Boundaries
We’ve seen how we can make our application talk to external systems easily by using
Dependency Inversion. This provides a local interface that represents our view of that
external system.
But we still need to talk to that system, over the network.
It should be obvious³ that we don’t actually send an object over the network. What
would that even mean?
What we have to do is “flatten it out” in a process we call serialisation.
This is where we take all the private data inside that object and write it out, together
with some extra metadata that says what the Class was.
This flattened out data is pure data. It’s not an object. It can’t have methods when it
is just ones and zeroes over a network.
³Ahem. I say obvious. You should know that the first time I used a fax machine, I photocopied the document before
I sent it. I wanted to have my own copy to keep…
Data Structures and Pure Functions 180
The success of our reporting tools is becoming its own worst enemy. For every new
report we deliver, we get asked to create two more.
If we choose to model this as an object, it gets unwieldy pretty quickly. It sprouts
hundreds of pretty random-looking methods and reaches out to all kinds of other
objects that it only wants the private data from.
We can use some patterns to help us; CollectingParameter, Strategy or even Observer
can come into play. But it tends to obscure what’s going on.
We really only care about the data for a report. The data is fixed. It comprises payment
details, product data and categories. All of this stays the same. It is the behaviours -
the reports themselves - that are in flux.
The sensible approach is to create a set of pure data classes, resplendent with getters
and setters. I might even go as far as making them have public data fields and no
getters, setters nor methods at all. I think this is honest. But so we don’t break
SonarQube (and freak out our team) we can stick to old-skool JavaBeans, with get/set
pairs. That’s fine in this context.
The reports can then be simple, separate classes that process these data objects,
spitting out a report at the end.
An approach I like sticks with an OOP domain model, and some of those domain
objects can be asked to produce a ReportingModel object. This is the get/set data
structure for use with the reporting package.
Both approaches are useful, given the trade-off between what is more likely to change
- data or behaviours.
What’s important is to understand which one you are using and why. Then do it
well.
Putting It All Together
Well, that’s it. That’s all I use day to day for building out object oriented applications.
No step-by-step plans
The first thing to notice is that there are no step by step plans here. I often wish there
were.
Novices often want to learn the steps needed to build different kinds of software.
They get frustrated to learn there aren’t any.
What we have instead is a Toolbelt of Techniques.
It’s like being a builder. You have hammers, saws and specialist tools. You have
wood, metal and other materials. You know the principles of measuring, joining,
load-bearing and more. It is only when you come to build a specific house that you
work out which ones to use, in which order.
The house gets built from thousands of smaller jobs. We cut a hole, fit a door frame,
screw a hinge. We never “build a house” with the “build a house tool”.
Software is just the same. You don’t “write a word processor”. You represent text,
save it to storage, fetch it from storage, layout a paragraph, draw a font to a display.
We create objects to do the work and get them to collaborate. Over time, we build
out the objects we need to create the features we want.
The trick is to approach each problem with confidence. As soon as we break down
to a small enough problem, one or more of our tools will solve it.
Getting Started
The way I get started is by taking an Agile, iterative approach.
Putting It All Together 184
Start with a simple user story - a single, small stripe of functionality that a user would
see. That might be “Add a new Customer to our system”.
From that, we can imagine what kinds of objects we need. A Customer. A Customer-
Repository. Some implementation of that for storage, depending on how we have
decided to store things. Some kind of interface to the Customer - possibly a Web
Service REST API to power a JavaScript front end.
We can begin to write tests to get small pieces of this code up and running. Refactor
it into shape. Add pieces as “stripes” through the whole system. Consider our
domain/external system splits. Think about making the domain ideas clear, hiding
mechanisms.
Start small. Scaffold with tests. Build out.
Bad code is like borrowing fifteen grand from the bank to blow it all on a party. It’s
fun for the night, but pretty grim afterwards. You’re paying it all back with nothing
to show for it.
If deadlines force compromises on us, then our limited foresight also frustrates us.
Many times, my code gets cleaned up in a later change. I wrote it as good as I could at
the time. But since then, I have learned more about the subject area I am modelling.
I know more about what the code needs to express in its domain model. I couldn’t
have written any better earlier than this.
I’ll always clean code up as I learn more - when it is safe to do so. By that, I mean
‘when it doesn’t impact anybody else’. A case in point is when you learn more about
code that is already in use by others. Any change affects those users. You should not
simply push changes out without letting them know and having a plan.
I also like to ‘Boy Scout’ code, as it’s called. Leave code a little cleaner than when you
got hold of it. Small, incremental improvements in clarity build up over time. When
you’re working on a feature, it’s usually good to clean up code around it. It makes
adding your feature easier and improves that code for the future.
First, accept it. Don’t panic or feel guilty or stupid. We’re just stuck. In a short time,
we won’t be. That’s just how this works.
Create some space. Do something else. I’ll often focus my brain on something I can
do. Perhaps another piece of work. Perhaps I’ll write something that I know about.
Or stare out of the window. Or read or watch a YouTube.
Space gives your subconscious mind a chance to work in the background. Don’t ask
me how, but many times, that’s all it takes. Literally sleeping on a problem works
wonders.
Other ideas that work are to ask for help. Explain the problem to somebody else and
what you are thinking about. Many times, this activity itself gives you the answer.
This even has a name - ‘rubber ducking’. You might as well have talked to a rubber
duck. Hence the one on the front cover - feel free to use it! It is the process of
organising your thoughts out loud that brings the answer.
Experimentation is another good approach. Try a little spike. I often do this inside
a unit test harness, just so it is easy to run. That often shows you ‘how a solution
might feel’.
Always be ready to simplify an idea for now if it is just too hard. Get the simple one
working first, then add extra features later. That stops ‘analysis paralysis’, where you
can’t start because the job is too big.
Further Reading
For further reading, here are my favourite software design books
My Blog
My writings on software. You’ll find tutorials and stories from my experience.
https://www.viewfromthecodeface.com
My Quora Space
Join 27,000+ others to see my answers to whatever random questions about software
get asked on Quora!
https://www.quora.com/q/alanmelloronsoftware
LinkedIn
Me: https://www.linkedin.com/in/alan-mellor-15177927/
Further Reading 190
LeanPub page
Where the book was originally written. It’s an excellent platform, I can recommend it.
You should definitely try writing a short book - leanpub takes care of the practicalities
https://leanpub.com/javaoopdoneright
Cheat Sheet
My mental model of how I work with OOP code:
Behaviours First
Design in this order:
• public methods
• supporting private fields
• constructor
• supporting private methods
• getters where sensible
• setters (rarely)
Design Principles
• DRY - Don’t Repeat Yourself. Eliminate duplication in code and tests
• KISS - Keep It Simple
• Tell Don’t Ask - Do work inside the object. Do not drag out data.
• YAGNI You Ain’t Gonna Need It (YAYA unless yes, you are)
• SOLID - Single responsibility. Invert/Inject dependencies. Swappable.
• Prefer Composition over Inheritance - nearly always
• Prefer interface/implements over Inheritance trees
• Test First - drive out the design with feedback, regression is a bonus
• FIRST tests - Fast, Independent, Repeatable, Self Validating, Timely
• Collaboration - which object should be doing this job?
• Hexagonal Architecture - Decouple external systems from the domain code
• Domain over Mechanism - Emphasis what is being done; hide how
Cheat Sheet 192
Clean Code
• Say what you mean, mean what you say
• Method names describe outcomes
• Variable names describe contents
Thanks
BJSS Limited and Manchester Digital for opportunities to use and teach this stuff.
Steven Taylor - great suggestions on the first draft (despite more work!)
My Mum. That ZX81 didn’t buy itself. You made my career happen.
Stephanie, Katy, Jake. Who would have guessed a 1980s computer nerd would end
up surrounded by amazing humans he can call ‘family’. What a privilege.
Katy for front cover art https://www.redbubble.com/people/kath-ryn/shop