Table of contents
Introduction
Hello devs π
As many of us know TypeScript is a superset of JavaScript that adds typing, classes, and interfaces to the language. These features make TypeScript a powerful language for OOP
TypeScript really excels when it comes to oop with JavaScript as It makes programming in an object-oriented fashion appear much like it does in other object-oriented languages such as C# or Java, .So devs who are familiar with those languages will easily and quickly learn typescript
For example:
Private access modifier in JS
class Base {
#x = 0;
constructor() {}
}
const b = new Base();
console.log(b.x); // this line will log undefined
Private access modifier in TS
class Base {
private x = 0; // Looks like C++ or Java Right π
constructor() {}
}
const b = new Base();
console.log(b.x); // this line will raise an error
Property x is private and only accessible within class Base
So let's start with the 4 pillars of OOP in TSπ
Inheritance
Inheritance is the ability to create new classes based on existing ones. In TypeScript, you can use the "extends" keyword to create a subclass that inherits properties and methods from a parent class. Here are some examples of inheritance in TypeScript:
Simple Inheritance
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log("Generic animal sound");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woo!");
}
}
const myDog = new Dog("Buddy");
console.log(myDog.name); // Output: Buddy
myDog.makeSound(); // Output: Woo!
Multiple Inheritance
In TypeScript, we canβt inherit or extend from more than one class, but Mixins helps us to get around that.
Mixins create partial classes that we can combine to form a single class that contains all the methods and properties from the partial classes you can check documentation thus here i am trying to focus on main and basic concepts of OOP .
Encapsulation π¬ π«
Encapsulation is the way of hiding the internal details of an object and exposing only what is necessary and what you specify . In TypeScript, you can use access modifiers like public, private, and protected to control access to class members.
Here are some examples of encapsulation in TypeScript:
Example 1: Private & public Access Modifiers
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
public greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
const person = new Person("John");
person.greet(); // Output: Hello, my name is John as
console.log(person.name); // Error
- greet method is a public method can be accessed anywhere.
- Property name is private so it isn't accessible outside the class.
Example 2: Protected Access Modifier
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
public bark() {
console.log(`Woo, my name is ${this.name}.`);
}
}
const dog = new Dog("Buddy");
dog.bark(); // Output: Woo, my name is Buddy.
console.log(dog.name); // Error:
- bark( ) method can access the protected member name as it is inside the child class dog
- Property name is protected and only accessible within class Animal and its subclasses
Abstraction
Abstraction identifies only the required characteristics of an object while ignoring how to implement details. In TypeScript, you can use abstract classes and Interfaces to implement abstraction concept.
Abstract classes
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract makeSound(): void;
move(distanceInMeters: number) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
makeSound() {
console.log("Woo!");
}
}
const myDog = new Dog("Buddy");
console.log(myDog.name); // Output: Buddy
myDog.move(10); // Output: Animal moved 10m.
myDog.makeSound(); // Output: Woo!
π Note:
- The class which implements an abstract class must call super( ) in the constructor.
- you have to implement abstract methods like makeSound that are extended from abstract classes
Interfaces
Generally, Interfaces serve as a contract in code. itβs all or nothing. When you implement an interface, you must implement everything defined in that interface methods to implement abstraction. Here's an example:
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log("Woo!");
}
}
const myDog = new Dog("Buddy");
console.log(myDog.name); // Output: Buddy
myDog.makeSound(); // Output: Woo!
- β οΈ In case we missed to implement whether the property name or method makeSound( ) we will get an error
polymorphism
Polymorphism is the ability of method to have different forms, whether toy are backend of frontend you are going to use Polymorphism may be
In Database Access: For example, a SQL database and a NoSQL database can both implement a common database interface and be used in the same way. This makes it easy to switch between different types of databases without changing the code that accesses the database.
Polymorphism can be represented by Overriding or Overloading.
Overriding
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log("Generic animal sound.");
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
makeSound() {
console.log("Woo!");
}
}
class Cat extends Animal {
makeSound() {
console.log("Meow!");
}
}
function makeAnimalSound(animal: Animal) {
animal.makeSound();
}
const myDog = new Dog("Buddy");
const myCat = new Cat("Whiskers");
makeAnimalSound(myDog); // Output: Woo!
makeAnimalSound(myCat); // Output: Meow!
Base class Animal has makeSound( ) method, subclasses Cat and Dog extend Animal have same method but with the overridden version
Overloading
Function Overloading with a different number of parameters and different types along with the same function name is not supported in normal way like :
class Greeter {
message: string;
constructor(message: string) {
this.message = message;
}
// Overload signatures
greet(personName: string): string {
return `${this.message}, ${personName}!`;
} // Duplicate function implementation error
greet(persons: string[]): string[] {
return persons.map((personName) => `${this.message}, ${personName}!`);
} // Duplicate function implementation error
}
I think As plain javascript couldn't differ between the overloaded methods as it doesn't support types, we have to do some work manually to check types of parameters in implementation.
So How can we implement overloading ?
- By defining a few overload signatures and one implementation signature.
class Greeter {
message: string;
constructor(message: string) {
this.message = message;
}
// Overload signatures
greet(person: string): string;
greet(persons: string[]): string[];
}
- The implementation signature has more generic parameter and return types and implements functionality.
Then our class become:
class Greeter {
message: string;
constructor(message: string) {
this.message = message;
}
// Overload signatures
greet(person: string): string;
greet(persons: string[]): string[];
// Implementation signature
greet(person: unknown): unknown {
if (typeof person === "string") {
return `${this.message}, ${person}!`;
} else if (Array.isArray(person)) {
return person.map((name) => `${this.message}, ${name}!`);
}
throw new Error("Unable to greet");
}
}
const hi = new Greeter("Hi");
hi.greet("Angela"); // Output 'Hi, Angela!'
hi.greet(["Pam", "Jim"]); // Output ['Hi, Pam!', 'Hi, Jim!']
π Note:
implementation signature is not callable. Only the overload signatures are callable.
greet("World"); // Overload signature is callable
greet(["Jane", "Joe"]); // Overload signature is callable
const someValue: unknown = "Unknown";
greet(someValue); // Error as implementation signature is NOT callable
Thank you for reading i hope you enjoyed and I hope this will help someone better understand Object Oriented Programming with TypeScript π
I will be happy if you share with me your comments and knowledge π
Top comments (0)