Level Up Your Object Oriented Programming Knowledge with Nintendo Switch 🎮👾
Tags: programming, learning, development, computerscience
Published: 2023-10-18 03:55:48

Imagine for a moment that **Object Oriented Programming (OOP)** is like the inner workings of your favorite gaming console, the Nintendo Switch. Just as the Switch combines different components to bring your favorite games to life, OOP unites data and functions to create efficient and organized code. In this article, we'll embark on a journey to explore the four fundamental principles of OOP through the lens of the Nintendo Switch, will try my best to teach you this powerful programming paradigm with examples that are as enjoyable as a game of the great N. Get ready to level up your coding skills and game on as we unlock the secrets of OOP with the Nintendo Switch as our guide.

OOP relies on two major concepts: **Classes** and **Objects**. These concepts can be applied in code to enable us to build applications. _Object Oriented Programming_ was created to guide the creation of better software, by achieving easier maintenance and reusability.

> **DISCLAIMER**: The following examples are just an attempt to explain my point, there may be inconsistencies according to reality

## CLASSES AND OBJECTS

Let's say that we want to create a brand new console, shall we? :) It comes in many colors, we have the ability to play videogames anywhere, it has a battery life of 6 hours that drains every time you play a game, and you can install many games on its internal storage! We decided to name it ✨*Nintendo Switch*✨

![Nintendo Switch Lite Drawing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afff8ufbctv5yzv10ft5.png)

If we translate this in code, we can create a **Class** named `NintendoSwitch` and we list the principal attributes and its methods, think of a *class* as it were a **template** or **blueprint** that will specify how the console looks and the way it's going to perform some actions (I would like to explain this class/object stuff with Typescript first):

```ts
class NintendoSwitch {
  private color: string;
  private screenSize: number = 5.5;
  private storageCapacity: number = 8;
  private batteryLife: number = 6;
  private gamesInstalled: string[];
  
  constructor(color: string, gamesInstalled: string[]) {
    this.color = color;
    this.gamesInstalled = gamesInstalled;
  }
  
  playGame(game: string): void {/.code logic./}
  
  installGame(game: string): void {/.code logic./}
  
  checkBatteryLife(): void {/.code logic./}
}
```

Here we have the principal *attributes* like `color`, `screenSize`, `storageCapacity`, `batteryLife`, `gamesInstalled`. Then we have the `constructor`, a function that serves to initialize the attribute's values when we create an **instance** of *NintendoSwitch* class.

Note that our class already has `storageCapacity`, `batteryLife`, and `screenSize` initialized, because it will be the same for all the new **instances** instead of `color` and `gamesInstalled`, those can be set at the moment we create the **instance** of the current class.

> An **Instance** is equal to an **Object**, we create as many objects as we want from a **Class**, in most languages we use the **new** keyword in order to create a new object, every object can be different one from another, because we can have many NintendoSwitch objects with different colors and different games installed!

![Instances example with Nintendo Switch Lite](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1c017zp0zgr84nkhuxog.png)
 
