Skip to main content

SOLID principles

Sure, let's regroup everything from the beginning, covering the SOLID principles with their definitions and JavaScript examples. I'll ensure to use the examples and definitions you previously provided.

### 1. Single Responsibility Principle (SRP)
**Definition:** A class should have one, and only one, reason to change. This means that a class should only have one job or responsibility.

**Example:**
Think of a chef in a restaurant. If a chef's job is to cook food, that's their single responsibility. If the chef starts taking orders, serving tables, and managing inventory all at once, it becomes messy. Just like in coding, a class should have one main purpose, like handling user authentication or managing database connections.

**Bad Example (Violating SRP):**
```javascript
class User {
    constructor(username, password, email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    validatePassword(password) {
        return this.password === password;
    }

    sendWelcomeEmail() {
        console.log(`Sending welcome email to ${this.email}`);
    }

    saveToDatabase() {
        console.log(`Saving user ${this.username} to the database`);
    }
}

// Usage
const user = new User('john_doe', 'securepassword', 'john@example.com');
if (user.validatePassword('securepassword')) {
    user.sendWelcomeEmail();
}
user.saveToDatabase();
```

**Good Example (Adhering to SRP):**
```javascript
class User {
    constructor(username, password, email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }
}

class PasswordValidator {
    static validate(user, password) {
        return user.password === password;
    }
}

class EmailService {
    static sendWelcomeEmail(user) {
        console.log(`Sending welcome email to ${user.email}`);
    }
}

class UserRepository {
    static save(user) {
        console.log(`Saving user ${user.username} to the database`);
    }
}

// Usage
const user = new User('john_doe', 'securepassword', 'john@example.com');
if (PasswordValidator.validate(user, 'securepassword')) {
    EmailService.sendWelcomeEmail(user);
}
UserRepository.save(user);
```

### 2. Open/Closed Principle (OCP)
**Definition:** Software entities should be open for extension but closed for modification. This means that the behavior of a module can be extended without modifying its source code.

**Example:**
Consider a remote control for a TV. If you want to add a new feature, like a volume control for the soundbar, you shouldn't have to open up the remote and modify its circuitry. Instead, you could add an external adapter that extends the functionality without altering the remote itself. In code, this means you can add new features by extending existing classes or modules without modifying their internal code.

**Bad Example (Violating OCP):**
```javascript
class RemoteControl {
    turnOnTV() {
        console.log('TV is ON');
    }

    turnOffTV() {
        console.log('TV is OFF');
    }

    increaseVolume() {
        console.log('TV volume increased');
    }

    decreaseVolume() {
        console.log('TV volume decreased');
    }

    // Adding new functionality directly to the class
    turnOnSoundbar() {
        console.log('Soundbar is ON');
    }

    turnOffSoundbar() {
        console.log('Soundbar is OFF');
    }
}

// Usage
const remote = new RemoteControl();
remote.turnOnTV();
remote.turnOnSoundbar();
```

**Good Example (Adhering to OCP):**
```javascript
class RemoteControl {
    turnOn() {
        console.log('Device is ON');
    }

    turnOff() {
        console.log('Device is OFF');
    }
}

class TVRemoteControl extends RemoteControl {
    increaseVolume() {
        console.log('TV volume increased');
    }

    decreaseVolume() {
        console.log('TV volume decreased');
    }
}

class SoundbarRemoteControl extends RemoteControl {
    adjustBass(level) {
        console.log(`Soundbar bass adjusted to level ${level}`);
    }
}

// Usage
const tvRemote = new TVRemoteControl();
tvRemote.turnOn();
tvRemote.increaseVolume();

const soundbarRemote = new SoundbarRemoteControl();
soundbarRemote.turnOn();
soundbarRemote.adjustBass(5);
```

### 3. Liskov Substitution Principle (LSP)
**Definition:** Objects of a superclass should be replaceable with objects of a subclass without altering the correctness of the program. This means that subclasses should behave in a way that doesn't break the expectations set by the superclass.

**Example:**
Imagine you have a toy box where each toy fits into a specific slot. You expect any toy to fit in its corresponding slot without any issues. If you suddenly have a toy that doesn't fit properly, like a round ball in a square hole, it breaks the principle. Similarly, in coding, subclasses should be usable wherever their parent class is expected without causing unexpected behavior.

