The OOP Fight : JavaScript Vs TypeScript
Tags: javascript, typescript, oop
Published: 2023-04-01 12:53:21

When I first started learning about JavaScript, I couldn’t help but notice how different it interacts with objects and entities compared to other languages that support object-oriented programming like C++ and Java, and how JavaScript is always categorized as an OOP Language by many. However, it has a different nature, as it is a special type of OOP that is **prototype-based** not the ***classical* class-based** OOP that we are mostly familiar with. That means that JS supports both functional and object-oriented programming; and that's why it handles objects and entities in a slightly different way.

However, despite the fact that TypeScript is a superset of JavaScript,  it offers several advantages over its predecessor, particularly when it comes to object-oriented programming. In this article, we will explore how TypeScript has enhanced OOP capabilities in comparison to JavaScript, and how it conforms to the SOLID principles with code examples.
<br>
##Table Of Contents
<!-- TOC start -->
- [TypeScript Key Features](#typescript-key-features)
  * [Strongly Typed Language](#first-strongly-typed-language)
  * [Classes and Interfaces](#second-classes-and-interfaces)
    + [Classes](#classes)
      - [Constructor Function in JavaScript](#constructor-function-in-javascript)
      - [Classes in JavaScript](#classes-in-javascript)
      - [Classes in TypeScript](#classes-in-typescript)
    + [Interfaces ](#interfaces)
      - [Type Aliases](#type-aliases)
  * [Access Modifiers ](#third-access-modifiers)
  * [Inheritance and Polymorphism](#fourth-inheritance-and-polymorphism)
  * [Abstract Classes](#fifth-abstract-classes)
  * [Generics](#sixth-generics)
  * [Intersection and Union Types](#seventh-intersection-and-union-types)
  * [Decorators](#eighth-decorators)
- [SOLID Principles ](#solid-principles)
  * [Single Responsibility Principle (SRP)](#first-single-responsibility-principle-srp)
  * [Open-Closed Principle (OCP)](#second-open-closed-principle-ocp)
  * [Liskov Substitution Principle (LSP)](#third-liskov-substitution-principle-lsp)
  * [Interface Segregation Principle (ISP)](#fourth-interface-segregation-principle-isp)
  * [Dependency Inversion Principle (DIP)](#fifth-dependency-inversion-principle-dip)
- [So .. Who Wins?](#so-who-wins)
- [References](#references)

<br>

---
<br>

<a name="typescript-key-features"></a>
## TypeScript Key Features


<a name="first-strongly-typed-language"></a>
### First : Strongly Typed Language


One of the most significant advantages of TypeScript over JavaScript is that it is a strongly typed language. This means that TS enforces types on variables, functions, and classes, ensuring that the code is more robust and less error-prone. In contrast, JavaScript is a dynamically typed language, which means that variables can change their types at runtime, leading to more bugs and less maintainable code.

```
// JavaScript
function add(a, b) {
  return a + b;
}
```
<br>
```
// TypeScript
function add(a: number, b: number): number {
  return a + b;
}
```
<br>
> In the above example, the TypeScript function add has types for its parameters (a and b) and return value. This makes it easier to understand the function's behavior and catch errors at compile-time. 

<br>
___________
<br>

<a name="second-classes-and-interfaces"></a>
### Second : Classes and Interfaces
Classes allow you to define blueprints for objects with properties and methods, while interfaces define the structure and types of objects. These features are mostly not available in JavaScript, which uses prototype-based inheritance instead of class-based inheritance.
<br>
<a name="classes"></a>
#### Classes

According to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), the JS class implementation is introduced in 2015, in ES6. It is based on the notation that classes are just *special functions*

> Classes are in fact "special functions", and just as you can define function expressions and function declarations, a class can be defined in two ways: a class expression or a class declaration.

<br>

<a name="constructor-function-in-javascript"></a>
##### Constructor Function in JavaScript

```
// JavaScript
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
}
```
Since TypeScript is a subset of JavaScript, under the hood, it implements classes the same way JS does. However, due to TS strongly typed nature, there are several properties added on top of JS that enhances readability, maintainability and reduces errors. In other words, TypeScript has full support for that syntax and also adds features on top of it, like member visibility, abstract classes, generic classes, arrow function methods, and a few others.
<br>
<a name="classes-in-javascript"></a>
##### Classes in JavaScript
<br>
```
// JavaScript

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
```

<a name="classes-in-typescript"></a>
##### Classes in TypeScript
<br>
```
class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  speak(): void {
    console.log(`${this.name} makes a noise.`);
  }
}

```
As you can see, there isn't a big difference in the general syntax between JS and TS when it comes to classes. However TypeScript Classes can be considered more readable, maintainable and less error prune. As we go through the key features of TypeScript, we will explore how TypeScript has full support for the class-based OOP.
<br>
<a name="interfaces"></a>
#### Interfaces 
JavaScript does not have support for interfaces, as it's inheritance is based on objects and not classes and due to it's dynamic nature. Moreover, JavaScript uses **[duck typing](https://en.wikipedia.org/wiki/Duck_typing)**.

> If it walks like a duck, and quacks like a duck, as far as JS cares, it’s a duck.


On the other hand, TypeScript has support for interfaces. `interface` and `type` are how TS can enforce structure on objects, making sure that objects have the expected properties.

TypeScript interfaces supports interface extending and adding fields to an existing interface  
```
// JavaScript
function printLabel(labelObj) {
  console.log(labelObj.label);
}
```
<br>
```
// TypeScript
interface LabelObj {
  label: string;
}

function printLabel(labelObj: LabelObj): void {
  console.log(labelObj.label);
}

printLabel({ label:"name" })
```

> In the above example, the JavaScript `printLabel` function takes an object with a `label` property. In TypeScript, an `interface` is defined for this object to ensure that it has a `label` property of type string. This makes it easier to catch errors at compile-time and write more maintainable code.  
  



<a name="type-aliases"></a>
##### Type Aliases
Types are another example of how Typescript enforces object structure. 

According to [TypeScript Documentation ](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces) :

> Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable

Here's an example

```
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

interface Animal {
favFood: string

}
```
<br>
```
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

interface Animal {
favFood: string

}
 // Error: Duplicate identifier 'Animal'.

```
<br>
___________
<br>
<a name="third-access-modifiers"></a>
### Third : Access Modifiers 

TypeScript also offers access modifiers, which allow you to restrict access to certain properties and methods of a class. Access modifiers like private and protected help to enforce encapsulation and prevent unauthorized access to the internal state of an object. JavaScript does not have access modifiers, making it more difficult to achieve encapsulation and maintain a clean code base.
<br>
Here is an example of how TypeScript access modifiers can help with encapsulation:
```
// JavaScript

function Person(age) {
   This.age =age;
}


Person.prototype.getAge = function() {
   Return this.age
}
```
<br>
Let's say that you want `Person` to be a class with a a private property called `age`. You want to create a method called `getAge()` that returns the value of the `age` property, but you don't want to allow direct access to the `age` property from outside the class.

```
// TypeScript
class Person {
 private age: number;


 constructor(age: number) {
   this.age = age;
 }


 public get getAge(): number {
   return this.age;
 }
}

```

By using access modifiers like `private`, `public`, and `protected`, you can control access to the internal state of an object, ensuring that it is only modified in a controlled manner. This helps to maintain encapsulation, which is an essential principle of OOP that promotes modularity, maintainability, and robustness.
<br>

Moreover, TypeScript has two additional access modifiers than the popular three mentioned above: `static` and `readonly`

- `static` properties: they can only be accessed from the class, not from an object instantiated from it and they can not be inherited.
- `readonly` properties: these properties can not be modified after initialization, they can only be read. They must be set at the class declaration or inside the constructor 

The example shows how the TS uses access modifiers inside the class. Note that `this` keyword can not be used to reference static properties and methods.

```
//TypeScript

class Employee {
private name: string;
readonly ssn : string;
private static  count: number =  0;


constructor(name:string, ssn:string){
this.name = name
this.ssn = ssn
Employee.count =+1;
}
public getName():string {
return this.name;
}
public static getCount():number {
   return Employee.count;
}
public getSSN():string{
   return this.ssn;
}


};


let emp:Employee = new Employee("Mike", "836527496");


//emp.count; // 'count' does not exist on type 'Employee'
//emp.ssn=5; // Cannot assign to 'ssn' because it is a read-only property

```
<br>
Now as mentioned before, JS `class` support was introduced in ES6, and with it some features of classes:

- `static` keyword

- private access modifier using `#` notation

- `get` and `set` keywords

Here's an example demonstrating the added features: 

```
//JavaScript

class Time {
    #hour = 0
    #minute = 0
    #second = 0
    static #GMT = 0
    constructor(hour, minute, second) {
        this.#hour = hour
        this.#minute = minute
        this.#second = second
    }
    static  get displayGMT() {
      	return Time.#GMT
    }

  	static set setGMT(gmt){
      Time.#GMT=gmt
    }
    get getHour(){
      return this.#hour
    }
   set setHour(hour){
       this.#hour=hour
    }

}
//console.log(Time.getHour()) // Error: Time.getHour is not a function
console.log(Time.getHour) //undefined
console.log(Time.displayGMT) //0
Time.setGMT = 2
myTime = new Time()
console.log(Time.#hour) //Error: reference to undeclared private field or method #hour
console.log(myTime.#hour) //Error: reference to undeclared private field or method #hour
console.log(Time.displayGMT) //2
```

> Despite having these features, their behavior is quite confusing to grasp at the first glance, moreover it's readability is less than it's TypeScript equivalent.


<br><br>
```
//TypeScript

class Time {
    private hour: number = 0
    private minute: number = 0
    private second: number = 0
    private static GMT: number = 0
    constructor(hour: number, minute: number, second: number) {
        this.hour = hour
        this.minute = minute
        this.second = second
    }
    static get displayGMT(): number {
        return Time.GMT
    }

    static set setGMT(gmt: number) {
        Time.GMT = gmt
    }
    get getHour(): number {
        return this.hour
    }
    set setHour(hour: number) {
        this.hour = hour
    }

}

//console.log(Time.getHour()) //Property 'getHour' does not exist on type 'typeof Time'.
console.log(Time.displayGMT) //0
Time.setGMT = 2
//myTime = new Time() //Expected 3 arguments, but got 0.
const myTime: Time = new Time(1, 2, 3)
//console.log(Time.hour) //Property 'hour' does not exist on type 'typeof Time'
//console.log(myTime.hour) //Property 'hour' is private and only accessible within class 'Time'.
console.log(Time.displayGMT) //2
```
<br>
___________
<br>
<a name="fourth-inheritance-and-polymorphism"></a>
### Fourth : Inheritance and Polymorphism

TypeScript supports inheritance and polymorphism, two essential concepts in OOP. Inheritance allows you to create a hierarchy of classes, with each subclass inheriting properties and methods from its parent class.

Polymorphism allows you to use a subclass object in place of a parent class object, making the code more flexible and reusable. JavaScript does support inheritance and polymorphism, but it uses a less intuitive and more verbose syntax.

```
// JavaScript
function Animal(name) {
 this.name = name;
}


Animal.prototype.speak = function() {
 console.log(`${this.name} makes a noise.`);
}


function Dog(name) {
 Animal.call(this, name);
}


Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;


Dog.prototype.speak = function() {
 console.log(`${this.name} barks.`);
}

```
> In the JavaScript example, inheritance is implemented using prototype-based inheritance, which can be difficult to read and maintain.  

<br>  
```
// TypeScript
class Animal {
 name: string;


 constructor(name: string) {
   this.name = name;
 }


 speak(): void {
   console.log(`${this.name} makes a noise.`);
 }
}


class Dog extends Animal {
 constructor(name: string) {
   super(name);
 }


 speak(): void {
   console.log(`${this.name} barks.`);
 }
}

```

>  In TypeScript, inheritance is implemented using the extends keyword, which makes the code more readable and easier to maintain. 
  
<br>
With the introduction of classes in ES6, `extends` keyword became supported as well with `class` implementations.

Here's an example:
```
class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(`${this.name} roars.`);
  }
}

const myLion = new Lion("Fuzzy");
// Fuzzy makes a noise.

myLion.speak();  // Fuzzy roars.


```  
<br>
___________
<br>
<a name="fifth-abstract-classes"></a>
### Fifth : Abstract Classes

TypeScript supports abstract classes, which are classes that cannot be instantiated directly. Abstract classes can only be used as base classes for other classes. This allows developers to define common functionality for a group of related classes without having to implement the functionality in each class. 

Here's an example:

```
// JavaScript
 function Vehicle()  
{  
    this.vehicleName="vehicleName";  
    throw new Error("You cannot create an instance of Abstract Class");  
}  
Vehicle.prototype.display=function()  
{  
    return "Vehicle is: "+this.vehicleName;  
}  
//Creating a constructor function  
function Bike(vehicleName)  
{  
    this.vehicleName=vehicleName;  
}  
//Creating object without using the function constructor  
Bike.prototype=Object.create(Vehicle.prototype); 

```  
> There is no direct JS Abstract class implementation, however there are ways/ workarounds that can be implemented to apply abstraction. Here in the JavaScript example above, we created a constructor function that throws an error if the constructor is called. And when it comes to the subclass `Bike`, using it’s prototype and accessing the Object create function to set it to the base class prototype.


<br>

```
// TypeScript
abstract class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  abstract speak(): void;
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }

  speak(): void {
    console.log(`${this.name} barks.`);
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }

  speak(): void {
    console.log(`${this.name} meows.`);
  }
}

```
> Unlike JavaScript, Typescript offers easy, direct and readable class abstraction implementation.  

<br>
___________
<br>
<a name="sixth-generics"></a>
### Sixth : Generics

TypeScript also supports generics, which allow you to create reusable code that can work with different types of data. Generics are especially useful when working with collections like arrays, where you may want to define a function or class that can work with any type of data. JavaScript does not have generics, making it more difficult to write generic code.

Here's a simple example of generics in TS:

```
// TypeScript
function identity<T>(arg: T): T {
 return arg;
}


let output1 = identity<string>("Hello");
let output2 = identity<number>(42);

```
<br>
However, this example can easily be created in JS with similar results, thus doesn’t really show why Generics are important, why their absence from JavaScript makes it difficult to write reusable strongly typed code.

Here's how :

```
// JavaScript
function identity(arg) {
   return arg;
}
let output1 = identity("Hello");
let output2 = identity(42);
```
<br>
**Now let's see their power in the example below:**

```
//JavaScript

function getArray(arr) {
   return new Array().concat(arr);
}
let myNumArr = getArray([10, 20, 30]);
let myStrArr = getArray(["Hello", "JavaScript"]);
myNumArr.push(40); // Correct 
myNumArr.push("Hello OOP"); // Correct 
myStrArr.push("Hello TypeScript"); // Correct 
myStrArr.push(40); // Correct 
console.log(myNumArr); // [10, 20, 30, 40, "Hello OOP"] 
console.log(myStrArr); // ["Hello", "JavaScript", "Hello TypeScript", 40]

```
<br>
In the above example, the `getArray()` function accepts an array. The `getArray()` function creates a new array, concatenates items to it and returns this new array. Since JavaScript doesn’t define data type enforcement, we can pass any type of items to the function. But, this may not be the correct way to add items. We have to add numbers to `myNumArr` and the strings to `myStrArr`, but we do not want to add numbers to the `myStrArr` or vice-versa.

To solve this, TypeScript introduced generics. With generics, the type variable only accepts the particular type that the user provides at declaration.

Here's the TypeScript to the previous example:

```
//TypeScript

function getArray<T>(arr: T[]): T[] {
    return new Array<T>().concat(arr);
}
let myNumArr = getArray<number>([10, 20, 30]);
let myStrArr = getArray<string>(["Hello", "JavaScript"]);
myNumArr.push(40); // Correct  
myNumArr.push("Hi! OOP"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'  
myStrArr.push("Hello TypeScript"); // Correct  
myStrArr.push(50); // Error: Argument of type 'number' is not assignable to parameter of type 'string'

console.log(myNumArr);
console.log(myStrArr);
```
<br>
___________
<br>
<a name="seventh-intersection-and-union-types"></a>
### Seventh : Intersection and Union Types

TypeScript supports intersection and union types, which are a way to combine types to create new types. Intersection types allow developers to create types that have all the properties and methods of two or more types. Union types allow developers to create types that can be one of two or more types.


Here's an example:

```
// TypeScript
interface Dog {
  name: string;
  breed: string;
}

interface Cat {
  name: string;
  color: string;
}

type Pet = Dog & Cat; // Intersection
type DogOrCat = Dog | Cat; // Union

let myPet: Pet = { name: "Fluffy", breed: "Poodle", color: "White" }; 
// Error: Property 'color' is missing in type 'Pet' but required in type 'Cat'.

let myDogOrCat: DogOrCat = { name: "Mittens", breed: "Tabby" };

```
> In the above example, the `Dog` and `Cat` interfaces are defined with different properties. The `Pet` type is defined as an intersection of the `Dog` and `Cat` interfaces, which means it has all the properties of both interfaces. The `DogOrCat` type is defined as a union of the `Dog` and `Cat` interfaces, which means it can be either a dog or a cat. The `myPet` variable is assigned an object that has all the properties of both the `Dog` and `Cat` interfaces, but TypeScript throws a compiler error because the color property is missing. The `myDogOrCat` variable is assigned an object that has the properties of the `Dog` interface.    

<br><br>

**Note** : It's not possible to define intersection and union types in JavaScript
<br>
___________
<br>
<a name="eighth-decorators"></a>
### Eighth : Decorators

TypeScript supports decorators, which are a way to add metadata to classes, methods, and properties. Decorators allow developers to write code that is more declarative and flexible. 

**Note:** Several libraries and frameworks are built based on this powerful feature, for example: _Angular_ and _Nestjs_.


Here is a simple method decorator example to demonstrate the syntax:

```
function myDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 console.log(`Decorating ${target.constructor.name}.${propertyKey}`);
}
class MyClass {
 @myDecorator
 myMethod() {
   console.log("Hello, world!");
 }
}
```
> In the above example, the `myDecorator` function is defined with three parameters: 
> - `target` (the class constructor)
> - `propertyKey` (the name of the decorated method)
> - `descriptor` (an object that describes the decorated method). 

> The `myMethod` method of the `MyClass` class is decorated with the `myDecorator` function. When the `myMethod` method is called, the decorator logs a message to the console.  

<br>

Now let’s review a more advanced example that shows an actual decorator use case, this example uses parameter decorator and method decorator

```
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
 let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
 existingRequiredParameters.push(parameterIndex);
 Reflect.defineMetadata( requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
 let method = descriptor.value!;
 descriptor.value = function () {
   let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
   console.log("paramas", requiredParameters)
   if (requiredParameters) {
     for (let parameterIndex of requiredParameters) {
       if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
         throw new Error("Missing required argument.");
       }
     }
   }
   return method.apply(this, arguments);
 };
}


class BugReport {
 type = "report";
 title: string;
 constructor(t: string) {
   this.title = t;
 }


 @validate
 print(@required verbose: boolean) {
   if (verbose) {
     return `type: ${this.type}\ntitle: ${this.title}`;
   } else {
    return this.title;
   }
 }
}


const br = new BugReport("Bug Report Message")


console.log(br.print()) // [ERR]: Missing required argument.
console.log(br.print(true)) // [LOG]: "type: report title: Bug Report Message"
```

> The `@required` decorator adds a metadata entry that marks the parameter as required. The `@validate` decorator then wraps the existing `greet` method in a function that validates the arguments before invoking the original method.

<br>

It’s important to mention that decorators, while useful and revolutionary, are an experimental feature in TypeScript. To use them you need to set the compiler target in `tsconfig.ts` to ES5 or higher and enable the ` experimentalDecorators`, moreover, in the previous example you will also need to install the `reflect-metadata` library and enable the `emitDecoratorMetadata` in the `tsconfig.ts` file as well

**Note**
I highly suggest that you check out the full documentation and article on decorators for more detailed explanations and examples. To do so, follow these links:

- https://mirone.me/a-complete-guide-to-typescript-decorator/

- https://www.typescriptlang.org/docs/handbook/decorators.html

---

<br>
<a name="solid-principles"></a>
## SOLID Principles 

SOLID is an acronym for five Object-Oriented design principles that aim to make software systems more maintainable, extensible, and robust. These principles are:

- S - Single Responsibility Principle (SRP)
- O - Open-Closed Principle (OCP)
- L - Liskov Substitution Principle (LSP)
- I - Interface Segregation Principle (ISP)
- D - Dependency Inversion Principle (DIP)

<br>

Let's see how TypeScript conforms to these principles with code examples.


<a name="first-single-responsibility-principle-srp"></a>
### First : Single Responsibility Principle (SRP)

The SRP states that a class should have only one reason to change. In other words, a class should be responsible for only one thing. This principle helps to promote modularity, maintainability, and testability.
In TypeScript, you can use classes and modules to achieve the SRP.

Here's an example:

```
// Account.ts
export class Account {
  private balance: number;

  constructor(balance: number) {
    this.balance = balance;
  }

  public getBalance(): number {
    return this.balance;
  }

  public deposit(amount: number): void {
    this.balance += amount;
  }

  public withdraw(amount: number): void {
    if (amount > this.balance) {
      throw new Error('Insufficient funds');
    }

    this.balance -= amount;
  }
}

// Bank.ts
import { Account } from './Account';

export class Bank {
  private accounts: Account[];

  constructor() {
    this.accounts = [];
  }

  public openAccount(balance: number): Account {
    const account = new Account(balance);
    this.accounts.push(account);
    return account;
  }

  public closeAccount(account: Account): void {
    const index = this.accounts.indexOf(account);
    if (index === -1) {
      throw new Error('Account not found');
    }

    this.accounts.splice(index, 1);
  }
}

```

> In this example, the `Account` class is responsible for managing an account's balance, while the `Bank` class is responsible for managing a collection of accounts. By separating these responsibilities into different classes, we ensure that each class has only one reason to change, making the code more modular, maintainable, and testable. 

<br>
___________
<br>
<a name="second-open-closed-principle-ocp"></a>
### Second : Open-Closed Principle (OCP)

The OCP states that a class should be open for extension but closed for modification. In other words, you should be able to add new functionality to a class without modifying its existing code. This principle helps to promote extensibility and maintainability.
In TypeScript, you can use inheritance and interfaces to achieve the OCP.

Here's an example:

```
// Shape.ts
export interface Shape {
  getArea(): number;
}

// Rectangle.ts
import { Shape } from './Shape';

export class Rectangle implements Shape {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }

  public getArea(): number {
    return this.width * this.height;
  }
}

// Circle.ts
import { Shape } from './Shape';

export class Circle implements Shape {
  private radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  public getArea(): number {
    return Math.PI * this.radius ** 2;
  }
}

```

> In this example, the `Shape` interface defines a contract for shapes that have an area. The `Rectangle` and `Circle` classes implement the `Shape` interface, providing their own implementations of the `getArea()` method. By using the `Shape` interface, we can add new shapes to the system without modifying the existing code, making the code more extensible and maintainable.



<br>
___________
<br>
<a name="third-liskov-substitution-principle-lsp"></a>
### Third : Liskov Substitution Principle (LSP)

The LSP states that a subclass should be substitutable for its parent class without affecting the correctness of the program. In other words, you should be able to use a subclass object in place of a parent class object without causing any problems. This principle helps to promote polymorphism and extensibility.
In TypeScript, you can use inheritance and interfaces to achieve the LSP

Here's an example:

```
// Vehicle.ts
export class Vehicle {
  public startEngine(): void {
    console.log('Engine started');
  }

  public stopEngine(): void {
    console.log('Engine stopped');
  }
}

// Car.ts
import { Vehicle } from './Vehicle';

export class Car extends Vehicle {
  public drive(): void {
    console.log('Driving');
  }
}

// RaceCar.ts
import { Car } from './Car';

export class RaceCar extends Car {
  public drive(): void {
    console.log('Driving fast

```

> In this example, the `Vehicle` class defines a basic set of methods for a vehicle, including starting and stopping the engine. The `Car` class extends the `Vehicle` class, adding a `drive()` method that is specific to cars. The `RaceCar` class extends the Car class, adding a `drive()` method that is specific to race cars.

By using inheritance and the LSP, we can use a `RaceCar` object in place of a `Vehicle` or `Car` object without affecting the correctness of the program. This promotes polymorphism and extensibility, making the code more flexible and reusable.  

<br>
___________
<br>
<a name="fourth-interface-segregation-principle-isp"></a>
### Fourth : Interface Segregation Principle (ISP)

The ISP states that a client should not be forced to depend on methods it does not use. In other words, you should design interfaces that are specific to each client's needs, rather than creating a monolithic interface that includes methods for all clients. This principle helps to promote modularity and maintainability.
In TypeScript, you can use interfaces to achieve the ISP. 

Here's an example:

```
// Database.ts
export interface Database {
  connect(): void;
  disconnect(): void;
  query(sql: string): any[];
}

// SqliteDatabase.ts
import { Database } from './Database';

export class SqliteDatabase implements Database {
  public connect(): void {
    console.log('Connecting to SQLite database');
  }

  public disconnect(): void {
    console.log('Disconnecting from SQLite database');
  }

  public query(sql: string): any[] {
    console.log(`Executing SQL query: ${sql}`);
    return [];
  }
}

// RedisDatabase.ts
import { Database } from './Database';

export class RedisDatabase implements Database {
  public connect(): void {
    console.log('Connecting to Redis database');
  }

  public disconnect(): void {
    console.log('Disconnecting from Redis database');
  }

  public query(sql: string): any[] {
    throw new Error('Redis does not support SQL queries');
  }
}

```

> In this example, the `Database` interface defines a contract for databases that can connect, disconnect, and execute SQL queries. The `SqliteDatabase` and `RedisDatabase` classes implement the `Database` interface, providing their own implementations of the methods.

By using interfaces and the ISP, we can create specific interfaces for each client's needs, rather than creating a monolithic interface that includes methods for all clients. This promotes modularity and maintainability, making the code more flexible and reusable.


<br>
___________
<br>
<a name="fifth-dependency-inversion-principle-dip"></a>
### Fifth :Dependency Inversion Principle (DIP)

The DIP states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. In other words, you should design your system so that high-level modules can be easily swapped out with other modules that implement the same abstractions. This principle helps to promote extensibility and maintainability.
In TypeScript, you can use inversion of control (IoC) and dependency injection (DI) to achieve the DIP. 

Here's an example:

```
// Logger.ts
export interface Logger {
  log(message: string): void;
}

// ConsoleLogger.ts
import { Logger } from './Logger';

export class ConsoleLogger implements Logger {
  public log(message: string): void {
    console.log(message);
  }
}

// Service.ts
import { Logger } from './Logger';

export class Service {
  private logger: Logger;

  constructor(logger: Logger) {
    this.logger = logger;
  }

  public doSomething(): void {
    this.logger.log('Doing something');
  }
}

```

> In this example, the `Logger` interface defines a contract for loggers that can log messages. The `ConsoleLogger` class implements the `Logger` interface, providing its own implementation of the `log()` method. The `Service` class depends on the `Logger` interface, rather than depending on a specific logger implementation. This allows us to easily swap out the `ConsoleLogger` with another logger implementation that implements the same `Logger` interface.

By using IoC and DI, we can design our system so that high-level modules depend on abstractions rather than low-level modules, promoting extensibility and maintainability.


---

<br>
<a name="so-who-wins"></a>
## So .. Who Wins?
TypeScript has enhanced OOP capabilities than JavaScript, particularly when it comes to adhering to the SOLID principles.

By adhering to these principles, you can create code that is more modular, maintainable, and robust. 

In addition to adhering to the SOLID principles, TypeScript also provides several key features that make it a powerful tool for building complex software systems. These features make TypeScript a powerful tool for building complex software systems, particularly those that rely heavily on OOP principles. 

By using TypeScript, you can create code that is more reliable, maintainable, and extensible, which can save you time and money in the long run. Moreover, TypeScript provides a powerful set of tools for creating and managing complex software systems, making it an excellent choice for building large-scale applications.

 
---

<br>
<a name="references"></a>
## References
#### Feel free to check out the references for more details and points of view

- https://www.dotnettricks.com/learn/typescript/modules-namespaces
- https://www.tutorialsteacher.com/typescript/typescript-namespace
- https://www.typescriptlang.org/docs/handbook/
- https://mirone.me/a-complete-guide-to-typescript-decorator/
- https://blog.logrocket.com/typescript-mixins-examples-and-use-cases/
- https://birdeatsbug.com/blog/object-oriented-programming-in-typescript
- https://github.com/jafari-dev/oop-expert-with-typescript
- https://levelup.gitconnected.com/typescript-object-oriented-concepts-in-a-nutshell-cb2fdeeffe6e
- https://medium.com/@aliafsah1988/how-to-properly-use-typescript-oop-and-solid-in-modern-web-applications-e4ef88761154
- https://medium.com/@baruahd5/object-oriented-programming-oops-using-typescript-cdbffd7d0cf5
- https://dev.to/kevin_odongo35/object-oriented-programming-with-typescript-574o
- https://betterprogramming.pub/understand-object-oriented-programming-with-typescript-c4ff8afa40d
- https://css-tricks.com/the-flavors-of-object-oriented-programming-in-javascript/
- https://www.freecodecamp.org/news/object-oriented-programming-javascript/
- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming
- https://javascript.plainenglish.io/why-javascript-is-called-prototype-based-e9326562bf43
- https://www.javatpoint.com/typescript-generics
- https://www.freecodecamp.org/news/typescript-generics-use-case-example/
- https://www.javatpoint.com/javascript-oops-abstraction
- https://medium.com/swlh/typescript-doesnt-turn-javascript-into-a-class-based-oop-language-b31aae6114e3
- https://www.digitalocean.com/community/tutorials/how-to-use-classes-in-typescript
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#defining_getters_and_setters
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
- https://blog.bitsrc.io/understanding-public-and-private-fields-in-javascript-class-a281bf930566
- https://connorsmyth.com/implementing-interface-in-javascript/
- https://en.wikipedia.org/wiki/Duck_typing
- https://www.typescripttutorial.net/typescript-tutorial/typescript-getters-setters/
- https://www.javascripttutorial.net/es6/javascript-getters-and-setters/