DEV Community

Cover image for Object Oriented Programming with TypeScript
Kevin Odongo
Kevin Odongo

Posted on

Object Oriented Programming with TypeScript

Hey Dev's

How has been your week? I hope everyone had a great week. In todays tutorial let us go through Object Oriented Programming (OOP).

Object Oriented programming (OOP) is a programming paradigm that relies on the concept of classes and objects. It is used to structure a software program into simple, reusable pieces of code blueprints (usually called classes), which are used to create individual instances of objects

Object programming is well-suited for programs that are large, complex and actively updated or maintained.

The Object class represents one of JavaScript's data types. It is used to store various keyed collections and more complex entities. Objects can be created using the Object() constructor or the object initializer / literal syntax.

Below is an example of an object class. In this example we have defined a Person with some attributes and a method.

// person class
class Person {
   name: string = '' // default
   age: number = 0 // default 

   greetings(){
     return this.name + ' ' + this.age
   }
}

// to create a new instance of a class
const person1: any = new Person()
person1.name = 'Kevin Odongo'
person1.age = 36 
console.log(person1.greetings()) // This should render Kevin Odongo 36
Enter fullscreen mode Exit fullscreen mode

Benefits of Object Oriented Programming

Some of the benefits of Object Oriented Programming are as follows:

  1. Easier debuging
  2. Reuse of code through inheritance
  3. Flexibility through polymorphism
  4. Effective problem solving
  5. Project decoupling (Separate project into groups)

Make this day great

Principles of Object Oriented Programming

Encapsulation.

The implementation and state of each object are privately held inside a defined boundary, or class. Other objects do not have access to this class or the authority to make changes but are only able to call a list of public functions, or methods. This characteristic of data hiding provides greater program security and avoids unintended data corruption.

Take a look at this example. In the example we have defined two object classes and created an instance of each. So in layman term encapsulation principle states that the new instance of motor1 cannot access the attributes of person1. If you try you should get such a warning.

Property 'age' does not exist on type 'Motor'

// person object
class Person {
   name: string = ''
   age: number = 0
}

// motor vehicle object
class Motor {
  make: string = ''
  model: string = ''
  color: string = ''
}

// create a new instance of each
const person1 = new Person()
const motor1 = new Motor()

// warning Property 'age' does not exist on type 'Motor'
motor1.age() 
Enter fullscreen mode Exit fullscreen mode

Abstraction.

Objects only reveal internal mechanisms that are relevant for the use of other objects, hiding any unnecessary implementation code. This concept helps developers more easily make changes and additions over time.

This is quite powerful in development and TypeScript provides us several ways of manupulating member visibilty in a class object.

Let us see how this work. In this example we have added public in all the Person attributes. By default all attributes are always public but for readability it is good practice to add it.

// person class object
class Person {
   public name: string = ''
   public age: string = ''

   public greetings(){
     return name + ' ' + age
   } 
}
Enter fullscreen mode Exit fullscreen mode

We can use the following to control attribute visibilities:

  • Public
  • Protected
  • Private
  • Static

Further we can use readonly which will prevents assignments to the field outside of the constructor.

Let us take another example to further understand this concept.

// class person

class Person {
   private readonly credentials: string = ''
   private name: string = ''
   private department: string = ''

   constructor(value: string){
      this.credentials = value
   }

   public setName(name: string): void {
      if(!this.credentials) return
      this.name = name
      // logic get department
      const userDepartment = axios.get(.....)
      if(userDepartment) this.department = userDepartment
   }

   public getName(){
     return `Employee name: ${this.name}, Department: ${this.department}`
   }
}
Enter fullscreen mode Exit fullscreen mode

In the example above if we try to create a new instance of Person without providing credentials we are going to get a warning and it wont compile. The first thing we should notice is credentials visibility is private readonly meaning it can only be updated within the constructor.

// warining An argument for 'value' was not provided.
const person1 = new Person()

// this will succeed
const person1 = new Person('123456')
Enter fullscreen mode Exit fullscreen mode

One more thing we should not is all our attributes are private and cannot be accessed outside the class object. Take note that we can only access public objects outside the class object.

Try this

// this will succeed
const person1 = new Person('123456')

person1. // this will automatically list for you all the properties accessible and you will NOTE all the private attributes cannot be accessed outside the class object.
Enter fullscreen mode Exit fullscreen mode

Inheritance.

Relationships and subclasses between objects can be assigned, allowing developers to reuse a common logic while still maintaining a unique hierarchy. This property of OOP forces a more thorough data analysis, reduces development time and ensures a higher level of accuracy.

