The Trouble With Objects

A perennial debate that I enjoy wading into is the classic “Should it be kick(ball) or ball.kick()?”

This seems to reveal a fundamental dichotomy in our shared understanding of Object-Oriented Programming.

It’s a trick question, of course. If the effect is the same – the displacement of the ball – then kick(ball) and ball.kick() mean exactly the same thing. But the debate rages around who is doing the kicking and who is being kicked.

Many programmers quite naturally assign agency to objects, and object (pun intended) to the ball kicking itself. Balls don’t kick themselves! They will often counter with “It should be player.kick(ball)“.

But this can lead us down the rabbit hole to distinctly non-OO code. Taking an example from a Codemanship training course about an online CD warehouse, the same question comes about whether it should be cd.buy() or warehouse.buy(cd).

Again, the protestation is that “CDs don’t buy themselves!” I can completely understand why students might think this, having had it drummed into us that objects do work. (Although why nobody ever objects that “Warehouses don’t buy CDs!” is one of life’s little mysteries.)

I’m the first person to say that object design should start with the work. Then we figure out what data is required to do that work. Put the data with the work. And, hey presto, you got an object. Assign one job to each object, and get them talking to each other to coordinate bigger jobs, and – hey presto! – you got OOP.

(The art of OOP is really in deciding where to put the work, and that’s what this debate is essentially all about.)

But warehouse.buy(cd) – in the training exercise we do – can lead us into deep water regarding encapsulation. The are told that the effect of buying a CD is that the stock count of that CD goes down, and that the customer’s credit card is charged the price of that CD.

So our test looks a bit like this:

public class WarehouseTest {
@Test
public void buyCd(){
CD cd = new CD(10, 9.99);
CreditCard card = mock(CreditCard.class);
Warehouse warehouse = new Warehouse();
warehouse.buy(cd, 1, card);
assertEquals(9, cd.getStock());
verify(card).charge(9.99);
}
}
view raw WarehouseTest.java hosted with ❤ by GitHub

The implementation that passes this test suffers from a distinct case of Feature Envy between Warehouse and CD, because buying a CD requires access to a CD’s stock and price.

public class Warehouse {
public void buy(CD cd, int quantity, CreditCard card) {
card.charge(cd.getPrice() * quantity);
cd.setStock(cd.getStock() quantity);
}
}
view raw Warehouse.java hosted with ❤ by GitHub

When we refactor this code to eliminate the Feature Envy (i.e., to encapsulate the work)…

…we end up with a CD that – shock, horror! – buys itself!

public class CD {
private int stock;
private final double price;
public CD(int stock, double price) {
this.stock = stock;
this.price = price;
}
public int getStock() {
return stock;
}
public void buy(int quantity, CreditCard card) {
card.charge(price * quantity);
stock = stock quantity;
}
}
view raw CD.java hosted with ❤ by GitHub

This refactoring is typically followed by “But… but….”. Placing this behaviour inside the CD class conflicts with our mental model of the world. CD’s don’t buy themselves!

And yet we encounter objects apparently doing things to themselves in OO libraries all the time: lists that filter themselves, database connections that open themselves, files that read themselves.

And that’s what’s meant by “object-oriented”. The CD is the thing being bought. It’s the object of the buy action. In OOP, we put the object first, followed by the action. Read cd.buy() not as “the CD buys” but as “buy this CD”.

Millions of people around the world read OO code the wrong way around. The ones who tend to grock that it’s object-oriented are those of us who’ve had to approximate OOP in non-OO languages – particularly C. (Check out previous posts about encapsulating in C and applying SOLID principles to C code.)

Without the benefit of an OO syntax, we resort to defining all the functions that apply to a type of data structure in one place, and the first parameter to every function is a pointer to an instance of that structure, usually named this.

Then we might hide the data definition of the structure – just declaring its type in our .h file – in the same .c implementation file, so only those functions can access the data. Then we might define a table of virtual functions – a “v-table” – that can be applied to that data structure, and attach the data structure to them so that clients can invoke functions on instances of the data structure. Is this all starting to sound familiar?

The set of operations defined by a class are the operations that can be applied to that object.

In reality, objects don’t do work. The CPU does. The object identifies the thing – the record in memory – to or with which the work is to be done. That’s literally how object-oriented programming works. cd.buy() means “apply the buy() function to this CD”. list.filter() means “filter this list”. file.read() means “read this file”.

