DEV Community

Gabriel_Silvestre
Gabriel_Silvestre

Posted on • Edited on

Introdução ao SOLID - Princípios S O D

Tabela de Conteúdo


SOLID

O que é?

SOLID, na programação, refere-se a cinco princípios que focam na escrita de um código limpo, legível e de fácil manutenção.

Princípios

  • Single responsibility principle
  • Open/Close principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

Aviso

O SOLID propõe PRINCÍPIOS a serem seguidos para o desenvolvimento de um código mais limpo, porém esses princípios não são absolutos e podem muito bem ser ignorados ou adaptados ao nosso contexto.

Em outras palavras, o SOLID nos da boas recomendações a serem seguidas, mas não passa disso, RECOMENDAÇÕES.

"Regra nenhuma, princípio nenhum e caso especial nenhum deve piorar a legibilidade ou a manutenibilidade do seu código."

Voltar ao topo


Single Responsibility Principle

Recomendação

O princípio da responsabilidade única recomenda que cada função ou classe realize uma única ação, dessa forma teremos nosso código divido em pequenos blocos altamente "especializados".

Exemplo

Imagine que precisemos efetuar o pagamento de uma conta em outra moeda, para isso vamos precisar converter o valor para Real, acessar nossa carteira (banco de dados), recuperarmos nosso saldo e se nosso saldo for suficiente, pagar a conta.

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

type ICurrency = keyof typeof EXCHANGE;
Enter fullscreen mode Exit fullscreen mode
const convertValue = (value: number, currency: ICurrency) => {
  return Math.floor(value * EXCHANGE[currency]);
};
Enter fullscreen mode Exit fullscreen mode
const getBalance = async () => {
  const query = `
    SELECT amount FROM user.wallet;
  `;

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

  return amount;
};
Enter fullscreen mode Exit fullscreen mode
const isEnough = (personalBalance: number, totalAmount: number) => {
  return personalBalance > totalAmount;
};
Enter fullscreen mode Exit fullscreen mode
const payBill = async (billValue: number, billCurrency: ICurrency) => {
  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 | saldo anterior ${myBalance} | saldo atual ${myBalance}`;
};
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, apesar de termos omitido alguns passos, conseguimos criar um código desacoplado, legível e de fácil entendimento. Isso foi possível graças à alta especialização do código, onde cada função é responsável por executar uma única ação.

Voltar ao topo


Open/Closed Principle

Recomendação

O princípio aberto/fechado diz que o comportamento de uma entidade deve ser aberto a extensões, mas fechado a modificações, em palavras mais simples, precisamos poder criar novos comportamentos sem alterar os já existentes.

Exemplo

Imagine que uma faculdade nos pediu para desenvolvermos um software que irá validar se os candidatos conseguiram passar no vestibular para determinado curso.

Para isso nós vamos precisar saber a nota do candidato, a nota de corte do curso e quantas vagas o curso possui, ou seja, temos duas regras variáveis: nota de corte e quantidade de vagas.

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

type Course = {
  name: string;
  openings: number;
  minNote: number;
}
Enter fullscreen mode Exit fullscreen mode
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,
    },
  },
];
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
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);
};
Enter fullscreen mode Exit fullscreen mode

No exemplo construído logo acima, estamos montando a lista de aprovados de acordo com o número de vagas e a nota de corte de cada curso, sendo que ambos os dados são variáveis, ou seja, podemos adicionar qualquer curso que quisermos a esse software sem a necessidade de alterá-lo.

Voltar ao topo


Dependency Inversion Principle

Recomendação

Antes de entrar mais a fundo nas recomendações, vale a citação que o princípio de inversão de dependências anda em paralelo com a arquitetura de Injeção de Dependências, essa que não será abordada aqui.

O princípio de inversão de dependências consiste na implementação de uma dependência baseada em uma abstração, normalmente uma Interface, ao invés de um objeto, valor ou classe propriamente dito.

Exemplo

Diferente dos exemplos anteriores, aqui iremos utilizar da Orientação a Objeto para exemplificar, pois acredito que seja mais fácil de entender dessa forma.

Pense no seguinte cenário, temos uma pessoa e ela tem vários jogos para jogar, porém todos esses jogos são de RPG, logo suas ações são bem similares. O objetivo é criar uma classe Pessoa que possa jogar qualquer jogo de RPG.

export interface IGameRPG {
  fight(): number; // <- retorna o dano causado
  flee(): boolean; // <- retorna se conseguiu fugir ou não
  levelUp(): void;

  get level(): number; // <- nível do jogo
}
Enter fullscreen mode Exit fullscreen mode
class Gamer {
  constructor(private gameRpg: IGameRPG) {}

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

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

  improve() {
    this.gameRpg.levelUp();
  }
}
Enter fullscreen mode Exit fullscreen mode
class LightSouls implements IGameRPG {
  private _level: number = 1;

  // 10 à 100 de dano
  fight() {
    console.log('Bate, rola, rola, bate, potion, bate');

    return Math.floor(Math.random() * (100 - 10 + 1)) + 10;
  }

  // 30% de chance de fugir
  flee() {
    console.log('Correeeeee');

    return Math.random() < 0.3;
  }

  levelUp() {
    console.log("Welcome Home, ashen one. Speak thine heart's desire.");

    this._level += 1;
  }

  get level() {
    return this._level;
  }
}
Enter fullscreen mode Exit fullscreen mode
export class MoOnline implements IGameRPG {
  private _level: number = 1;

  // 1000 à 10000 de dano
  fight() {
    console.log('Bate, bate, bate, bate, bate, potion, bate, bate');

    return Math.floor(Math.random() * (10000 - 1000 + 1)) + 1000;
  }

  // 60% de chance de fugir
  flee() {
    console.log('Sair da missão');

    return Math.random() < 0.6;
  }

  levelUp() {
    console.log('Acessando novo andar...');

    this._level += 1;
  }

  get level() {
    return this._level;
  }
}
Enter fullscreen mode Exit fullscreen mode
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.combat(); // Bate, bate, bate, bate, bate, potion, bate, bate
soulsGamer.combat(); // Bate, rola, rola, bate, potion, bate
Enter fullscreen mode Exit fullscreen mode

Como podemos ver no exemplo acima, a classe Gamer pode receber qualquer jogo, desde que ele seja uma implementação da abstração IGameRPG.

Isso só é possível, pois estamos garantindo, através da interface IGameRPG, que todo a classe que a implemente vai ter os métodos fight, flee e levelUp.

Voltar ao topo


Repositório

Todos os exemplos demonstrados aqui se encontram nesse repositório, fique a vontade para favoritar, clonar ou realizar um fork.

Este repositório está em construção. Novos exemplos serão adicionados em breve.

Voltar ao topo


Links Úteis

Voltar ao topo

Top comments (0)