Введение в принципы SOLID — S O D


Оглавление

  • SOLID
  • Принцип единой ответственности
  • Принцип открытости/закрытости
  • Принцип инверсии зависимостей
  • Полезные ссылки

SOLID

Что такое SOLID?

SOLID, в программировании, означает пять принципов, которые направлены на написание чистого, читабельного и сопровождаемого кода.

принципы

  • Принцип единой ответственности
  • Принцип открытия/закрытия
  • Принцип замещения Лискова
  • Принцип разделения интерфейсов
  • Принцип инверсии зависимостей

Предупреждение

SOLID предлагает ПРИНЦИПЫ, которым нужно следовать для разработки более чистого кода, но эти принципы не являются абсолютными и вполне могут быть проигнорированы или адаптированы к нашему контексту.

Другими словами, SOLID дает нам хорошие рекомендации, которым мы должны следовать, но это всего лишь РЕКОМЕНДАЦИИ.

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

Вернуться к началу


Принцип единой ответственности

Рекомендация

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

Пример

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

const EXCHANGE = {
  USD: 4.6,
  EUR: 5,
  CAD: 3.7,
};

type CURRENCY = {
  [key in EXCHANGE]: string;
}
Войдите в полноэкранный режим Выход из полноэкранного режима
const convertValue = (value: number, currency: CURRENCY) => {
  return value * EXCHANGE[currency];
};
Войдите в полноэкранный режим Выход из полноэкранного режима
const getBalance = async () => {
  const query = `
    SELECT amount FROM user.wallet;
  `;

  const [{ amount }] = await mysql.execute(query);

  return amount;
};
Войдите в полноэкранный режим Выход из полноэкранного режима
const isEnough = (personalBalance: number, totalAmount: number) => {
  return personalBalance > totalAmount;
};
Войдите в полноэкранный режим Выход из полноэкранного режима
const payBill = async (billValue: number, billCurrency: CURRENCY) => {
  const myBalance = await getBalance();
  const convertedValue = convertValue(billValue, billCurrency);

  if (isEnough(myBalance, convertedValue)) {
    return `Conta paga | saldo anterior ${myBalance} | saldo atual ${myBalance - convertedValue}`;
  }

  return `Saldo insuficiente`;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

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

Вернуться к началу


Принцип открытости/закрытости

Рекомендация

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

Пример

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

Для этого нам нужно знать балл кандидата, отборочный балл курса и количество вакансий на курсе, то есть у нас есть две переменные правила: отборочный балл и количество вакансий.

type Student = {
  name: string;
  examNote: number;
  course: Course;
}

type Course = {
  name: string;
  openings: number;
  minNote: number;
}
Войдите в полноэкранный режим Выход из полноэкранного режима
const students = [
  {
    name: 'John',
    examNote: 87,
    course: {
      name: 'ADM',
      openings: 40,
      minNote:  62,
    },
  },
  {
    name: 'Joe',
    examNote: 42,
    course: {
      name: 'ENG',
      openings: 20,
      minNote:  76,
    },
  },
  {
    name: 'Katy',
    examNote: 66,
    course: {
      name: 'MED',
      openings: 10,
      minNote:  85,
    },
  },
];
Войдите в полноэкранный режим Выход из полноэкранного режима
const rankingStudents = (students: Student[]) => {
  return students.sort((a, b) => a.examNote - b.examNote );
};

const hasOpening = (listLength: number, listLimit: number) => {
  return listLength < listLimit;
};

const hasNote = (studentNote: number, minNote: number) => {
  return studentNote >= minNote;
}
Войдите в полноэкранный режим Выход из полноэкранного режима
const approvedList = {
  ADM: [],
  ENG: [],
  MED: [],
}

const approvedStudents = (students: Student[]) => {
  return rankingStudents(students)
    .reduce((acc, { name, examNote, course }) => {
      const courseApprovedList = acc[course.name];

      if (!hasOpening(courseApprovedList.length, course.openings)) return acc;

      if (hasNote(examNote, course.minNote)) {
        acc[couse.name].push(name);
      }

      return acc;
    }, approvedList);
};
Войдите в полноэкранный режим Выход из полноэкранного режима

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

Вернуться к началу


Принцип инверсии зависимостей

Рекомендация

Прежде чем углубиться в рекомендации, стоит упомянуть, что принцип инверсии зависимостей идет рука об руку с архитектурой Dependency Injection, которая здесь рассматриваться не будет.

Принцип инверсии зависимости заключается в реализации зависимости на основе абстракции, обычно интерфейса, вместо самого объекта, значения или класса.

Пример

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

Подумайте о следующем сценарии: у нас есть человек, и у него есть несколько игр, в которые он играет, но все эти игры — RPG, поэтому их действия очень похожи. Цель — создать класс Person, который может играть в любую RPG-игру.

interface IGameRPG {
  fight(): void;
  flee(): void;
  levelUp(): void;
}
Войдите в полноэкранный режим Выход из полноэкранного режима
class Gamer {
  constructor(private gameRpg) {}

  combat() {
    this.gameRpg.fight();
  }

  escape() {
    this.gameRpg.flee();
  }

  improve() {
    this.gameRpg.levelUp();
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима
class LightSouls implements IGameRPG {
  fight() {
    console.log('Bate, rola, rola, bate, potion, bate');
  }

  flee() {
    console.log('Correeeeee');
  }

  levelUp() {
    console.log('Welcome Home, ashen one. Speak thine heart's desire.');
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима
class MoOnline implements IGameRPG {
  fight() {
    console.log('Bate, bate, bate, bate, bate, potion, bate, bate');
  }

  flee() {
    console.log('Sair da missão');
  }

  levelUp() {
    console.log('+1 força');
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима
const moOnline = new MoOnline();
const lightSouls = new LightSouls();

const moGamer = new Gamer(moOnline);  // <- a classe Gamer recebe o jogo Mo Online
const soulsGamer = new Gamer(lightSouls);  // <- a classe Gamer recebe o jogo Light Souls

moGamer.fight();  // Bate, bate, bate, bate, bate, potion, bate, bate
soulsGamer.fight();  // Bate, rola, rola, bate, potion, bate
Войдите в полноэкранный режим Выход из полноэкранного режима

Как видно из примера выше, класс Gamer может получить любую игру, если она является реализацией абстракции IGameRPG.

Это возможно только потому, что через интерфейс IGameRPG мы гарантируем, что каждый класс, который его реализует, будет иметь методы fight, flee и levelUp.

Вернуться к началу


Полезные ссылки

  • Гуру рефакторинга
  • Medium — Паттерны проектирования в TypeScript Factory от Сезара Уильяма
  • YouTube RocketSeat — Принципы SOLID в REST API
  • YouTube RocketSeat — Создание API с помощью SOLID + TDD
  • YouTube Código Fonte TV — SOLID // Dicionário do Programador

Вольтар на вершине

Оцените статью
Procodings.ru
Добавить комментарий