DEV Community

Cover image for Object Oriented Programming
Vincent Villaluna
Vincent Villaluna

Posted on

Object Oriented Programming

What is it?
Object-Oriented Programming (OOP) based on my understanding is that it is a way of coding in which you have to group every data and operation into an object/class and it must be properly grouped.

Sounds easy right? But in reality, it should be planned very well because sometimes data and operations are hard to group and could easily be put in the wrong object. This is where the skills of a programmer came into place to create a well-designed object and I will give some basic information about how to achieve it.

First will talk about:

  • Cohesion and Coupling
  • 4 Pillars of OOP

Cohesion and Coupling
We have the concept of cohesion and coupling in the S.O.L.I.D Principle by Robert C. Martin (a.k.a uncle bob).

Cohesion
Cohesion is the measure of how well grouped or related your data and operation are, if it is high cohesion means it is grouped well and if it is not then the cohesion is low. ( The goal is to have a high cohesion ).

Coupling
Coupling is the measure of dependency between objects. Objects that have a lot of dependency on other objects are high coupling. Coupling is inevitable but we can design them to lessen coupling by using abstraction or interfaces. ( The goal is to have fewer coupled objects ).

4 Pillars of OOP

  1. Inheritance
  2. Encapsulation
  3. Abstraction
  4. Polymorphism

Inheritance
It is when we want to reuse an existing object by extending or composing them. You can create more generic objects and let more specific objects extend those generic objects.

// generic object
class Tag {
  className: string;
  id: string;
  constructor(className: string, id: string) {
    this.className = className;
    this.id = id;
  }
  display() { ... }
}

// inheritance
class AnchorTag extends Tag {
  href: string;
  constructor(className: string, id: string, href: string) {
    super(className, id);
    this.href = href;
  }
  // override
  display() { ... }
  // adding feature
  onClick() { ... }
}
Enter fullscreen mode Exit fullscreen mode

Composition ( Preferred over inheritance )
Inheritance is hard to track when the hierarchy of objects becomes big. When this happens you have to go through the entire parents of the objects just to know what fields and operations they had. ( This means also you are tightly coupled to the parent objects ). With composition, you can refer to an object by the interface to reduce coupling.

// generic object
class Tag {
  className: string;
  id: string;
  constructor(className: string, id: string) {
    this.className = className;
    this.id = id;
  }
  display() { ... }
}

// inheritance
class AnchorTag {
  href: string;
  tag: Tag; // composing the Tag object
  constructor(className: string, id: string, href: string) {
    this.tag = new Tag(className; id); // this can be improved by providing factory method that returns interface to reduce coupling.
    this.href = href;
  }
  // use Tag display method
  display() {
    this.tag.display();
  }
  // adding feature
  onClick() { ... }
}
Enter fullscreen mode Exit fullscreen mode

Encapsulation
Is about protecting data from assigning invalid states and hiding where data was taken from. In javascript to achieve encapsulation, you have to wrap the object with a function.

const user = (name) => {
  let nameField = name;
  return {
    setName: (newName) => {
      /* 
       * validate input to protect 
       * invalid state for nameField
       */
      nameField = newName;
    },
    getName: () => {
      /*
       * hide how nameField retrieved
       */
      return nameField;
    }
  }
}

const tom = user("tom");
// tom.nameField = 123897 is now protected
console.log(tom.getName()) // tom
tom.setName('thomas');
console.log(tom.getName()) // thom
Enter fullscreen mode Exit fullscreen mode

This can also be achievable with typescript by using the interface and only exposing accessors.

interface IUser {
  setName(newName: string);
  getName(): string;
}

class User implements IUser {
  name: string
  constructor(name: string) {
    // validation
    this.name = name;
  }
  setName(newName: string) {
    // validation
    this.name = newName;
  }
  getName(): string {
    // hide how it was retrieved
    return this.name;
  }
}

// factory function
const createUser = (name: string): IUser {
  return new User(name);
}

const user = createUser('tom');
// user.name error name is not defined on interface.
user.setName('thom');
user.getName(); // thom
Enter fullscreen mode Exit fullscreen mode

Abstraction
Is about hiding complexity or irrelevant implementation of our object to the consumer. 80% of our time as programmers read code and the rest is writing new, improving, and fixing bugs. So it is good to hide the implementation to reduce time consumption on reading. Abstraction also reduces coupling because we are not relying on concrete implementation.

interface IPlayer {
  play();
}

// Abstracted;
class Player implements IPlayer {
  file: IPlayer;
  constructor(f: IPlayer) {
    this.file = f;
  }
  play() { // abstraction of play();
    this.file.play();
  }
}

// concrete class
class Mp3 implements IPlayer {
  play() {
    // do stuffs here.
  }
}

// concrete class
class Mp4 implements IPlayer {
  play() {
    // do stuffs here
  }
}

// factory function
const createFile(file: string): IPlayer {
  switch(file) {
    case 'mp3':
      return new Mp3();
    case 'mp4':
      return new Mp4();
  }
}

// client
const player = new Player(createFile('mp3'));
player.play();
Enter fullscreen mode Exit fullscreen mode

Abstraction is not only for objects this can be found everywhere.

No abstraction.

// consumer
if (15 > 10 && 15 < 20)) {
  console.log('I inside of range');
}
Enter fullscreen mode Exit fullscreen mode

With abstraction.

// Abstraction
const initRange = (value) => (min, max) => value > min && value < max;
const rangeOf = initRange(15);

// Consumer
// Abstracted
if (rangeOf(10, 20)) {
  console.log('I inside of range');
}
Enter fullscreen mode Exit fullscreen mode

Polymorphism
It can perform multiple implementations by passing objects or information.

interface Action {
  act();
}
class People {
  action: Action;
  constructor(action: Action) {
    this.action = action;
  }
  setAction(action: Action) {
    this.action = action;
  }
  perform() {
    this.action.act();
  }
}

class Sing implements Action {
  act() {
    console.log('Singing');
  }
}
class Dance implements Action {
  act() {
    console.log('Dancing');
  }
}

const sarah = new People(new Sing())
sarah.act(); // Singing
sarah.setAction(new Dance());
sarah.act(); // Dancing
Enter fullscreen mode Exit fullscreen mode

Learn Design Patterns
Object-Oriented Programming is sometimes hard to appreciate, but I encourage you to learn and read Design Patterns because it is based on scenarios where you can have more ideas and appreciate how useful each pillar is based on your use case.

Thank you
Thank you for finishing reading my blog, I appreciate it, and feel free to contact me with my email to improve my writing I hope you learned a lot ;)

Follow me
Follow me on TWITTER in which I post some short ideas and share what I learn daily.

Top comments (0)