In this case, none of our instances have games to play with :( but the good part is that you can now understand what *Classes* and *Objects* are, so let's move on!

In order to achieve mastery, we need to understand the ✨**four principles** of Object Oriented Programming✨ starting with the first one:

## 📦 ENCAPSULATION

Is the need to separate your code in independent pieces for a single purpose, think of it like a box 📦 that holds data (*properties, attributes*) and the things that can be done with that data (*methods, functions*). This way, you can ensure that your data is safe and it's used correctly.

This principle can define the reason for having classes, we encapsulate a set of *attributes* and the things we can perform with those attributes as *methods*, with the purpose of hiding the internal process and just showing what matters to the public, it's up to us if we want to set some attributes or methods as **public**, or keep it **private** just for internal use.

If you want to explain this in a more technical way:

> _Encapsulation is a way to restrict the direct access to some components of an object, so users cannot access state values for all of the variables of a particular object. Encapsulation can be used to hide both data members and data functions or methods associated with an instantiated class or object._

## INHERITANCE

Is the idea that you can inherit perks and abilities from your parents 👨‍👩‍👦, like eye color or hair type.

Let's go back to the Nintendo Switch; Now we want to create a new console by taking all the things inside of the _Nintendo Switch_ but with additional features. If we talk in terms of coding, maybe some newbie developer may think of copying and pasting the _Nintendo Switch_ class and creating a new one to add the desired new features... easy, right? But what if I told you that instead of re-write the code again, we can create our new model from what we already have 😱 Yes! you heard it right! we can **extend** from the *Nintendo Switch* class and then create the *Nintendo Switch Pro* class without copying all the code from scratch, we will have all the features of the original Nintendo Switch, and then we can just add the new code for the features! on this new model, we want detachable controllers and also the ability to connect the Nintendo Switch to a dock and be able to play on a TV! how awesome is that?

So we proceed to create our new class for the *Nintendo Switch Pro*, **extending** from *NintendoSwitch* class, like this:

```ts
class NintendoSwitchPro extends NintendoSwitch {  
  constructor(props) {
    this.super(props);
  }
  
  detachJoycons() {/../}
  
  connectToTV() {/../}
}
```

![Nintendo Switch Drawing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vo04f6sw9cwa4jyj1laa.png)

We can see that its composition it's similar, but now we can detach the controllers (`detachJoycons`) to play with them wireless, and we can also connect it to the TV! (`connectToTV`)

Maybe this is a vague example but in the big picture this is ✨**inheritance**✨ we take the *NintendoSwitch* class, and extend this in a new class *NintendoSwitchPro* that inherits all the *NintendoSwitch* attributes and methods, and additionally we have two more methods that can do new things!

This approach makes things more organized and saves you from doing the same things over and over again.

## POLYMORPHISM

Polymorphism means many shapes, in OOP we can **modify the properties and methods that we inherited** to have different behavior in a way that makes sense for each class. This makes our code flexible and easy to adapt to many use cases.

Going back to our previous example, our first class, *Nintendo Switch*, as we know has a little screen of 5.5in, its size makes you able to even fit in your pocket and the battery lasts 6 hours of continuous play! But then when we created the *Nintendo Switch Pro*, and decided that this new model would have a screen of 7in, bigger than his **parent**!! and Thus we have a larger size of the whole console that can barely fit in your jeans pocket, we increment the battery life up to 10 hours of continuous play, and the rest remains the same, so now our new class will end like this:

```ts
class NintendoSwitchPro extends NintendoSwitch {
  private screenSize: number = 7;
  private batteryLife: number = 10;
  
  constructor(props) {
    this.super(props);
  }
  
  detachJoycons() {/../}
  
  connectToTV(game: string) {/../}
}
```

> **super()** is another keyword that most languages adopt to refer to the **super class** or **parent class**, in the case above, we are using the same exact constructor of the *parent* class because we don't need to add anything new to it.

![Explanation of inheritance with Nintendo Switch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suhdn87cu66pejknazes.png)

As you can see, we set a couple of attributes that are named the same as some of the *parent* models but modified their values to fit the new necessity. This is called **Overriding** and it is a valid practice, even with methods! is like taking the inherited abilities and leveraging them with superpowers 💪🏻

## ABSTRACTION

Abstraction means simplifying complex things by breaking them into smaller and more manageable parts. Think of it like driving a car 🚗 we don't need to know all the technical details of how the engine works, we just need to understand how the basic controls like the steering wheel, gas pedal, and breaks work. With that being said, our methods contained in the *NintendoSwitch* class can tell us **WHAT** it does, without caring much about *HOW* it does.

For example, the method `checkBatteryLife()` can tell us what it does just by its own name! but internally works like this:

```ts
// NintendoSwitch class file
checkBatteryLife(): void {
  console.log(`This Nintendo Switch has ${this.batteryLife} hours of battery life remaining.`);
}

//Our main script
const myFirstSwitch = new NintendoSwitch('Blue', []);

myFirstSwitch.checkBatteryLife(); // Console output: This Nintendo Switch has 6 hours of battery life remaining.
```

As you can see, the `batteryLife` attribute on its own doesn't say much about it, it's just `6`, but 6 what?? then `checkBatteryLife()` gives us more context, and the result is that the logged message in the console specifying that the battery life attribute is meant to be the *hours* left of battery life!

> Sometimes we can find **getters** and **setters** functions inside classes, we won't focus on these things right now, but think of them as functions that help us to set/modify the value of an object's attribute (*setters*), and functions to read those values (*getters*). At first sight, this may seem useless, but sometimes this can keep our attributes safer, like by adding a validation layer, so no one can set a null-ish value that can break our app, or set that *abstraction* layer like in the example above to give a meaning to our attributes values.

In OOP we can hide complicated pieces of code and provide a simple interface, so we can focus on what our code does rather than how it does it, this makes our code easier to understand and maintain.

---

And that's all, folks!

I hope that you can understand better the OOP principles with these examples :) Let me know in the comments your thoughts, this is an important topic in software development, and can even get you an opportunity to get a job in a big tech company! they often opt for developers with this kind of general knowledge instead of looking for someone with a specific language, because if you know all of this stuff you are more likely to adapt to any technology.

This is the first of a three-part series that I'm about to write, because I want you to understand from my perspective, how OOP, Javascript Prototypes, and SOLID principles can be related ✨