SOLID on the example of JavaScript with chatGPT

Probably everyone has already heard about the technology ChatGPT. In short, I recently decided to use it myself and be of no benefit to the programmer, using an example I will tell you how I understood another SOLID example and share an example with those who are just delving into design patterns.

Inquiry:

write me javascript code with solid principles and comment code

Answer:

// Single Responsibility Principle: Каждая функция должна иметь одну и только одну ответственность
function getUserById(userId) {
  // Эта функция отвечает за получение пользователя по его ID
  return fetch(`https://example.com/users/${userId}`)
    .then(response => response.json())
    .then(user => {
      if (!user) {
        throw new Error(`User with ID ${userId} not found`);
      }
      return user;
    });
}

// Open/Closed Principle: Модуль должен быть открыт для расширения, но закрыт для модификации
class UserService {
  constructor(userRepository) {
    // Класс UserService закрыт для модификации, но его можно расширить, передав другую реализацию userRepository.
    this.userRepository = userRepository;
  }

  getUserById(userId) {
    return this.userRepository.getUserById(userId);
  }
}

// Тут по подробней описал снизу, чтобы окончательно понимать суть принципа...
// Liskov Substitution Principle: Подтипы должны иметь возможность заменять свои родительские типы без изменения корректности программы.
class CachedUserRepository {
  constructor(userRepository) {
    this.userRepository = userRepository;
    this.cache = new Map();
  }

  getUserById(userId) {
    // CachedUserRepository является подтипом UserRepository и может использоваться в качестве замены, не влияя на корректность программы.
    if (this.cache.has(userId)) {
      return Promise.resolve(this.cache.get(userId));
    }
    return this.userRepository.getUserById(userId).then(user => {
      this.cache.set(userId, user);
      return user;
    });
  }
}

// Interface Segregation Principle: Не следует заставлять модуль реализовывать интерфейсы, которые он не использует.
class UserController {
  constructor(userService) {
    this.userService = userService;
  }

  getUserById(req, res) {
    const userId = req.params.id;
    this.userService.getUserById(userId)
      .then(user => res.json(user))
      .catch(err => res.status(404).json({ error: err.message }));
  }
}

// Dependency Inversion Principle: Модули высокого уровня не должны зависеть от модулей низкого уровня.
const userRepository = new UserRepository();
const cachedUserRepository = new CachedUserRepository(userRepository);
const userService = new UserService(cachedUserRepository);
const userController = new UserController(userService);

In short, we have userRepository be it a file or a module that has a getUserByID function, which is already an “S” principle.

Next we create a class User Servicewhere the constructor will be this userRepositoryin it we encapsulate the work of executing the function getUserByID, in the future we can extend this class for other methods, here we have the principle under the letter “O”.

“L” – is that we do not change the parent class, but only create a subclass that does not change the main logic of the parent class.

Inquiry:

tell me about Liskov Substitution Principle more in this example

Answer:

The Liskov Substitution Principle (LSP) states that subtypes should be able to replace their parent types without changing program correctness. In this example, the CachedUserRepository class is a subtype of the UserRepository class and can be used as a replacement for UserRepository without affecting program correctness.

The CachedUserRepository class is designed to cache the results of calls to the getUserById method, so if the same user ID is requested multiple times, you can return the cached value instead of making a new API call.

The CachedUserRepository class has a constructor that takes a UserRepository instance as an argument and uses that instance to actually call the API when there is no user in the cache.

In this example, the CachedUserRepository class respects the Liskov substitution principle because it has the same method as the UserRepository class, it just adds another caching function and doesn’t change the behavior or return value of the getUserById method.

Therefore, wherever a UserRepository is used in an application, CachedUserRepository can also be used as a replacement without breaking existing code.

The “I” principle of separation of interfaces states that a module should not be forced to implement interfaces that it does not use.

Class UserController has a constructor that takes an instance User Service as an argument. Class UserController uses only method getUserById from the class User Service and does not use any other methods.

Class User Service may have other methods like getAllUsers, createUser, updateUser etc. But the class UserController uses only getUserByIdit doesn’t need to implement all the methods, and it doesn’t need to know about the existence of other methods.

Following ISP, class UserController is not forced to implement any unnecessary methods, which makes the code more flexible and easier to understand.

This approach makes the code more maintainable and easier to change as changes to the class User Service will only affect classes that actually use the modified methods, not classes that do not.

In addition, ISP allows you to create small, specialized interfaces, making them more reusable and flexible, making code more decoupled and less error prone.

“D” – the principle of dependency inversion, says that classes cannot be dependent on a low-level class, from the example in life that we cannot be the fathers of our fathers.

In conclusion… ChatGPT is great for documenting, or at least helping you find the answers you’re stuck on while learning.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *