OOP vs functional programming
Jakob Klamser
Posted on August 28, 2020
Introduction
Before we get started with coding I want to give you a quick introduction to object-oriented and functional programming.
Both are programming paradigms differing in the techniques they allow and forbid.
There are programming languages that only support one paradigm e.g. Haskell (purely functional).
Aswell as languages that support multiple paradigms such as JavaScript, you can use JavaScript to write object-oriented or functional code or even a mixture of both.
Setup
Before we can dive deep into the differences between these two paradigms we need to setup the project.
For that we first create all the files and folders we need like this:
$ mkdir func-vs-oop
$ cd ./func-vs-oop
$ cat index.html
$ cat functional.js
$ cat oop.js
I'm using the cat command because it works both on Linux-systems and Windows Powershell.
Next up we need to create a simple form for the factorial calculator inside the index.html.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<script src="functional.js" defer></script>
</head>
<body>
<div class="container mt-5">
<div class="container mt-3 mb-5 text-center">
<h2>Functional vs OOP</h2>
</div>
<form id="factorial-form">
<div class="form-group">
<label for="factorial">Factorial</label>
<input class="form-control" type="number" name="factorial" id="factorial" />
</div>
<button type="submit" class="btn btn-primary">Calculate</button>
</form>
<div class="container mt-3">
<div class="row mt-4 text-center">
<h3>Result:</h3>
<h3 class="ml-5" id="factorial-result"></h3>
</div>
</div>
</div>
</body>
</html>
To give this form a better look and feel we use bootstrap as a CSS-Framework.
If you display this HTML in the browser it should look like this:
Currently this form won't do anything.
Our goal is to implement a logic where you can enter a number up to 100. After clicking the "Calculate"-button it should show the result in the result-div.
We will implement this both in the object-oriented way and the functional way.
Functional implementation
First off we will create a file for the functional programming approach.
$ cat functional.js
To get started we need a function that get's called when loading this file into the browser.
This function should get the form and then add the functionality we need to the submit-event of the form.
function addSubmitHandler(tag, handler) {
const form = getElement(tag);
form.addEventListener('submit', handler);
}
addSubmitHandler("#factorial-form", factorialHandler);
First we declare the function called addSubmitHandler.
This function takes in two parameters, the first one being the tag we want to look for in our HTML, the second one being the function we want to bind to the submit-event of our Element.
Next we call this function by passing in #factorial-form and the function name factorialHandler.
The hashtag in front of the tag indicates that we are looking for the id-attribute in the HTML.
This code will throw an error if you try to run it now, because neither the function getElement nor factorialHandler are defined anywhere.
So let's first define getElement above our addSubmitHandler function like this:
function getElement(tag) {
return document.querySelector(tag);
}
This function is really simple and only returns the HTML-Element we found by the tag we passed in.
But we will reuse this function later on.
Now let's start creating the core logic by adding the factorialHandler function above the addSubmitHandler.
function factorialHandler(event) {
event.preventDefault();
const inputNumber = getValueFromElement('#factorial');
try {
const result = calculateFactorial(inputNumber);
displayResult(result);
} catch (error) {
alert(error.message);
}
}
We pass in the event and instantly call preventDefault.
This will prevent the default behavior of the submit event, you can try out what happens on the button click without calling preventDefault.
After that we get the value entered by the user from the input-field by calling the getValueFromElement function.
Upon getting the number we try to calculate the factorial by using the function calculateFactorial and then render the result to the page by passing the result to the function displayResult.
If the value is not in the correct format or the number is higher than 100, we will throw an error and display that as and alert.
This is the reason for using a try-catch-block in this particular case.
In the next step we create two more helper functions, getValueFromElement and displayResult.
Let's add them below the getElement function.
function getValueFromElement(tag) {
return getElement(tag).value;
}
function displayResult(result) {
getElement('#factorial-result').innerHTML = result
}
Both of these functions use our getElement function. This reusablility is one part why functional programming is so effective.
To make this even more reusable we could potentially add a second argument to displayResult, called tag.
So that we can dynamically set the element that should display the result.
But in this example I went with the hard-coded way.
Next up we create our calculateFactorial function right above factorialHandler.
function calculateFactorial(number) {
if (validate(number, REQUIRED) && validate(number, MAX_LENGTH, 100) && validate(number, IS_TYPE, 'number')) {
return factorial(number);
} else {
throw new Error(
'Invalid input - either the number is to big or it is not a number'
);
}
}
We validate if the argument 'number' is not empty, not above 100 and of type number.
For that we use a function called validate that we will need to create next.
If the checks pass we call the function factorial and return it's result.
If these checks don't pass we throw the error we catched in the factorialHandler function.
First let's create the validate function right below displayResult and the three constants MAX_LENGTH, IS_TYPE and REQUIRED.
const MAX_LENGTH = 'MAX_LENGTH';
const IS_TYPE = 'IS_TYPE';
const REQUIRED = 'REQUIRED';
function validate(value, flag, compareValue) {
switch (flag) {
case REQUIRED:
return value.trim().length > 0;
case MAX_LENGTH:
return value <= compareValue;
case IS_TYPE:
if (compareValue === 'number') {
return !isNaN(value);
} else if (compareValue === 'string') {
return isNaN(value);
}
default:
break;
}
}
In this function we use a switch to determine which kind of validation we are going to perform.
After determining that, it is just a simple value validation.
Now we are going to add the actual factorial function right above the calculateFactorial declaration.
This will be our last function for this approach.
function factorial(number) {
let returnValue = 1;
for (let i = 2; i <= number; i++) {
returnValue = returnValue * i;
}
return returnValue;
}
There are many different ways to perform a factorial calculation, I went with the iterative approach.
If you want to learn more about the different approaches I recommend you check out this article on geeksforgeeks:
The final functional.js file should look like this:
const MAX_LENGTH = 'MAX_LENGTH';
const IS_TYPE = 'IS_TYPE';
const REQUIRED = 'REQUIRED';
function getElement(tag) {
return document.querySelector(tag);
}
function getValueFromElement(tag) {
return getElement(tag).value;
}
function displayResult(result) {
getElement('#factorial-result').innerHTML = result
}
function validate(value, flag, compareValue) {
switch (flag) {
case REQUIRED:
return value.trim().length > 0;
case MAX_LENGTH:
return value <= compareValue;
case IS_TYPE:
if (compareValue === 'number') {
return !isNaN(value);
} else if (compareValue === 'string') {
return isNaN(value);
}
default:
break;
}
}
function factorial(number) {
let returnValue = 1;
for (let i = 2; i <= number; i++) {
returnValue = returnValue * i;
}
return returnValue;
}
function calculateFactorial(number) {
if (validate(number, REQUIRED) && validate(number, MAX_LENGTH, 100) && validate(number, IS_TYPE, 'number')) {
return factorial(number);
} else {
throw new Error(
'Invalid input - either the number is to big or it is not a number'
);
}
}
function factorialHandler(event) {
event.preventDefault();
const inputNumber = getValueFromElement('#factorial');
try {
const result = calculateFactorial(inputNumber);
displayResult(result);
} catch (error) {
alert(error.message);
}
}
function addSubmitHandler(tag, handler) {
const form = getElement(tag);
form.addEventListener('submit', handler);
}
addSubmitHandler("#factorial-form", factorialHandler);
In this approach we worked exclusively with functions. Every function got a single purpose and most of them are reusable in other parts of the application.
For this simple web application the functional approach is a bit of an overkill. Next we will code the same functionality but this time object-oriented.
Object-oriented implementation
First of all we need to change the src in the script-tag of our index.html file to the following.
<script src="oop.js" defer></script>
Now we create the oop.js file.
$ cat oop.js
For the OOP approach we want to create three different classes, one for validation, one for the factorial calculation and one for handling the form.
We get started with creating the class that handles the form.
class InputForm {
constructor() {
this.form = document.getElementById('factorial-form');
this.numberInput = document.getElementById('factorial');
this.form.addEventListener('submit', this.factorialHandler.bind(this));
}
factorialHandler(event) {
event.preventDefault();
const number = this.numberInput.value;
if (!Validator.validate(number, Validator.REQUIRED)
|| !Validator.validate(number, Validator.MAX_LENGTH, 100)
|| !Validator.validate(number, Validator.IS_TYPE, 'number'))
{
alert('Invalid input - either the number is to big or it is not a number');
return;
}
const factorial = new Factorial(number);
factorial.display();
}
}
new InputForm();
In the constructor we get the form-element and the input-element and store it in class variables, also called properties.
After that we add the method factorialHandler to the submit-event.
In this case we need to bind 'this' of the class to the method.
If we don't do that we will get reference errors, e.g. calling this.numberInput.value will be undefined.
After that we create the class method factorialHandler with the event as an argument.
The code of this method should look somewhat familiar, for example the if-statement checks if the inputvalue is valid or not, like we did in the calculateFactorial function.
Validator.validate is a call to a static method inside the class Validator that we still need to create.
We don't need to initialize a new instance of an object if we work with static methods.
After the validations pass we create a new instance of the Factorial class, pass in the inputvalue and then display the calculated result to the user.
Next up we are going to create the Validator class right above the InputForm class.
class Validator {
static MAX_LENGTH = 'MAX_LENGTH';
static IS_TYPE = 'IS_TYPE';
static REQUIRED = 'REQUIRED';
static validate(value, flag, compareValue) {
switch (flag) {
case this.REQUIRED:
return value.trim().length > 0;
case this.MAX_LENGTH:
return value <= compareValue;
case this.IS_TYPE:
if (compareValue === 'number') {
return !isNaN(value);
} else if (compareValue === 'string') {
return isNaN(value);
}
default:
break;
}
}
}
As you can see everything inside of this class is static, the method validate aswell as the three properties.
Therefor we do not need any constructor.
The advantage of this is that we do not need to initialize this class everytime we want to use it.
validate is mostly the same as the validate function is our functional.js except that we do this.REQUIRED, this.MAX_LENGTH and this.IS_TYPE instead of just the variable name.
Next up we create our Factorial class right below the Validator class.
class Factorial {
constructor(number) {
this.resultElement = document.getElementById('factorial-result');
this.number = number;
this.factorial = this.calculate();
}
calculate() {
let returnValue = 1;
for (let i = 2; i <= this.number; i++) {
returnValue = returnValue * i;
}
return returnValue;
}
display() {
this.resultElement.innerHTML = this.factorial;
}
}
Upon initializing an instance of this class we get the resultelement and store it as a property aswell as the number we pass in.
After that we call the method calculate and store it's return value in a property.
The calculate method contains the same code as the factorial function in functional.js.
Last but not least we got the display method that sets the innerHTML of our resultelement to the calculated factorial number.
The complete oop.js file should look like this.
class Validator {
static MAX_LENGTH = 'MAX_LENGTH';
static IS_TYPE = 'IS_TYPE';
static REQUIRED = 'REQUIRED';
static validate(value, flag, compareValue) {
switch (flag) {
case this.REQUIRED:
return value.trim().length > 0;
case this.MAX_LENGTH:
return value <= compareValue;
case this.IS_TYPE:
if (compareValue === 'number') {
return !isNaN(value);
} else if (compareValue === 'string') {
return isNaN(value);
}
default:
break;
}
}
}
class Factorial {
constructor(number) {
this.resultElement = document.getElementById('factorial-result');
this.number = number;
this.factorial = this.calculate();
}
calculate() {
let returnValue = 1;
for (let i = 2; i <= this.number; i++) {
returnValue = returnValue * i;
}
return returnValue;
}
display() {
this.resultElement.innerHTML = this.factorial;
}
}
class InputForm {
constructor() {
this.form = document.getElementById('factorial-form');
this.numberInput = document.getElementById('factorial');
this.form.addEventListener('submit', this.factorialHandler.bind(this));
}
factorialHandler(event) {
event.preventDefault();
const number = this.numberInput.value;
if (!Validator.validate(number, Validator.REQUIRED)
|| !Validator.validate(number, Validator.MAX_LENGTH, 100)
|| !Validator.validate(number, Validator.IS_TYPE, 'number'))
{
alert('Invalid input - either the number is to big or it is not a number');
return;
}
const factorial = new Factorial(number);
factorial.display();
}
}
new InputForm();
We created three different classes handling three different aspects of our application:
- Validation: Validation class
- Factorial Handling: Factorial class
- Form Handling: InputForm class
Conclusion
Both approaches are valid ways of structuring your code.
Personally I like to try out what works best in the different projects I work on.
Most of the time it is not even possible to seperate both paradigms so clearly.
I hope this little comparison gave you a fundamental understanding of what the different approaches look like.
As always you can find the code for this project on my github.
Posted on August 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.