Что такое принцип замещения Лисков?

Принцип замещения Лискова (LSP) — это руководство, которое способствует эффективному функционированию программного обеспечения. В нем говорится, что если что-то определенного типа, вы должны иметь возможность заменить его другим, но похожим элементом, и программное обеспечение по-прежнему будет работать правильно.

Рассмотрим ящик для игрушек, наполненный различными видами игрушек, таких как транспортные средства, куклы и мячи. Поскольку ящик для игрушек может содержать только игрушки, все, что в него помещается, должно быть игрушкой. Согласно LSP, если у вас есть игрушечная машинка, вы сможете заменить ее игрушечным грузовиком, а ящик для игрушек по-прежнему будет функционировать должным образом.

Согласно LSP, объекты суперкласса должны иметь возможность заменяться объектами подкласса без ущерба для корректности программы.

Как реализовать принцип подстановки Лисков в React?

Вот несколько способов реализовать принцип подстановки Лисков в React:

  • Создайте свои собственные хуки, чтобы указать ожидаемое поведение и особенности определенных функций, таких как ввод формы. Любой компонент, использующий пользовательский хук, должен иметь возможность использоваться всякий раз, когда ожидается ввод формы, и приложение должно по-прежнему функционировать должным образом.
  • Создайте настраиваемые хуки, которые ожидают набор свойств, например объект, содержащий состояние и поведение компонента, а затем возвращают желаемое поведение и свойства. Пока элементы имеют одинаковую форму, это позволяет легко заменять различные объекты различными действиями.
  • Используйте композицию, чтобы определить ожидаемое поведение и свойства компонента. Объединение небольших и специализированных компонентов позволяет легко заменять связанные компоненты, соответствующие тому же интерфейсу и поведению.

Вот пример сценария, в котором нарушается принцип подстановки Лискова:

Предположим, у вас есть пользовательский хук с именем useAuth, который контролирует состояние аутентификации приложения, например токен пользователя и статус входа в систему.

import { useState } from "react";

const useAuth = () => {
  const [token, setToken] = useState(null);
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const login = (token) => {
    setToken(token);
    setIsLoggedIn(true);
  }

  const logout = () => {
    setToken(null);
    setIsLoggedIn(false);
  }

  return { token, isLoggedIn, login, logout };
}

Предположим, у вас есть компонент с именем Profile, который отображает информацию о пользователе, когда он вошел в систему, и кнопку входа, когда он не вошел.

const Profile = () => {
  const { token, isLoggedIn, login } = useAuth();

  if (!isLoggedIn) return <button onClick={() => login("pass")}>Login</button>;

  return <p>Welcome, you are logged in.</p>;
}

Представьте, что вы хотите добавить еще один компонент с именем Settings, который позволяет пользователю обновлять свои настройки, когда он вошел в систему, и кнопку входа в систему, когда это не так.

const Settings = () => {
  const { token, isLoggedIn, login } = useAuth();

  if (!isLoggedIn) return <button onClick={() => login("pass")}>Login</button>;

  return (
    <div>
      <form>
        <label>Change your password</label>
        <input type="password" placeholder="New password" />
        <button>Save</button>
      </form>
    </div>
  );
}

Как видите, компоненты Profile и Settings используют хук useAuth для управления состоянием аутентификации приложения, но поскольку у каждого компонента свои ожидания от хука, хук useAuth не может обрабатывать различные случаи. Поскольку хук useAuth нельзя использовать взаимозаменяемо в различных компонентах, это нарушает LSP.

Чтобы решить эту проблему, вы можете создать разные хуки для разных вариантов использования или разработать более общий хук, который может обрабатывать несколько обстоятельств, предоставляя больше параметров, таких как ожидаемое поведение или данные.

Следуя этому принципу, вы можете разработать набор взаимозаменяемых хуков, что упрощает добавление новых функций в ваше приложение, поскольку вы можете использовать те же хуки для других компонентов, и приложение по-прежнему будет функционировать должным образом.

Вот одно из решений проблемы с хуком useAuth, нарушающим LSP:

Создайте более общий хук useAuth, который может обрабатывать несколько случаев, предоставляя дополнительные аргументы, такие как желаемое поведение или данные.

import { useState } from "react";

const useAuth = (initialAuth) =>{
  const [auth, setAuth] = useState(initialAuth);

  const login = (token) => {
    setAuth({ token, isLoggedIn: true });
  }

  const logout = () => {
    setAuth({ token: null, isLoggedIn: false });
  }

  return { auth, login, logout };
}

Передайте начальное состояние авторизации в качестве параметра хуку useAuth.

const Profile = () => {
  const { auth, login } = useAuth({ token: null, isLoggedIn: false });

  if (!auth.isLoggedIn)
    return <button onClick={() => login("pass")}>Login</button>;

  return <p>Welcome, you are logged in.</p>;
};

const Settings = () => {
  const { auth, login } = useAuth({ token: null, isLoggedIn: false });

  if (!auth.isLoggedIn)
    return <button onClick={() => login("pass")}>Login</button>;

  return (
    <div>
      <form>
        <label>Change your password</label>
        <input type="password" placeholder="New password" />
        <button>Save</button>
      </form>
    </div>
  );
};

useAuthhook может обрабатывать несколько сценариев и взаимозаменяемо использоваться в других компонентах, задавая начальное состояние аутентификации в качестве параметра. Это позволяет компонентам иметь разные ожидания от хука, но при этом функционировать должным образом в соответствии с принципом замещения Лискова.