Object Oriented Programming - patrickcole/learning GitHub Wiki
Object Oriented Programming (OOP)
- JavaScript added more OOP-friendly patterns and syntaxes in ES6
- Being that JavaScript is a prototypal language, the object-oriented approach is no more than syntactic sugar (or an more familiar OOP approach) to building objects in JS
- Built on-top of the constructor and prototype nature of JS using
this
context for properties
// a more traditional approach using functions:
let Meal = function(food) {
this.food = food;
}
Meal.prototype.eat = function() {
return 'eating...';
}
// now with classes, all properties and methods can
// be encapsulated inside a Class:
class Meal {
constructor(food) {
this.food = food;
}
eat() {
return 'eating...';
}
static description() {
return 'The Meal object brought to you by Patrick';
}
}
- The
constructor()
method represents the function that is executed on each new instantiation of the object - The
eat()
method represents a prototype method that has been added to the objectMeal
- The
description()
method is implemented as astatic
method. That means it can be called without instantiating a new object
Meal.description();
// => 'The Meal object brought to you by Patrick'
Instantiation / Invoking a New Object
let breakfast = new Meal('cereal');
breakfast.eat();
// => 'eating...'
breakfast.description();
// => TypeError!
- Static methods cannot be called on an invoked or created object, they can only be called from the Class object
Meal
Note: we are using the
this
context inside our classes. This is important to note as arrow functions use the context they are declared in. This can be difficult to manage or debug when something goes wrong. Also, declaring objects with an arrow function means they are not able to use thenew
keyword as well.
Static Fields
- Fields that are able to be called directly on the class rather than on an invocation of the class object
- They cannot be called when an object is invoked
class Computer {
constructor(type) {
this.type = type;
}
static about() {
return 'I am a computer, beep boop'
}
}
let mac = new Computer('Mac');
mac.about(); // TypeError!
Computer.about(); // "I am a computer, beep boop"
Private Fields
- fields that are only available to the class itself
- they are not available outside the class or even in subclasses (which would be protected fields)
- private fields can be marked using the hashtag
#
symbol
class Machine {
#firmware_version = "3.49.0098"
constructor(brand) {
this.brand = brand;
}
}
let mac = new Machine("Apple");
console.log(mac.firmware_version); // Error!
- this rather simple example just shows that you cannot access the private field
firmware_version
from outside the Class itself. - however, from within the class, the
firmware_version
is available
class Machine {
#firmware_version = "3.49.0098"
constructor(brand) {
this.brand = brand;
}
startup(firmware_check) {
if ( firmware_check === this.#firmware_version ) {
return "Check matched!";
}
}
}
let mac = new Machine('Apple');
mac.startup("3.49.0098"); // => "Check matched!"
// but we still can access the field firmware_version:
let fw = mac.firmware_version; // => Error!, undefined
Using Extends and Super
Given the example below of student:
class Student{
constructor(name,age){
this.name = name;
this.age = age;
}
getAge(){
return this.age;
}
}
A new subclass can be created extending off of the base class of Student
:
class MedStudent extends Student {
constructor(name, age, course) {
super(name, age);
this.course = course;
}
getCourse() {
return this.course;
}
}
// and then invoked:
let medStudent = new MedStudent('Larry', 27, 'Anatomy');
By using extends
to establish the base class and then running super()
inside the constructor. The extended class of MedStudent
gets all the values and methods from the Student
class.
Being that the MedStudent
extended the Student
class the prototype of MedStudent
will be the Student
class. Nothing changes to the Student
class, it just has been used as a base for another class.
Beware of God Objects
Sometimes your classes or objects will need to share properties or methods later on during the development cycle. The natural solution might be to refactor your classes so they all inherit from what is known as a God object
. However, this seemingly easy solution can potentially cause issues later on.
With inheritance, you structure your classes around what they are, a User, an Animal, a Dog, a Cat - all of those words encapsulate a meaning centered around what those things are.
Perhaps take a look at what those objects can do. In the future, these objects may change and become difficult to bootstrap new features or ideas onto them. Taking this approach is thinking of objects in more of a Composition
Composition
Rather than building dedicated objects using Class
that are extended, the idea of composition takes what those objects can do. The basic rule of thumb is to develop the actions into dedicated functions that can be applied to an object, via a function constructor.
Take this example:
const eater = (state) => ({
eat(amount) {
console.log(`${state.name} is eating.`)
state.energy += amount
}
})
const sleeper = (state) => ({
sleep(length) {
console.log(`${state.name} is sleeping.`)
state.energy += length
}
})
const barker = (state) => ({
bark() {
console.log('Woof Woof!')
state.energy -= .1
}
})
We have two actions that any object can perform, regardless of what that object is. If we wanted to declare a Dog that barks in addition to eating and sleeping, we can. For the Cat object, we could just compose an object with eating and sleeping.
function Dog (name, energy, breed) {
// using object shorthand:
let dog = { name, energy, breed }
// now add all these functions to the Dog object:
return Object.assign(
dog,
eater(dog),
sleeper(dog),
barker(dog)
)
}
// now the cat:
function Cat (name, energy, declawed) {
// again, using object shorthand:
let cat = { name, energy, declawed }
return Object.assign(
cat,
eater(cat),
sleeper(cat)
)
}
// now each can still be used:
let rocky = new Dog("Rocky", 100, "Goldendoodle");
rocky.eat(10); // => "Rocky is eating"
rocky.bark(); // => "Woof Woof!"
This makes sharable actions more flexible to be added or removed in future updates. The allure of using Class
to make objects inherit shared properties or methods is easy to fall under. Developers should be aware of how to plan out their objects so that they don't fall in the trap of having to refactor with God objects
.