OOP Core Concepts

Mmmmm... pie.

A PIE


What does it stand for?

  • [ A ]bstraction
  • [ P ]olymporphism
  • [ I ]nheritance
  • [ E ]ncapsulation

Abstraction

We create abstractions to simplify our program domain. They allow us to focus on the problem we are solving rather than getting lost in minute details. You don't need to know how every subsystem in your car works in order to drive, do you?

Think about writing code as the process of creating a language to describe a problem we're solving. Abstractions are the building blocks of this language.


In our code

We are creating abstractions in the small whenever we:

  • name a variable
  • create (and name) a method
  • create (and name) a class
  • simplify an attribute by representing it as a number (hunger) or a label ("purple")

This is why naming is so important: we need to accurately convey the abstraction we're describing to those that follow (as well as our later selves).

We apply the other OO principles (PIE) to create meaningful and useful abstractions of more complex concepts.


Polymorphism

the quality or state of existing in or assuming different forms

Polymorphism allows us to represent intent, but allow the implementation to vary as needed based on context.


In our code

Method overloading

A method with the same name that accepts different argument types.

Examples:

  • console's log methods.
  • Jest's numerous matchers methods

Both of these methods accept strings, numbers, booleans, etc.

In our code

Method overriding

A method that redefines a method from a parent Object is said to override that method. A great example of this is the toString method from the root Object that we have discussed. (Remember, Object is the parent Object of all Objects.)


Parent Objects

We also implement polymorphism by extending a parent Object (a String isA Object). We talk about inheritance in the form of isA relationships. An Ellipse isA Circle isA Shape isA... etc. Which brings us to...


Inheritance

Inheritance is the mechanism whereby a class inherits behavior from a parent Object. We call this extending the parent Object. Recall that all Objects, whether we tell them to or not, implicitly extend Object.

We can use inheritance in JavaScript to build basic abstractions that we then specify more concretely by extending their behavior to other Objects.


In our code

Inheritance is what allows String concatenation to work without us doing anything extra (though perhaps it ain't pretty). Like when we do this:

let pet = new VirtualPet();
console.log("My pet is " + pet);

What happens behind the scenes is that the toString() method of Object (VirtualPet's parent class) is being called. VirtualPet has inherited this method from its parent.


Encapsulation

Encapsulation at its simplest is hiding away information that isn't necessary to share. The more knowledge we have about an object, the more complex our problem solving becomes.

Knowledge is power. Power corrupts.


In our code

class Circle {
  constructor(radius) {
    this.radius = radius;
  }

  getRadius() {
    return this.radius;
  }
}

The radius instance variable above is exposed via an accessor method (getRadius). Not only does this avoid radius being manipulated externally and possibly resulting in an invalid state, but also gives us the flexibility to implement getRadius in a different way if necessary. The code that asks our Circle object for the radius doesn't need to know how the radius is being determined.


A note on privacy

JavaScript doesn't have visibility modifiers like you might see in Java or C# (i.e. private, protected, or public). We can implement this behavior in a round about way. Let's see that:

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  getRadius() {
    return this._radius;
  }

  setRadius(radius) {
    this._radius = radius;
  }
}

Privacy continued

While this property isn't private in the same sense as Java or C#, it is conventionally understood that values prefaced with an underscore are meant to be treated as private. It is considered best practice not to manipulate values directly from outside or their Object scope. Super-sets or JS such as TypeScript or Elm adhere to the same principles and actually offer visibility modifiers.

This is encapsulation. A more complex example of encapsulation would be an object responsible for calculating sales tax for a transaction. If the formula changes, yet it is encapsulated within the object, other objects requesting the tax calculation need not change. This would also likely involve polymorphism, since sales tax calculations vary by a number of factors, including location and product type.


Gratuitous Picture of Pie