Привет всем! Я Мыльников Кирилл, frontend-разработчик в компании Usetech. Сегодня хочу поделиться примером реализации фабричного метода во фронтенде и объяснить, когда и где его следует применять. Освежим память о паттернах и роли, которую они играют в проектах.

Я готовлю серию статей о паттернах в React. Первая статья будет посвящена фабричному методу. Все примеры мы с вами разберем на реальных сценариях, с которыми вы могли бы столкнуться в проекте.

Давайте по классике начнем с определений, вспомним, что такое паттерны и фабричный метод.

Что такое паттерн?

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

Что такое «Фабричный метод»?

Фабричный метод – это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.

Итак, мы разобрали классическую трактовку паттернов и фабричного метода, которые традиционно описываются в книгах и официальных источниках.

Давайте теперь все это адаптируем под наш проект на React.

Фабричный метод в React

В React фабричный метод служит для динамического создания компонентов или функций с различными свойствами или поведениями. Представьте фабрику, где товары создаются по заказу с уникальными параметрами: размером, цветом и материалом. Здесь процесс создания товаров автоматизирован, что позволяет изготавливать предметы без необходимости их ручной настройки. Также и в React мы можем создать фабричную функцию для компонентов.

Так давайте же перейдем от теории к практике. Все примеры буду показывать в песочнице, сначала буду описывать проблему, которая может возникнуть в проекте, а потом сразу покажу решение через фабричный метод.

Реализация фабричного метода в React

Будем использовать для тестовых данных вот этот сервис DummyJSON.

Вот ссылка на codesandbox с примером. В первом случае список отображается там, где могут возникнуть проблемы при внедрении или изменении логики в дальнейшем, а во втором – применен паттерн «Фабричный метод». Мы получаем список продуктов и выводим их, но при различных категориях нам приходится настраивать каждую карточку индивидуально.

Компонент становится сложным и трудночитаемым из-за условий, и изменения могут нарушить логику других категорий, но это можно исправить с помощью паттерна «Фабричный метод».

Пример

const Product = ({ product }: IProductProps) => {
  return (
    <li>
      <div className={styles.product}>
        <img src={product.thumbnail} alt="thumbnail" />
      </div>
      <div className={styles.productInfo}>
        {product.category !== "beauty" && (
          <p className={styles.productTitle}>{product.title}</p>
        )}
        <p className={styles.productPrice}>${product.price}</p>
      </div>
      {product.category === "groceries" && (
        <p className={styles.description}>{product.description}</p>
      )}
      {product.category === "fragrances" && (
        <p className={styles.policy}>
          <span className={styles.text}> Условия возврата: </span>
          {product.returnPolicy}
        </p>
      )}
    </li>
  );
};

export default Product;

Вот наша вторая реализация, где мы применяем паттерн «Фабричный метод». Это позволяет нам легко вносить изменения и настраивать каждую карточку в зависимости от категории. Теперь мы можем без проблем изменять логику для каждого типа продукта, и никакие изменения не затронут другие категории. Не забываем, что можно перейти по ссылке и посмотреть пример полностью – codesandbox.

Пример

import { IProducts } from "../../../../typings";
import BeatuProduct from "./BeautyProduct";
import GroceriesProduct from "./GroceriesProduct";
import FragrancesProduct from "./FragrancesProduct";

interface IProductFactoryProps {
  product: IProducts;
}

const ProductFactory = ({ product }: IProductFactoryProps) => {
  switch (product.category) {
    case "beauty":
      return <BeautyProduct product={product} />;
    case "groceries":
      return <GroceriesProduct product={product} />;
    case "fragrances":
      return <FragrancesProduct product={product} />;
    default:
      return null;
  }
};

export default ProductFactory;

Еще один наглядный пример использования фабричного метода это UI – компоненты, допустим у нас в проекте есть кнопка, которая может выглядеть по-разному в различных частях интерфейса – с разными цветами и темами. В рассмотренном примере ниже мы видим, что в зависимости от типа кнопки мы отображаем конкретный компонент кнопки со своими стилями и свойствами. Благодаря использованию паттерна «Фабричный метод», нам легко и просто настраивать кнопку под нужды проекта и в любой момент заменять ее другой кнопкой с новым типом или добавить новый тип и настроить через новый компонент, реализация тут – codesanbox.

Пример

import DangerButton from "./DangerButton";
import PrimaryButton from "./PrimaryButton";

export interface IButtonProps {
  type: "primary" | "danger";
  title: string;
  onClick?: () => void;
}

const Button = ({ type, title, onClick }: IButtonProps) => {
  switch (type) {
    case "primary":
      return <PrimaryButton title={title} onClick={onClick} />;
    case "danger":
      return <DangerButton title={title} onClick={onClick} />;
    default:
      return null;
  }
};

export default Button;

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

Материалы

Паттерны – Каталог паттернов проектирования (refactoringguru.cn)

Codesanbox – ProductFactory.tsx – sandbox – CodeSandbox

Комментарии (3)


  1. ivankprod
    29.06.2024 20:16

    Не проще тогда сразу использовать PrimaryButton / DangerButton где нужно?


    1. juwon22 Автор
      29.06.2024 20:16

      Так не гибко, в компонент Button можно вынести общие стили ну и кнопок может быть гораздо больше. Все они будут лежать в одном месте и ими управлять так проще, просто указываешь нужный тип


      1. ThisMan
        29.06.2024 20:16

        Тут есть нюанс, при использовании такой "фабрично кнопки", у вас будет тянутся код всех кнопок, даже если вы используете только одну-две. И какой-нибудь tree shaking тут тоже не поможет