Object Oriented Programming Foundations

Introduction

Object-Oriented Programming (OOP) is a popular approach to computer programming. It is a way of organizing and structuring code to model real-world entities (things) or “objects” and their interactions.

It makes programming more intuitive because humans think and interact with the world in terms of objects and what is known about them and what they can do.

What is Object-Oriented Programming (OOP)?

To best understand OOP, it is worth considering why it emerged or what it solves. You have worked with objects created through plain object syntax. Perhaps you wondered why these types of constructs came about, why not just use variables.

In the earlier days of programming, code bases were simply a collection of variables and functions, typically declared at the highest scope. This made it rather cumbersome to arrange, navigate and think about systems and code functionality.

Consider the example below which is part of a coffee order system. Each coffee order has information to keep track of; an id, type, quantity and price and would be stored in a variable in global scope.

javascript
	// Global scope variables for three coffee orders
	
	// First order
	let order1Id = 1;
	let order1Type = "Latte";
	let order1Quantity = 2;
	let order1Price = 5.0;
	
	// Second order
	let order2Id = 2;
	let order2Type = "Espresso";
	let order2Quantity = 1;
	let order2Price = 3.0;
	
	// Third order
	let order3Id = 3;
	let order3Type = "Cappuccino";
	let order3Quantity = 3;
	let order3Price = 4.5;
	
	function displayOrders(orderId, orderType, orderQuantity, orderPrice) {
	  console.log(
	    `Order Info: ID=${orderId}, Type=${orderType}, Quantity=${orderQuantity}, Price=$${orderPrice}`
	  );
	}
	
	// Display the orders
	displayOrders(order1Id, order1Type, order1Quantity, order1Price);

This means that your script would have a whole bunch of variables mixed up together. This would make it rather difficult to know which variables belong to which order and how to interact and manage the data let alone manage their scope and protect the values from being changed incorrectly. Each variables would need to be manually organized and named appropriately so that you know which variables belong to a given order. You can see how this approach is not particularly desirable. Especially when working with a lot more coffee orders.

The Object-Oriented Programming approach emerged within the programming community to solve this problem. Instead data for a given entity or thing is grouped into objects. This way all the data belonging to that object is in one place. This makes it easier and more intuitive to code and design systems. This is illustrated below using plain old Javascript objects (POJO), which you have used before.

javascript
	// Coffee order objects
	
	let order1 = {
	  id: 1,
	  type: "Latte",
	  quantity: 2,
	  price: 5.0,
	};
	
	let order2 = {
	  id: 2,
	  type: "Espresso",
	  quantity: 1,
	  price: 3.0,
	};
	
	let order3 = {
	  id: 3,
	  type: "Cappuccino",
	  quantity: 3,
	  price: 4.5,
	};
	
	function displayOrders(order) {
	  console.log(
	    `Order: ID=${order.id}, Type=${order.type}, Quantity=${order.quantity}, Price=$${order.price}`
	  );
	}
	
	// Display the orders
	displayOrder(order1);

With the above refactored version, all the data for the orders id, type, quantity and price is stored in an object. So instead of needing to pass the four loose variables into the function, we can just pass in one object. Another example would be when you want to update some data. Lets say the quantity for order1 and order3 needs to be increased by two. The previous approach would require the developer to look through all 13 variables to make sure the correct data is updated. But with the OOP approach, each objects quantity can be easily referenced and updated. Below shows these two different scenarios.

javascript
	// loose variables
	order1Quantity = 3;
	order3Quantity = 4;
	
	// OOP
	order1.quantity = 3;
	order3.quantity = 4;

There are many more examples of how OOP makes coding more intuitive compared to its predecessor approaches. You will experience these the more you work with OOP.

OOP is a vast subject and has numerous conventions and principles. These conventions and principles are crucial for OOP-based languages typically used in backend web development like C# and Java because they help in managing complex, large-scale systems by promoting code reuse, scalability, and maintainability. Backend systems often require handling numerous interactions, data manipulations, and business logic, which can be efficiently managed using OOP principles such as encapsulation, inheritance, and polymorphism.

On the other hand, frontend development typically focuses more on user interfaces and immediate user interactions. While OOP principles can still be beneficial, frontend development often deals with smaller, more modular components and may prioritize flexibility and speed of development over the strict structure and organization provided by OOP conventions. Thus, the need for rigorous adherence to OOP principles is less pronounced in the frontend compared to the backend.