The idea of objects doing work, and passing messages to each other to coordinate larger pieces of work – “collaborations” – is a metaphor. And it works just fine once you let go of the idea that balls don’t kick themselves.

But words are powerful things, and in programming especially, they can get tangled in our mental models of how the problem domain works in the real world. In the real world, only life has agency (well, maybe). Most things are acted upon. So we have a natural tendency to separate agency from data, and this leads us to oodles and oodles of Feature Envy.

I learned to read object-oriented code as “do that to this” a long time ago, and it therefore has no conflict with my mental model of the world. The CD isn’t buying. The CD is bought.

object.action().

UPDATE

I’ve been very much enjoying the ensuing furore that suggesting ball.kick() means “kick the ball” inevitably starts. The fun part is reading the “better” designs folk come up with to avoid accepting that.

player.kick(ball) is one of the most popular. Note now that we have two classes instead of one to achieve the same outcome.

Likewise, cd.buy() seems to have offended the design senses of some. It should be cart.add(cd), they say. Again, we now have two classes involved, and also the CD didn’t actually get bought yet. And it also kind of proves my point, because the CD is being add to the cart.

On a more general note, when students go down the warehouse.buy(cd) route, I ask them why the warehouse needs to be involved if we know which CD we’re buying.

object.action() tends to simplify things.

S.O.L.I.D. Visual Basic?

In my journey back through the decades, investigating how the software design principles I teach on Codemanship courses could have been applied in programming languages of the day, I’ve visited 2009 (Ruby), 1989 (C), 1979 (Fortran 77) and 1969 (Simula 67), as well as a shiny new language from the present day (Kotlin) to bring us up to date.

For 1999, I’ve thought long and hard about what language to choose, and eventually settled on arguably the most popular at the time: Visual Basic. In that year, VB was in it’s last incarnation before the introduction of .NET. This was the height of the Microsoft COM era. Visual Basic 6 had some elements of object orientation, which were – in reality – built on COM. That is, VB6 “classes” were modules that had private implementations hidden behind public COM interfaces (a class with no fields at all still took up 96 bytes because… COM).

Here’s the carpet quote example done using VB6 class modules.

Option Explicit
Public Function Quote(ByRef room As Room, ByRef carpet As Carpet) As Double
Dim area As Double
area = room.Length * room.Length
If carpet.RoundUp Then
area = Ceiling(area)
End If
Quote = carpet.PricePerSqMtr * area
End Function

view raw
CarpetQuote.cls
hosted with ❤ by GitHub

Room and Carpet are simple data classes, leading to the inevitable Feature Envy in CarpetQuote.

Also, the Quote() function does two distinct jobs: calculating the area of carpet required for a room and calculating the price of that fitted carpet. It knows too much.

Let’s break up the work…

Option Explicit
Public Function Quote(ByRef room As Room, ByRef carpet As Carpet) As Double
Quote = Price(carpet, Area(room))
End Function
Public Function Area(ByRef room As Room)
Area = room.Width * room.Length
End Function
Public Function Price(ByRef carpet As Carpet, ByVal area As Double)
If carpet.RoundUp Then
area = Ceiling(area)
End If
Price = carpet.PricePerSqMtr * area
End Function

view raw
CarpetQuote.cls
hosted with ❤ by GitHub

Now let’s move those functions to where they belong.

Option Explicit
Public Function Quote(ByRef room As Room, ByRef carpet As Carpet) As Double
Quote = carpet.Price(room.Area())
End Function

view raw
CarpetQuote.cls
hosted with ❤ by GitHub

Plus one for encapsulation, right? Well, not quite. Let’s take a look inside Room and Carpet.

Option Explicit
Private mWidth As Double
Private mLength As Double
Public Property Let Width(ByVal value As Double)
mWidth = value
End Property
Public Property Get Width() As Double
Width = mWidth
End Property
Public Property Let Length(ByVal value As Double)
mLength = value
End Property
Public Property Get Length() As Double
Length = mLength
End Property
Public Function Area() As Double
Area = Width * Length
End Function

view raw
Room.cls
hosted with ❤ by GitHub

