Programming paradigms: comparing functional and object-orientated styles in JavaScript
Posted Jul 4, 2017 | 7 min. (1405 words)Recently a friend of mine who is studying programming (in-particular, programming paradigms,) came to me with a question regarding implementing a game of TicTacToe in JavaScript for an online course. While the question related to some tricky behavior involving some nested arrays, it made me think about how I would best approach structuring a simple game myself without the various constrictions and methodologies I use within the frameworks I typically use.
Raygun lets you detect and diagnose errors and performance issues in your codebase with ease
In this post, I experiment implementing TicTacToe in functional and object-orientated styles and compare the difficulties and benefits I experienced when writing the game. While I wasn’t satisfied with either implementation, I do feel like I learned a bit more about working with vanilla JavaScript as well as some of the difficulties caused by the language design.
An exploration of programming paradigms in JavaScript
The first version of the game I implemented was the object-oriented style. (View the code and play it here on codepen.)
To start, I created several classes for the various objects in the game:
- A player class which stored some information about who controlled the player and which piece (‘X’ or ‘O’) they had
- A board class which saved the nine cells of the game and
- A square class which stored those who may have naught or crossed it, along with handling some interaction
I then wrapped the core logic of the game in a game class (this handled the various phases of the game):
function Player(piece, ai) {
this.piece = piece;
this.controller = ai ? 'ai' : 'human';
}
function Board() {
this.squares = [[null, null, null],[null, null, null],[null, null, null]];
for(var x = 0; x <= 2; x++) {
for(var y = 0; y <= 2; y++) {
this.squares[x][y] = new Square(document.querySelector('.js-x' + x + 'y' + y), this);
}
}
this.lastClicked = null;
}
function Game() {
this.board = new Board();
this.players = [new Player('O', true), new Player('X', false)];
this.currentTurn = 0;
}
Some initial difficulties I ran into when implementing the game this way was determining how to manage the player interaction. As JavaScript interactions are typically handled via a function triggered by the runtime when a user action occurs, I had trouble reconciling this with the game loop I wanted to implement. I ended up attaching event handlers on each of the squares and letting the square object handle the click. This notified the board object, and the board buffered the input in its lastClicked field.
I felt that the right way to go was to let the object clicked manage the response.
If I were to refactor the code, I would probably create a buffering system which would attach certain actions to a buffer handled by the player object rather than the board. Fortunately, TicTacToe is a simple game, so this isn’t strictly necessary.
As I was implementing the game flow via the main game loop, actions were independent of this loop, which means iterating this loop had to be done with a setTimeout which either repeated an input check or continued to the next turn of the game.
This design felt like it wasn’t suited to JavaScript’s execution model as what would be asynchronous thread.sleep within a loop turned into a chain of recursive asynchronous method calls.
Game.prototype.ProcessTurn = function() {
var currentPlayer = this.players[this.currentTurn % 2];
var t = this;
setTimeout(function() {
var currentMove = currentPlayer.GetMove(t.board);
var moveSuccess = t.board.ApplyMove(currentMove);
if(moveSuccess) {
t.Run();
} else {
t.ProcessTurn();
}
}, 100);
}
I felt the separation of concerns programming in an object oriented style made reasoning about parts of the code quite easy and I was happy with the architecture, though I have predominantly used Marionette Backbone so that could be put down to familiarity.
One thing that was frustrating (and may have been due to over abstraction) was some boilerplate constructors and function declarations I ended up writing. I could have avoided this had I forgone best practices and just accessed objects three references deep I felt that in a larger scale project this would have become untenable.
The second implementation was written in functional style (view the code and play it here).
One of the main advantages of functional programming is the lack of shared state and determinism. A key term is “pure function” which is a function which has no side effects, this means it doesn’t modify any program state, the returned result being the only new/modified information. “Pure function” makes reasoning about the code easier as you can be sure that no existing state changes when making a function call.
When writing this implementation, I saw some potential advantages to the object orientated style as well as some messiness, especially when trying to maintain a reasonable amount of functional purity yet having to deal with DOM manipulation.
A critical decision I made at the start when comparing the programming paradigms was to put the entire game state within a structure (JavaScript object) as TicTacToe contains minimal state. By doing this I could then clone and return an updated structure each time I performed a mutating operation.
Unfortunately, I still had to deal with a global reference (a pointer to the current state), and due to the asynchronous nature of event handling in JavaScript, I had to copy the references to the DOM elements that comprised the squares of the TicTacToe board each time I cloned the game state.
I removed the game loop in this implementation and triggered all events by a user click which then fires off the chain of functions including a subsequent move by the computer player and restarting the game.
function clickListener(x, y) {
...
state = processTurn(state, {x: x, y: y});
state = checkEndgame(state);
..
}
The advantage of a functional style meant that I could quickly build simple functions which all took the game state and did one thing with little to no side effects.
For instance checking whether the game is a draw:
function checkDraw(gameState) {
return gameState.turn > 8;
}
And checking the endgame:
function checkEndgame(gameState) {
var running = true;
if (checkWinner(gameState)) {
...
return newGameState;
}
This pattern would certainly help if I wanted to create a better opponent AI than the currently (random pick) as it would be relatively easy to plug in an algorithm which evaluated the board state recursively (into future generations) while not modifying any existing states.
Overall the functional implementation of the game while including a lot of repetition especially when cloning the game state (like so:)
var newGameState = {
turn: gameState.turn,
board: [
[cloneSquare(gameState.board[0][0]), cloneSquare(gameState.board[0][1]), cloneSquare(gameState.board[0][2])],
[cloneSquare(gameState.board[1][0]), cloneSquare(gameState.board[1][1]), cloneSquare(gameState.board[1][2])],
[cloneSquare(gameState.board[2][0]), cloneSquare(gameState.board[2][1]), cloneSquare(gameState.board[2][2])],
], players: gameState.players,
running: true
};
This method allowed me to manage the game flow and avoid complex data interactions. If I was going to do this on a larger scale, helper methods could reduce the amount of manual cloning, and a module system could easily allow me to divide up functions into logical groupings.
Final thoughts
In the end, the amount of code for each implementation was almost identical (about 170 lines – including spaces) with non-logical – constructor or cloning code taking up between 20% to 30% of each.
Regarding suitability within JavaScript, I believe functional styles work well with asynchronous operations and event callbacks. Regarding DOM interaction the encapsulation of elements in a JavaScript object is responsible for updating its rendered state and propagating any interactions.
Overall this exercise made me appreciate that while JavaScript might not be the most elegant of languages in any one respect, the multi-paradigm nature of the language does allow for a range of styles to be used independently to produce maintainable code. Some languages make it easy to write in some programming paradigms but not others. While I’d say my preferred style for this project was the object orientated style this was probably due to the simplicity of abstraction it gave me. The relatively small size of the project didn’t reveal some of the actual benefits function styles may have bought.
Now read
Useful JavaScript debugging tips you might not know
JavaScript debugging made easy with source maps
JavaScript unit testing frameworks – comparing Jasmine, Mocha and more