There are some core concepts and principles that you need to understand about OOP, namely encapsulation, information hiding, inheritance and polymorphism. In order to understand these, we need to understand objects are defined and created in modern JS development, using classes.

Classes

A class is a blueprint or template for objects. It defines the fields (variables) and methods (functions) that objects of that class will have. Classes are like cookie cutters, and objects are like the cookies you make from those cutters. They will all have the same structure.

Why is this useful? Consider the example below:

jsx
	const myFirstOrderPojo = {
	  orderId: 1,
	  total: 190,
	  date: "10-30-2023",
	};
	
	const mySecondOrderPojo = {
	  orderId: 2,
	  totals: 70,
	  date: "11-30-2023",
	};

Notice that the total property is different in the second object (totals), it should be total, these two objects don’t have the same structure. Creating objects in this way makes it possible to accidentally define inconsistent object structures.

Classes provide a consistent way of creating objects as opposed to needing to repeatedly create plain old JS objects (POJO) and manually ensure the properties are consistent.

Anatomy of a Class

Classes are typcially made up of four main parts, fields (variables), methods (functions), constructors and properties (getters and setters). The class below demonstrates fields, methods and a constructor. Getters and setters are shown when we discuss information hiding.

Consider the following class which contains a constructor, a method called myMethod and three fields myInitial, basicProperty and emptyProperty:

javascript
	class MyClass {
	  // This is a field in our class
	  basicField = "My basic field";
	  // This is another field however it isn't initialized to a value
	  emptyField;
	  // The constructor lets us set values when we create an instance of a class
	  myInitial;
	  constructor(myInitialValue, emptyFieldValue) {
	    // A field 'myInitial' being initialized to an argument
	    this.myInitial = myInitialValue;
	    // A field 'emptyProperty' being initialized to an argument
	    this.emptyField = emptyFieldValue;
	  }
	
	  // A method in the class
	  myMethod() {
	    console.log(this.myInitial);
	  }
	}

As mentioned, a class is just a template of an object. We can create an object with the class, called an “instance”, which then has all of the properties and methods from the original class.

Creating Objects

We create a new instance of a class by using the new keyword.

javascript
	const myClassInstance = new MyClass("Hello world", 3);

We can now call the myMethod method from this class or for example log out the value of the emptyField:

javascript
	// Logs "Hello world"
	myClassInstance.myMethod();
	console.log(myClassInstance.emptyField);

Let’s look at a more relevant example.

javascript
	class Order {
	  // fields
	  orderId;
	  total;
	  date;
	
	  // constructor
	  constructor(ordId, total, date) {
	    this.orderId = ordId;
	    this.total = total;
	    this.date = date;
	  }
	
	  // method/s
	  printReceipt() {
	    console.log(
	      `Receipt Id: ${this.orderId}, Date: ${this.total} Total: ${this.date}`
	    );
	  }
	}

The above Order class has three fields (orderId, total and date), one method (printReceipt) and a constructor.

Fields

The fields are the variables that store the data for an object. It represents what is known about an object, its total or date etc.

Constructor

You can create multiple objects from a single class, each with its own set of data and behaviours.

We use the new keyword and then the name of the Class with parenthesis like a function. This will call the constructor to construct the object. Typically, the constructor (like in the Order class) has parameters so that we can provide values for the objects fields in line, instead of setting values for each field individually.

javascript
	const myFirstOrder = new Order(1, 190, "10-30-2023");
	const mySecondOrder = new Order(2, 70, "11-30-2023");

The constructor in a class allows us to set any initial field values or allow for values to be passed in as arguments as shown in the Order class above.

NOTE: You do not need to make use of a constructor if you have nothing to initialize.

Methods

Methods are functions inside a class. They are copied when we create a new instance of a class.

We don’t use the function keyword when creating a method in a class, we simply write the method name:

javascript
	printReceipt() {
	    console.log(
	      `Receipt Id: ${this.orderId}, Date: ${this.total} Total: ${this.date}`
	    );
	  }
NOTE

The two objects created above with the Order class (myFirstOrder and mySecondOrder) are guaranteed to have the same structure (fields and methods) when created using the Order class. The fields and methods are defined once in the class, no need to repeat for each object.

