Explain class implementation in TypeScript.
TypeScript brings robust object-oriented programming (OOP) capabilities to JavaScript through its enhanced support for classes. Building upon ECMAScript 2015 (ES6) classes, TypeScript adds powerful features like static typing, access modifiers, and interfaces, making it easier to build scalable and maintainable applications.
Core Concepts of TypeScript Classes
Classes in TypeScript serve as blueprints for creating objects (instances). They encapsulate data (properties) and behavior (methods) into a single unit, promoting modularity and reusability. TypeScript's type system ensures that class members are used correctly, catching potential errors at compile-time rather than runtime.
Basic Class Structure
A class is defined using the class keyword, followed by the class name. It can contain properties (variables) and methods (functions) that define the state and behavior of objects created from it.
class Greeter {
message: string; // Property
constructor(message: string) {
this.message = message;
}
greet() { // Method
console.log(this.message);
}
}
let greeter = new Greeter("Hello, TypeScript!");
greeter.greet(); // Output: Hello, TypeScript!
Constructors
The constructor is a special method that is called when a new instance of the class is created using the new keyword. It's primarily used to initialize class properties. TypeScript allows a shorthand for declaring and initializing properties directly in the constructor signature, especially with access modifiers.
class Point {
// Shorthand: public x and y are declared and initialized
constructor(public x: number, public y: number) {}
getDistance() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
}
let p = new Point(3, 4);
console.log(p.x); // Output: 3
console.log(p.getDistance()); // Output: 5
Access Modifiers: public, private, protected
TypeScript provides access modifiers to control the visibility and accessibility of class members (properties and methods):
public: Members are accessible from anywhere. This is the default modifier if none is specified.private: Members are only accessible from within the class they are declared in.protected: Members are accessible from within the class itself and from subclasses (derived classes).
class Animal {
public name: string;
private age: number;
protected species: string;
constructor(name: string, age: number, species: string) {
this.name = name;
this.age = age;
this.species = species;
}
describe() {
console.log(`This is a ${this.species} named ${this.name}.`);
// console.log(this.age); // Accessible here
}
}
class Dog extends Animal {
constructor(name: string, age: number) {
super(name, age, "Canine");
console.log(`Dog's species: ${this.species}`); // Protected member accessible in subclass
// console.log(this.age); // Error: Property 'age' is private and only accessible within class 'Animal'.
}
}
let myAnimal = new Animal("Leo", 5, "Lion");
console.log(myAnimal.name); // Accessible: 'Leo'
// console.log(myAnimal.age); // Error: Property 'age' is private
// console.log(myAnimal.species); // Error: Property 'species' is protected
Inheritance
TypeScript supports inheritance, allowing a class (subclass or derived class) to extend another class (superclass or base class) and inherit its properties and methods. The extends keyword is used for this purpose, and super() is used in the subclass's constructor to call the base class's constructor.
class Vehicle {
constructor(public wheels: number) {}
move(): void {
console.log("Vehicle is moving.");
}
}
class Car extends Vehicle {
constructor(public make: string, wheels: number) {
super(wheels); // Call the base class constructor
}
drive(): void {
console.log(`The ${this.make} car with ${this.wheels} wheels is driving.`);
}
}
let myCar = new Car("Tesla", 4);
myCar.move(); // Inherited method
myCar.drive(); // Own method
Implementing Interfaces
Classes can implement interfaces, which means they must adhere to the contract defined by the interface. This ensures that the class provides specific properties and methods, promoting consistent structure across different classes.
interface ILogger {
log(message: string): void;
warn(message: string): void;
}
class ConsoleLogger implements ILogger {
log(message: string): void {
console.log(`LOG: ${message}`);
}
warn(message: string): void {
console.warn(`WARN: ${message}`);
}
}
let logger: ILogger = new ConsoleLogger();
logger.log("This is a log message.");
Abstract Classes
Abstract classes are base classes from which other classes may be derived. They cannot be instantiated directly. They can contain abstract methods (methods declared without an implementation) that must be implemented by non-abstract derived classes, enforcing a common structure and behavior.
abstract class Shape {
constructor(public color: string) {}
abstract calculateArea(): number; // Abstract method, no implementation here
displayColor(): void {
console.log(`This shape is ${this.color}.`);
}
}
class Circle extends Shape {
constructor(color: string, public radius: number) {
super(color);
}
calculateArea(): number { // Implementation of abstract method
return Math.PI * this.radius * this.radius;
}
}
// let myShape = new Shape("blue"); // Error: Cannot create an instance of an abstract class.
let myCircle = new Circle("red", 10);
console.log(myCircle.calculateArea());
myCircle.displayColor();
Static Members
Static members (properties and methods) belong to the class itself, not to instances of the class. They are accessed directly on the class name, rather than on an object instance. They are useful for utility functions or properties that are shared across all instances or don't require an instance state.
class MathUtils {
static PI: number = 3.14159; // Static property
static calculateCircumference(radius: number): number { // Static method
return 2 * MathUtils.PI * radius;
}
}
console.log(MathUtils.PI); // Access static property directly
console.log(MathUtils.calculateCircumference(5)); // Access static method directly
In conclusion, TypeScript's class implementation provides a robust and type-safe way to build object-oriented applications. By leveraging features like access modifiers, inheritance, interfaces, abstract classes, and static members, developers can create well-structured, maintainable, and scalable codebases.