Option Explicit
Private mPricePerSqMtr As Double
Private mRoundUp As Boolean
Public Property Let PricePerSqMtr(ByVal value As Double)
mPricePerSqMtr = value
End Property
Public Property Get PricePerSqMtr() As Double
PricePerSqMtr = mPricePerSqMtr
End Property
Public Property Let RoundUp(ByVal value As Boolean)
mRoundUp = value
End Property
Public Property Get RoundUp() As Boolean
RoundUp = mRoundUp
End Property
Public Function Price(ByVal Area As Double)
If RoundUp Then
Area = Ceiling(Area)
End If
Price = PricePerSqMtr * Area
End Function

view raw
Carpet.cls
hosted with ❤ by GitHub

I’d like to hide the data, and get rid of these property procedures. In a language like Java, I could pass the data in to a constructor and keep them private. But VB6 doesn’t support constructors, because COM components don’t support constructors. So I have to instantiate each class and then set their data from outside.

Private Sub btnQuote_Click()
Dim quote As New CarpetQuote
Dim room As New Room
Dim carpet As New Carpet
room.Width = CDbl(txtWidth.Text)
room.Length = CDbl(txtLength.Text)
carpet.PricePerSqMtr = CDbl(txtPricePerSqMtr.Text)
carpet.RoundUp = chkRoundUp.value
lblPrice.Caption = "£" & quote.quote(room, carpet)
End Sub

view raw
frmCarpetQuote.frm
hosted with ❤ by GitHub

Is there a way to approximate a constructor in VB6 and keep the data hidden? Well, not really. In C, we could use a function in the same module as the data is defined to initialie a new Room or Carpet. VB6 doesn’t support static methods on classes, so a function to create an object could only be defined in a separate module, so the ability to set field values would have to be exposed. We could get rid of our getters, but not our setters.

Public Function CreateRoom(ByVal Width As Double, ByVal Length As Double) As Room
Dim room As New Room
room.Width = Width
room.Length = Length
Set CreateRoom = room
End Function

view raw
RoomFactor.bas
hosted with ❤ by GitHub

Option Explicit
Private mWidth As Double
Private mLength As Double
Public Property Let Width(ByVal value As Double)
mWidth = value
End Property
Public Property Let Length(ByVal value As Double)
mLength = value
End Property
Public Function Area() As Double
Area = mWidth * mLength
End Function

view raw
Room.cls
hosted with ❤ by GitHub

Then our client can just use the CreateRoom() function to instantiate Room. It’s a little better, but – as with many languages – encapsulation can only really be achieved through discipline in writing client code, not actual language features. (This is just as true in, say, Python as in VB6.)

Now, how about swappability? Does VB6 support easy polymorphism? Remember that, in VB6, “classes” are really modules hidden behind COM interfaces. For sure, we can multiple classes that implement the same COM interface. So, if we wanted to have two different ways of calculating the area of a room, we could define a Room interface (as a .cls file)

Option Explicit
Public Function Area() As Double
End Function

view raw
Room.cls
hosted with ❤ by GitHub

…and then have different implementations of Room that know about the details.

Option Explicit
Implements Room
Private mWidth As Double
Private mLength As Double
Public Property Let Width(ByVal value As Double)
mWidth = value
End Property
Public Property Let Length(ByVal value As Double)
mLength = value
End Property
Public Function Room_Area() As Double
Room_Area = mWidth * mLength
End Function

view raw
RectangularRoom.cls
hosted with ❤ by GitHub

Option Explicit
Implements Room
Private mRadius As Double
Public Property Let Radius(ByVal value As Double)
mRadius = value
End Property
Public Function Room_Area() As Double
Room_Area = (mRadius * 2) * (mRadius * 2)
End Function

view raw
CircularRoom.cls
hosted with ❤ by GitHub

CarpetQuote works with either implementation of room, and only sees the Room COM interface with the Area() method. This is how we hide details/data in Visual Basic 6 – using COM interfaces.

So that’s a tick for swappability, and a kind of tick – after a fashion – to encapsulation. Finally, can we make a VB6 class present client-specific interfaces?

Imagine we have a client that just needs to know how many flights of stairs carpet fitters will have to climb/descend to reach a room, so we can calculate a premium. Can we add another interface to an implementation of Room?

Option Explicit
Public Function FlightsOfStairs() As Integer
End Function

view raw
FloorLevel.cls
hosted with ❤ by GitHub

A VB6 class can implement more than one COM interface.

