The Single Responsibility Principle states that a class should have only one reason to change, meaning it should have only one responsibility. This promotes separation of concerns, making the code easier to understand and maintain.
class UserService {
createUser(user: User): void {
// Logic to create a user
}
deleteUser(userId: number): void {
// Logic to delete a user
}
}
class EmailService {
sendEmail(email: Email): void {
// Logic to send an email
}
}
The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that you should be able to add new features or functionality without changing existing code.
interface Shape {
area(): number;
}
class Rectangle implements Shape {
width: number;
height: number;
area(): number {
return this.width * this.height;
}
}
class Circle implements Shape {
radius: number;
area(): number {
return Math.PI * this.radius * this.radius;
}
}
const shapes: Shape[] = [new Rectangle(), new Circle()];
const totalArea = shapes.reduce((acc, shape) => acc + shape.area(), 0);
The Liskov Substitution Principle states that objects of a superclass should be able to be replaced with objects of a subclass without affecting the correctness of the program. This ensures that the derived classes are fully substitutable for their base classes.
class Bird {
fly(): void {
console.log("I can fly.");
}
}
class Penguin extends Bird {
fly(): void {
console.log("I can't fly.");
}
}
const birds: Bird[] = [new Bird(), new Penguin()];
birds.forEach((bird) => bird.fly());
The Interface Segregation Principle states that no client should be forced to depend on interfaces it does not use. This principle helps to create smaller, more specialized interfaces that focus on specific functionalities.
interface Drawable {
draw(): void;
}
interface Updatable {
update(): void;
}
class Graphic implements Drawable, Updatable {
draw(): void {
console.log("Drawing a graphic.");
}
update(): void {
console.log("Updating the graphic.");
}
}
class Animation implements Updatable {
update(): void {
console.log("Updating the animation.");
}
}
const graphic = new Graphic();
graphic.draw(); // Drawing a graphic.
graphic.update(); // Updating the graphic.
const animation = new Animation();
animation.update(); // Updating the animation.
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules, but both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions. This principle promotes decoupling and increases maintainability.
interface DataStorage {
readData(): string;
}
class LocalStorage implements DataStorage {
readData(): string {
return "Data from local storage.";
}
}
class CloudStorage implements DataStorage {
readData(): string {
return "Data from cloud storage.";
}
}
class DataManager {
constructor(private dataStorage: DataStorage) {}
fetchData(): void {
console.log(this.dataStorage.readData());
}
}
const localStorage = new LocalStorage();
const dataManager1 = new DataManager(localStorage);
dataManager1.fetchData(); // Data from local storage.
const cloudStorage = new CloudStorage();
const dataManager2 = new DataManager(cloudStorage);
dataManager2.fetchData(); // Data from cloud storage.
The DRY principle emphasizes avoiding code duplication and encourages the use of abstractions, modularization, and code reusability.
function greet(name: string): void {
console.log(`Hello, ${name}!`);
}
greet("Alice"); // Hello, Alice!
greet("Bob"); // Hello, Bob!
The KISS principle encourages keeping code simple and easy to understand, avoiding unnecessary complexity whenever possible.
function add(a: number, b: number): number {
return a + b;
}
const result = add(1, 2); // 3
The YAGNI principle advises focusing on implementing only the features that are needed right now and resisting the urge to add features that might be needed in the future.
class BasicCalculator {
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
}
const calculator = new BasicCalculator();
console.log(calculator.add(1, 2)); // 3
console.log(calculator.subtract(4, 1)); // 3
The Separation of Concerns principle advocates separating the different aspects of an application into distinct components to promote modularity, maintainability, and reusability.
class DataFetcher {
fetchData(): string {
return "Fetched data";
}
}
class DataProcessor {
processData(data: string): string {
return data.toUpperCase();
}
}
class App {
private fetcher = new DataFetcher();
private processor = new DataProcessor();
run(): void {
const data = this.fetcher.fetchData();
const processedData = this.processor.processData(data);
console.log(processedData);
}
}
const app = new App();
app.run(); // FETCHED DATA
This principle suggests using composition (combining simple objects to create more complex ones) instead of inheritance (creating new objects by extending existing ones) to achieve better flexibility and maintainability in object-oriented programming.
interface Flyable {
fly(): void;
}
class Bird {
constructor(private flyable: Flyable) {}
performFly(): void {
this.flyable.fly();
}
}
class SimpleFlying implements Flyable {
fly(): void {
console.log("Simple flying");
}
}
class NoFlying implements Flyable {
fly(): void {
console.log("Can't fly");
}
}
const simpleFlyingBird = new Bird(new SimpleFlying());
simpleFlyingBird.performFly(); // Simple flying
const noFlyingBird = new Bird(new NoFlying());
noFlyingBird.performFly(); // Can't fly
Also known as the principle of least knowledge, this principle states that an object should have limited knowledge about other objects it interacts with, promoting loose coupling and reducing dependencies between objects.
class Bank {
private accounts: { [id: string]: number } = {};
getBalance(id: string): number {
return this.accounts[id] || 0;
}
deposit(id: string, amount: number): void {
this.accounts[id] = (this.accounts[id] || 0) + amount;
}
}
class Customer {
constructor(public id: string) {}
}
class BankTeller {
constructor(private bank: Bank) {}
checkBalance(customer: Customer): number {
return this.bank.getBalance(customer.id);
}
deposit(customer: Customer, amount: number): void {
this.bank.deposit(customer.id, amount);
}
}
const bank = new Bank();
const teller = new BankTeller(bank);
const customer = new Customer("1");
teller.deposit(customer, 100);
console.log(teller.checkBalance(customer)); // 100