**Bad Example (Violating LSP):**
```javascript
class Bird {
    fly() {
        console.log('Bird is flying');
    }
}

class Penguin extends Bird {
    fly() {
        throw new Error('Penguins cannot fly');
    }
}

// Usage
function makeBirdFly(bird) {
    bird.fly();
}

const bird = new Bird();
const penguin = new Penguin();

makeBirdFly(bird);    // Works as expected
makeBirdFly(penguin); // Throws an error: Penguins cannot fly
```

**Good Example (Adhering to LSP):**
```javascript
class Bird {
    makeSound() {
        console.log('Bird is making a sound');
    }
}

class FlyingBird extends Bird {
    fly() {
        console.log('Flying bird is flying');
    }
}

class NonFlyingBird extends Bird {
    // No fly method
}

// Now Penguin is a NonFlyingBird, not a FlyingBird
class Penguin extends NonFlyingBird {
    swim() {
        console.log('Penguin is swimming');
    }
}

// Usage
function makeBirdFly(bird) {
    if (bird instanceof FlyingBird) {
        bird.fly();
    } else {
        console.log('This bird cannot fly');
    }
}

const bird = new FlyingBird();
const penguin = new Penguin();

makeBirdFly(bird);    // Works as expected: Flying bird is flying
makeBirdFly(penguin); // Works as expected: This bird cannot fly
```

### 4. Interface Segregation Principle (ISP)
**Definition:** Clients should not be forced to depend on interfaces they do not use. Instead of having large interfaces that cater to multiple clients, it's better to have smaller, more specific interfaces tailored to the needs of individual clients. This principle prevents interface pollution and minimizes coupling between components.

**Example:**
Instead of having a large interface that covers many methods, create smaller, more specific interfaces. This way, classes can implement only the interfaces relevant to them.

**Bad Example (Violating ISP):**
```javascript
class Machine {
    start() {
        throw new Error('Method not implemented');
    }

    stop() {
        throw new Error('Method not implemented');
    }

    print() {
        throw new Error('Method not implemented');
    }

    scan() {
        throw new Error('Method not implemented');
    }

    fax() {
        throw new Error('Method not implemented');
    }
}

class MultiFunctionPrinter extends Machine {
    start() {
        console.log('Printer starting...');
    }

    stop() {
        console.log('Printer stopping...');
    }

    print() {
        console.log('Printing document...');
    }

    scan() {
        console.log('Scanning document...');
    }

    fax() {
        console.log('Sending fax...');
    }
}

class SimplePrinter extends Machine {
    start() {
        console.log('Printer starting...');
    }

    stop() {
        console.log('Printer stopping...');
    }

    print() {
        console.log('Printing document...');
    }

    // Methods not used by SimplePrinter
    scan() {
        throw new Error('SimplePrinter cannot scan');
    }

    fax() {
        throw new Error('SimplePrinter cannot fax');
    }
}

// Usage
const printer = new SimplePrinter();
printer.print();  // Works as expected
printer.scan();   // Throws error: SimplePrinter cannot scan
```

**Good Example (Adhering to ISP):**
```javascript
class Startable {
    start() {
        throw new Error('Method not implemented');
    }
}

class Stoppable {
    stop() {
        throw new Error('Method not implemented');
    }
}

class Printable {
    print() {
        throw new Error('Method not implemented');
    }
}

class Scannable {
    scan() {
        throw new Error('Method not implemented');
    }
}

class Faxable {
    fax() {
        throw new Error('Method not implemented');
    }
}

// Now each machine only implements the interfaces it needs
class MultiFunctionPrinter extends Startable, Stoppable, Printable, Scannable, Faxable {
    start() {
        console.log('Printer starting...');
    }

    stop() {
        console.log('Printer stopping...');
    }

    print() {
        console.log('Printing document...');
    }

    scan() {
        console.log('Scanning document...');
    }

    fax() {
        console.log('Sending fax...');
    }
}

class SimplePrinter extends Startable, Stoppable, Printable {
    start() {
        console.log('Printer starting...');
    }

    stop() {
        console.log('Printer stopping...');
    }

    print() {
        console.log('Printing document...');
    }
}

// Usage
const printer = new SimplePrinter();
printer.print();  // Works as expected
```

### 5. Dependency Inversion Principle (DIP)
**Definition:** High-level modules should not depend on low-level modules

. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions. This principle encourages decoupling between modules and promotes flexibility, extensibility, and testability in the codebase.

**Example:**
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions. This principle encourages decoupling between modules and promotes flexibility, extensibility, and testability in the codebase.

**Bad Example (Violating DIP):**
```javascript
class MySQLDatabase {
    connect() {
        console.log('Connecting to MySQL database...');
    }

    saveUser(user) {
        console.log(`Saving user ${user.name} to MySQL database...`);
    }
}

class UserService {
    constructor() {
        this.database = new MySQLDatabase();
    }

    addUser(user) {
        this.database.connect();
        this.database.saveUser(user);
    }
}

// Usage
const userService = new UserService();
userService.addUser({ name: 'John Doe' });
```

**Good Example (Adhering to DIP):**
```javascript
// Abstraction (interface) for database operations
class Database {
    connect() {
        throw new Error('Method not implemented');
    }

    saveUser(user) {
        throw new Error('Method not implemented');
    }
}

// MySQLDatabase implements the Database interface
class MySQLDatabase extends Database {
    connect() {
        console.log('Connecting to MySQL database...');
    }

    saveUser(user) {
        console.log(`Saving user ${user.name} to MySQL database...`);
    }
}

// UserService depends on the Database abstraction
class UserService {
    constructor(database) {
        this.database = database;
    }

    addUser(user) {
        this.database.connect();
        this.database.saveUser(user);
    }
}

// Usage
const mySQLDatabase = new MySQLDatabase();
const userService = new UserService(mySQLDatabase);
userService.addUser({ name: 'John Doe' });

// We can easily switch to another database implementation without modifying UserService
class MongoDBDatabase extends Database {
    connect() {
        console.log('Connecting to MongoDB database...');
    }

    saveUser(user) {
        console.log(`Saving user ${user.name} to MongoDB database...`);
    }
}

const mongoDBDatabase = new MongoDBDatabase();
const userServiceMongo = new UserService(mongoDBDatabase);
userServiceMongo.addUser({ name: 'Jane Doe' });
```

By understanding and applying the SOLID principles, you can create software that is more modular, maintainable, and scalable.

Comments

Popular posts from this blog

Globant part 1

 1)call,apply,bind example? Ans: a. call Method: The call method is used to call a function with a given this value and arguments provided individually. Javascript code: function greet(name) {   console.log(`Hello, ${name}! I am ${this.role}.`); } const person = {   role: 'developer' }; greet.call(person, 'Alice'); // Output: Hello, Alice! I am developer. In this example, call invokes the greet function with person as the this value and passes 'Alice' as an argument. b. apply Method: The apply method is similar to call, but it accepts arguments as an array. Javascript code: function introduce(language1, language2) {   console.log(`I can code in ${language1} and ${language2}. I am ${this.name}.`); } const coder = {   name: 'Bob' }; introduce.apply(coder, ['JavaScript', 'Python']); // Output: I can code in JavaScript and Python. I am Bob. Here, apply is used to invoke introduce with coder as this and an array ['JavaScript', 'Pyt...

Node.js: Extract text from image using Tesseract.

In this article, we will see how to extract text from images using Tesseract . So let's start with this use-case, Suppose you have 300 screenshot images in your mobile which has an email attribute that you need for some reason like growing your network or for email marketing. To get an email from all these images manually into CSV or excel will take a lot of time. So now we will check how to automate this thing. First, you need to install Tesseract OCR( An optical character recognition engine ) pre-built binary package for a particular OS. I have tested it for Windows 10. For Windows 10, you can install  it from here. For other OS you make check  this link. So once you install Tesseract from windows setup, you also need to set path variable probably, 'C:\Program Files\Tesseract-OCR' to access it from any location. Then you need to install textract library from npm. To read the path of these 300 images we can select all images and can rename it to som...

CSS INTERVIEW QUESTIONS SET 2

  You make also like this CSS interview question set 1. Let's begin with set 2, 5)What is the difference between opacity 0 vs display none vs visibility hidden? Property           | occupies space | consumes clicks | +--------------------+----------------+-----------------+ | opacity: 0         |        yes      |        yes       | +--------------------+----------------+-----------------+ | visibility: hidden |        yes       |        no        | +--------------------+----------------+-----------------+ | display: none      |        no       |        no        | When we say it consumes click, that means it also consumes other pointer-events like onmousedown,onmousemove, etc. In e...