NOTE

Practical example 1: Person object

Now that we’ve covered the basics, let’s start looking at a more practical example:

The following is a basic example of a class called User. It takes in firstName and lastName parameters and has a method greetUser() which logs a greeting message.

javascript
	class User {
	  language = "Norwegian";
	  constructor(firstName, lastName) {
	    // Set 'firstName' to the 'firstName' parameter
	    this.firstName = firstName;
	    // Set 'lastName' to the 'lastName' parameter
	    this.lastName = lastName;
	  }
	
	  // 'greetUser' method that logs a greeting message
	  greetUser() {
	    console.log(
	      `Hello ${this.firstName} ${this.lastName}! Language: ${this.language}`
	    );
	  }
	}
	
	const newUser = new User("Ola", "Nordmann");
	// Logs "Hello Ola Nordmann! Language: Norwegian"
	newUser.greetUser();

Practical example 2: Shopping Cart

We are going to have a look at a more complex implementation of a class.

In this example, we are going to create a Shopping Cart.

This class has:

  • addToCart: A function to add to the cart.
  • removeFromCart: Removes an item from the cart.
  • calculateTotalCost: Calculates the total cost of the items in the cart.
  • displayCart: Displays the items from the cart.
  • displayTotalCost: Displays the total cost of the items in the cart.
javascript
	class ShoppingCart {
	  cart = [];
	
	  constructor(shopName, currency) {
	    this.shopName = shopName;
	    this.currency = currency;
	  }
	
	  /** Adds the item to the cart **/
	  addToCart(item) {
	    this.cart.push(item);
	  }
	
	  /** Removes the item from the cart **/
	  removeFromCart(item) {
	    const idToFind = item.id;
	    // Find the index to remove
	    const indexToRemove = this.cart.findIndex(
	      (currentItem) => currentItem.id === idToFind
	    );
	    // If the index is -1 then it means no item was found, so
	    //    we return null to break out of the function
	    if (indexToRemove === -1) {
	      return null;
	    }
	    // Filter the items and remove the item that matches our index
	    const newCart = this.cart.filter((item, index) => index !== indexToRemove);
	    // Set the cart to cart without the item by spreading out the array
	    this.cart = [...newCart];
	  }
	
	  /** Calculates the total cost of items in the cart **/
	  calculateTotalCost() {
	    const totalCost = this.cart.reduce((total, item) => {
	      total += item.price;
	      return total;
	    }, 0);
	    return totalCost;
	  }
	
	  /** Displays the items in the cart **/
	  displayCart() {
	    console.log("Your cart:");
	    console.log("-------------------");
	    this.cart.forEach((item) => {
	      console.log(item.title);
	    });
	    console.log("===================");
	  }
	
	  /** Displays the total cost of the items in the cart **/
	  displayTotalCost() {
	    console.log("Total items: ", this.cart.length);
	    console.log("The total of the cart is:", this.calculateTotalCost());
	  }
	}
	
	const myCart = new ShoppingCart("Norway Bakery", "USD");
	
	const cookies = { id: 23, title: "Chocolate Chip Cookies", price: 20.0 };
	const cake = { id: 45, title: "Vanilla Cake", price: 30.0 };
	
	myCart.addToCart(cookies); // Add an item
	myCart.addToCart(cookies); // Add an item
	myCart.addToCart(cake); // Add an item
	myCart.displayCart(); // Display the cart
	myCart.removeFromCart(cookies); // Remove an item
	myCart.displayCart(); // Display the cart
	myCart.displayTotalCost(); // Display total cost of the cart

Lesson task

Goal

To be able to create a class, add methods and then create an instance of that class.

Brief

We are going to create a basic class based on what you have learnt so far.

NOTE: Lesson tasks do not get submitted on Moodle and are not assessed by tutors. They are mainly there for you to practise what you have learned in the lesson.

Level 1 process

  1. Create a class called Person.

  2. It should take in firstName and lastName arguments.

  3. Add a method introduce that logs "Hello I am Ola Nordmann" if I was to supply Ola as the firstName and Nordmann as the last name.

  4. Create a new instance of this class and then call the introduce method from this instance.

Additional resources

MDN: Classes

Javascript.info: Class