Option Explicit
Implements Room
Implements FloorLevel
Private mWidth As Double
Private mLength As Double
Private mLevel As String
Public Property Let Width(ByVal value As Double)
mWidth = value
End Property
Public Property Let Length(ByVal value As Double)
mLength = value
End Property
Public Property Let Level(ByVal value As String)
mLevel = value
End Property
Public Function Room_Area() As Double
Room_Area = mWidth * mLength
End Function
Public Function FloorLevel_FlightsOfStairs() As Integer
Select Case mLevel
Case "G"
FloorLevel_FlightsOfStairs = 0
Case "B"
FloorLevel_FlightsOfStairs = 1
Case Else
FloorLevel_FlightsOfStairs = CInt(mLevel)
End Select
End Function

view raw
RectangularRoom.cls
hosted with ❤ by GitHub

Our client binds only to the FloorLevel interface.

Private Function CalculateStairsPremium(ByRef Level As FloorLevel) As Double
CalculateStairsPremium = Abs(Level.FlightsOfStairs) * 50#
End Function

So, through the use of COM interfaces, that’s a tick for Interface Segregation.

Which means that – perhaps surprisingly – VB6 is 100% S.O.L.I.D.  Who’d have thunk?

 

You can view the complete source code at https://github.com/jasongorman/SOLID-VB6

Refactoring to Functions

While I’ve been porting the Codemanship Software Design Principles code examples to JavaScript – in both OO and FP styles – I’ve been thinking a lot about the relationship between those two programming styles.

Possibly the best way to illustrate might be to refactor an object oriented code example into a functional example that’s logically equivalent. This might also serve to illustrate how we might move from one style to the other in a disciplined way, without breaking the code.

This is the simple class I’m going to start with.

function BankAccount() {
this.balance = 0;
this.credit = function (amount) {
this.balance += amount
}
this.debit = function (amount) {
if (amount > this.balance) {
throw "Insufficient funds error";
}
this.balance -= amount;
}
}

view raw
bank_account.js
hosted with ❤ by GitHub

And these are its tests.