Take a look at this example. You will NOTE that by extending the TeslaCompnany we have inherited all the public attributes and can call them when we create a new instance of TeslaEmployee. This can allow us have a base class and reuse the base class in different subsclasses.

// class company 
type Department = {
   name: string
}

type Employee = {
   name: string
   age: number
}

class TeslaCompany {
      private static role = "Admin"
      private readonly credentials: string = ''
      private departments: (Department)[] = []
      private employees: (Employee)[] = []

      constructor(cred: string) {
        this.credentials = cred
      }

      addDepartment(value: Department) {
        this.departments = [...this.departments, value]
      }

      addEmployee(value: Employee) {
        this.employees = [...this.employees, value]
      }
    }

    class TeslaEmployee extends TeslaCompany {
      private new_employee: Employee = { name: '', age: 0}

      public setName(name: Employee): void {
        this.new_employee = name

      }
}

const newTeslaEmployee = new TeslaEmployee('123456')
newTeslaEmployee.setName({ name: 'Kevin Odongo', age: 36 })
newTeslaEmployee.addDepartment({ name: 'Finance' })
newTeslaEmployee.addEmployee({ name: 'Kevin Odongo', age: 36 })
console.log(newTeslaEmployee)
Enter fullscreen mode Exit fullscreen mode

Polymorphism.

Objects can take on more than one form depending on the context. The program will determine which meaning or usage is necessary for each execution of that object, cutting down the need to duplicate code.

Let us understand this by going through an example. We will notice that the Person object is extended by two different subsclasses and take different context when each a new instance of each subsclass is created.

// class Person
class Person {
   public name: string = ''
   public role: string = '' 
}

class basketballPlayer extends Person {
     public setName(name: string){
       this.name = name
       this.role = 'BasketBall Player'
    }
    public getName(){
       return `User name: ${this.name} Role: ${this.role}`
    }
}

class golfPlayer extends Person {
    public setName(name: string){
       this.name = name
       this.role = 'Golf Player'
    }
    public getName(){
       return `User name: ${this.name} Role: ${this.role}`
    }
}

const person1 = new basketballPlayer()
const person2 = new golfPlayer()
person1.setName('Kevin Odongo')
person2.setName('Kevin Odongo')
console.log(person1.getName())
console.log(person2.getName())
Enter fullscreen mode Exit fullscreen mode

Celebration

Congratulations for the patience we have gone through the principles of Object Oriented Programming with examples to help us understand each concept.

Summary explanation

Encapsulation - Class objects are in a specific boundary. For example citizens in a country. They are all living within a specific boundary can governed by its laws.

Abstraction - Class objects can only share public properties and hide private properties. This creates a great way of controlling its behaviour.

Inheritance - We can inherit other classes and extend all there public properties.

Polymorphism - Objects can take various behaviour depending on the context.

Instead of using classes we can also use functions. Take the person class object we have been using through the example we can define it as follows:

Option 1

In this option to access Person object we do not need to create a new instance of the Person object.

// person object
var Person = (function () {
      let user: {
        name: ''
        age: 0
      }

      return {
        setName: function (value: any) {
          user = Object.assign({}, value)
        },

        getName: function (){
          return user
        },

        greetings: function() {
          return `Hello this is ${user.name}`
        }
      }

})()
Person.setName({ name: "Kevin Odongo", age: 36})
console.log(Person.getName())
console.log(Person.greetings())
Enter fullscreen mode Exit fullscreen mode

Option 2

In this example you will note we are using this to access the attributes of user within the function.

// person object
function Person() {
      const userProfile = {
        user: {
          name: '',
          age: 0,
        },

        setName: function (value: any) {
          this.user = Object.assign({}, value)
        },

        getName: function () {
          return this.user
        },

        greetings: function () {
          return `Hello this is ${this.user.name}`
        }
      }
  return userProfile
}

const person1 = Person()
person1.setName({ name: "Kevin Odongo", age: 36 })
console.log(person1.getName())
console.log(person1.greetings())
Enter fullscreen mode Exit fullscreen mode

Let me stop there we digest this and pick it from here in our next tutorial. I hope this will help someone better understand Object Oriented Programming with TypeScript.

Have a blessed week ahead of you!

Bye.

Top comments (1)

Collapse
 
enricodelarosa profile image
enricodelarosa

Inheritance - We can inherit other classes and extend all there public properties. * and protected properties as well right?