const BankAccount = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = new BankAccount();
account.credit(50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = new BankAccount();
account.credit(50);
account.debit(50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = new BankAccount();
account.credit(50);
expect(() => account.debit(51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

The first refactoring step might be to make each method of the class properly stateless (i.e., they don’t reference any fields).

To achieve this, we’ll have to add a parameter to each method that accepts an instance of BankAccount. Then we replace this with a reference to that parameter. This will work if the BankAccount we pass in is the exact same object this refers to.

function BankAccount() {
this.balance = 0;
this.credit = function (account, amount) {
account.balance += amount
}
this.debit = function (account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error";
}
account.balance -= amount;
}
}

view raw
bank_account.js
hosted with ❤ by GitHub

So, in our tests, we pass in the BankAccount object we were invoking credit() and debit() on.

const BankAccount = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = new BankAccount();
account.credit(account, 50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = new BankAccount();
account.credit(account, 50);
account.debit(account, 50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = new BankAccount();
account.credit(account, 50);
expect(() => account.debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

Now we can pull these instance methods out of BankAccount and turn them into global functions.

function BankAccount() {
this.balance = 0;
}
const credit = function (account, amount) {
account.balance += amount
}
const debit = function (account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error";
}
account.balance -= amount;
}
module.exports = {BankAccount, credit, debit};

view raw
bank_account.js
hosted with ❤ by GitHub

The tests can now invoke them directly.

const {BankAccount, credit, debit} = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
const account = new BankAccount();
credit(account, 50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const account = new BankAccount();
credit(account, 50);
debit(account, 50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const account = new BankAccount();
credit(account, 50);
expect(() => debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

One last piece of business: the BankAccount data object. We can replace it in two steps. First, let’s use a JSON version instead that matches the schema credit() and debit() expected. To make this the smallest change possible (so we don’t have to re-write those functions yet), let’s make them mutable.

const {BankAccount, credit, debit} = require("../../src/liskov_substitution/bank_account");
describe('bank account', () => {
it('credit account', () => {
let account = {balance: 0};
credit(account, 50);
expect(account.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
let account = {balance: 0};
credit(account, 50);
debit(account, 50);
expect(account.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
let account = {balance: 0};
credit(account, 50);
expect(() => debit(account, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

Then we can re-write credit() and debit() to return mutated copies.

const credit = function (account, amount) {
return {account, balance: account.balance + amount};
}
const debit = function (account, amount) {
if (amount > account.balance) {
throw "Insufficient funds error";
}
return {account, balance: account.balance amount};
}

view raw
bank_account.js
hosted with ❤ by GitHub

This will require us to re-write the tests to use the mutated copies.

describe('bank account', () => {
it('credit account', () => {
const credited = credit({balance: 0}, 50);
expect(credited.balance).toBe(50);
})
it('debit account with sufficient funds', () => {
const debited = debit(credit({balance: 0}, 50), 50);
expect(debited.balance).toBe(0);
})
it('debit account with insufficient funds', () => {
const credited = credit({balance: 0}, 50);
expect(() => debit(credited, 51)).toThrow('Insufficient funds error');
})
})

view raw
bank_account.test.js
hosted with ❤ by GitHub

So, there you have it: from OO to FP (well, functional-ish, maybe) for a simple class with no collaborators. In the next post, I’ll refactor some a code example that involves several related classes so we can examine the relationshi between dependency injection and high-order functions.

 

S.O.L.I.D. JavaScript – OO Version

A little while back I wrote a post on the old blog about how we could apply the same design principles – near enough – to functional programming as we might to object oriented programming, using JavaScript examples.

That encouraged a couple of people to get in touch saying “But we don’t do FP in JavaScript!”, and suggesting therefore that – strangely – these principles don’t apply to them. The mind boggles.

But, for completeness, here’s how I might apply S.O.L.I.D. principles to OO JavaScript code. To make things backwards compatible, I’ve not used the class syntax of later versions of JS.

First of all, the big tomale: swappable dependencies (Dependency Inversion).

Consider this snippet of code for a simplistic shopping basket:

function Basket(customer, items){
this.add = function(item) {
items.push(item);
}
this.checkout = function(){
const payments = new PayPalPayments();
return payments.pay(this.total(), customer.creditCard)
}
this.total = function() {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}

view raw
basket.js
hosted with ❤ by GitHub

The problem here is what happens if we want to change the way we process payments? Maybe we don’t want to use PayPal any more, for example. Or what if we don’t want to use a real payment processor in a unit test? In this design, we’d have to change the Basket class. That breaks the Open-Closed Principle of SOLID (classes should be open to extension, but closed for modification.)

If we inject the payment processor, then it becomes easy to swap the implementation for whatever purpose (in this example, to stub the processor for a test.)

function Basket(customer, items){
this.add = function(item) {
items.push(item);
}
this.checkout = function (payments) {
return payments.pay(this.total(), customer.creditCard)
}
this.total = function() {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}
test('should process payment on checkout', () => {
let basket = new Basket({creditCard: '1234'}, []);
basket.add({price: 10.0, quantity: 10});
expect(basket.checkout(new PaymentsStub(true))).toBe(true);
});

view raw
basket.js
hosted with ❤ by GitHub

And there we have it: three fifths of SOLID is about making dependencies swappable – Open-Closed, Liskov Substitution and Dependency Inversion. (or “OLD”, if you like.)

And can we agree classes should have a Single Responsibility? That’s not really an OO principle. The same’s true of functions and modules and microservices and any other discrete unit of executable code.

Finally, the Interface Segregation Principle: classes should present client-specific interfaces. That is, interfaces should only include the methods a client uses. With duck typing, it doesn’t really matter of a class presents methods a client doesn’t use. This is true whether we’re talking about methods of classes, or functions in modules.

It might help to make the code easier to understand of we document protocols by explicitly defining pure abstract classes that describe what methods any implementation would need to support. But it’s not necessary for our code to compile and run.

But, as with the functional examples I used, there is a case for saying that modules shouldn’t reference implementations they’re not using. Let’s suppose that after I refactored my Basket to use dependency injection, I forgot to remove the import for PayPalPayments:

const paypalPayments = require('./paypalpayments.js');
function Basket(customer, items){
this.add = function(item) {
items.push(item);
}
this.checkout = function (payments) {
return payments.pay(this.total(), customer.creditCard)
}
this.total = function() {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}

view raw
basket.js
hosted with ❤ by GitHub

It’s important to remember to clean up your imports regularly to avoid situations where changes to things we don’t use could break our code.

So, the sum up: the same principles apply in JavaScript regardless of whether you’re doing FP or OOP.

No excuses!