Olá! Boas-vindas a este curso de Solid.
Meu nome é Emerson Laranja, sou instrutor na Escola de Programação.
Audiodescrição: Emerson se descreve como um homem negro com barba e cabelo curtos. Está usando óculos quadrados e uma blusa azul. Ao fundo, há uma parede iluminada nas cores azul e verde.
Se desejam escrever um código mais limpo e seguro, este conteúdo é perfeito para vocês. Ao longo deste curso, vamos explorar os cinco princípios do Solid, que são:
Vamos ver tudo isso, na prática, em um sistema de gerenciamento de uma empresa. Vamos explorar o módulo que gerencia pessoas, pagamentos e até mesmo a infraestrutura desse sistema.
Para acompanhar melhor este conteúdo, é importante que já tenham conhecimento básico de TypeScript e de programação orientada a objetos.
Convidamos vocês a aproveitarem os recursos da plataforma e ir além dos vídeos, cumprindo as atividades, tirando suas dúvidas no fórum do curso e conversando com outras pessoas que estejam estudando este mesmo assunto na nossa comunidade no Discord.
Então, vamos lá?
Fomos contratados para implementar algumas melhorias e refatorações em um sistema de gerenciamento de uma empresa. As atividades que precisaremos executar foram listadas no Trello, que temos aberto.
A primeira tarefa é "Código Canivete Suíço". Vamos abrir o card para entendermos melhor do que se trata. Tivemos uma conversa inicial com o Tech Lead (Líder Técnico) e fomos informados que a maioria dessas melhorias não está relacionada a um código legado ou a uma nova biblioteca que precisa ser usada, mas sim a algumas refatorações de boas práticas que precisaremos implementar. Começando com a descrição desta primeira atividade:
Temos um módulo do sistema que realiza diversas funções e isso dificulta a manutenção. Trata-se da classe Sistema no código anexado.
Além disso, estamos repetindo código para calcular o salário, já que não conseguimos reutilizar código com tudo tao acoplado.
Por favor, resolva o problema e realize as refatoraçóes necessárias.
No card da tarefa, temos um projeto no GitHub em anexo, que eu já deixei no nosso editor de código, o VS Code. Com o código aberto, vamos visualizar do que se trata essa primeira tarefa. No explorador de arquivos do VS Code, temos a pasta "tarefa-1" e alguns outros arquivos para a configuração do projeto Node.
Na pasta "tarefa 1", temos as pastas "dist", onde ficará o nosso código compilado em JavaScript e "enum", com o arquio cargos.ts
, com os cargos possíveis na empresa:
Na pasta "tarefa-1", temos também a classe Colaborado
, com três atributos: nome
, _cargo
e _saldo
. Nela também temos, e algumas operações de get
e set
. Além disso, temos a classe main
e a classe Sistema
.
Podemos então abrir o Sistema.ts
, onde está, de fato, nosso problema. Neste arquivo temos dois atributos: _colaboradores
, do tipo Colaborador
, e _salarioBase
, do tipo number
. Além disso, ele possui vários métodos, como contratarColaborador
, demitirColaborador
, calcularSalario
, pagarColaborador
, gerarRelatorioJSON
e um get
dos colaboradores desse sistema.
Como podemos ver, é uma classe grande, o que dificulta tanto entender o código, quanto fazer a manutenção dele. Além disso, esse código apresenta baixa coesão, o que significa que ele assume responsabilidades que não são dele.
Por exemplo, temos um sistema que, além de lidar com colaboradores, lida com pagamento. Para termos uma alta coesão, o ideal é que os métodos da nossa classe se relacionem com a sua definição. Se é uma classe de pagamento, ali lidaremos com pagamento apenas, não com manipulações com colaborador.
Além disso, possuímos um alto acoplamento. Porque hoje, se precisarmos, como exigido pela tarefa, calcular o salário, precisaremos importar uma instância de todo o sistema, com vários outros métodos, só para calcular um salário. Quando o ideal seria ter um módulo para lidar apenas com isso.
Para facilitar o entendimento de como seria essa solução, já criei um diagrama. Voltando ao nosso navegador, vou abrir o Lucidchart, onde tem um quadrado com a metade superior sendo um fundo vermelho e a metade inferior é um fundo verde.
No fundo vermelho, temos a nossa situação atual, e, no fundo verde, onde queremos chegar, ou seja, o código correto, assim digamos. Vamos ampliar o que está escrito no fundo vermelho para entendermos como está a relação hoje dessas classes.
Temos um Colaborador
e um Sistema
, que possuem alguns métodos que precisam dos Cargos
. O Sistema
se liga ao Colaborador
, porque possui um quadro de colaboradores, e nosso objetivo é resolver o fato do nosso Sistema
ter tantos métodos, ou seja, tantas responsabilidades.
No nosso cenário ideal, de fundo verde, continuamos com o Colaborador
e o enum
, ou seja, os Cargos
. Não vamos alterar essa parte, apenas não teremos mais uma classe Sistema
, mas sim, novas quatro classes, cada uma com uma das responsabilidades do método Sistema
.
Se vamos lidar com pagamento, temos a classe Pagamento
. Para calcular o salário, teremos uma classe CalculaSalario
, bem como uma classe apenas para gerar o relatório (GeraRelatorio
) e outra para lidar com o quadro de colaboradores (QuadroColaboradores
), onde vamos contratar, demitir e buscar esses colaboradores.
A implementação do que temos como solução, veremos na sequência.
Vimos anteriormente que a nossa classe Sistema
possuía alguns problemas. O primeiro deles é se quisermos utilizar o método de calcular um salário em outras partes do nosso código.
Temos um problema de autoacoplamento, pois ele depende de todos os outros métodos e atributos definidos no Sistema
. Precisaríamos criar uma instância do Sistema
para conseguir utilizar apenas esse método de calcular o salário. Outro ponto que vimos é a baixa coesão, onde ele assume responsabilidades que não são dele. Seria melhor, como vimos no nosso diagrama, separar as responsabilidades.
Voltando ao nosso diagrama, notamos que essa separação das responsabilidades foi resolvida criando quatro classes: Pagamento
, CalculaSalario
, GeraRelatorio
e QuadroDeColaboradores
, que vai substituir o nosso Sistema
. E é assim que vamos começar.
Voltando ao nosso VS Code, criarems esses arquivos. Clicaremos pasta tarefa-1
e depois no botão "Novo arquivo", criando o Pagamento.ts
. Repetiremos o processo para criarmos os arquivos CalculaSalario.ts
e o GeraRelatorio.ts
.
CalculaSalario
Feito isso, retornamos ao Sistema.ts
, onde recortaremos cada uma das responsabilidades, ou seja, cada método correspondente a cada um dos arquivos. Começaremos com o método calcularSalario()
, que está na nossa linha 22. Selecionamos todo o método, que vai da linha 22 até a 40, pressionamos "Ctrl + X" para recortá-lo.
calcularSalario(cargo: Cargos) {
if (cargo === Cargos.Estagiario) {
return this.salarioBase * 1.2;
}
else if (cargo === Cargos.Junior) {
return this.salarioBase * 3;
}
else if (cargo === Cargos.Pleno) {
return this.salarioBase * 5;
}
else if (cargo === Cargos.Senior) {
return this.salarioBase * 7;
}
return 0;
}
Antes de colar esse código no arquivo CalculaSalario
, precisamos exportar e definir a classe. Para isso, escrevemos export default class CalculaSalario{}
. Dentro das chaves, colaremos o código que recortamos. O próprio VS Code já indica que precisamos fazer a importação dos nossos Cargos
, então clicamos nele, que está na linha 2, com uma marcação de erro, e pressionamos "Ctrl + espaço" para concluir a importação.
import { Cargos } from "./enum/cargos";
export default class CalculaSalario {
calcularSalario(cargo: Cargos) {
if (cargo === Cargos.Estagiario) {
return this.salarioBase * 1.2;
}
else if (cargo === Cargos.Junior) {
return this.salarioBase * 3;
}
else if (cargo === Cargos.Pleno) {
return this.salarioBase * 5;
}
else if (cargo === Cargos.Senior) {
return this.salarioBase * 7;
}
return 0;
}
}
Precisamos voltar ao Sistema.ts
e recortar também o atributo salarioBase
. Como também vamos precisar do construtor, selecionamos da linha 6, onde temos a definição dos atributos, até a linha 12, onde temos final do construtor.
private _colaboradores: Colaborador[];
protected salarioBase: number;
constructor(salarioBase: number = 1000) {
this.salarioBase = salarioBase;
}
Voltamos para o CalculaSalario.ts
e colamos esse trecho embaixo do nome da classe. Como não precisaremos de _colaboradores
, podemos excluir a linha private _colaboradores: Colaborador[];
. Consequentemente, excluiremos também a this._colaboradores = []
de dentro do construtor.
import { Cargos } from "./enum/cargos";
export default class CalculaSalario {
protected salarioBase: number;
constructor(salarioBase: number = 1000) {
this.salarioBase = salarioBase;
}
calcularSalario(cargo: Cargos) {
if (cargo === Cargos.Estagiario) {
return this.salarioBase * 1.2;
}
else if (cargo === Cargos.Junior) {
return this.salarioBase * 3;
}
else if (cargo === Cargos.Pleno) {
return this.salarioBase * 5;
}
else if (cargo === Cargos.Senior) {
return this.salarioBase * 7;
}
return 0;
}
}
Agora sim, temos o salarioBase
, o construtor e o método funcionando. Agora, se precisarmos, em outra parte do nosso código, calcular o salário, só precisamos dessa classe. Não temos mais nenhuma outra dependência, resolvendo a questão do autoacoplamento do nosso código.
Pagamento
Ainda temos algumas melhorias para fazer. Vamos voltar para o nosso Sistema.ts
e agora recortar o método pagaColaborador()
. Portanto, selecionamos da linha 24 a 27 e pressionamos "Ctrl + X". Abriemos agora o arquivo Pagamento.ts
, onde começaremos codando export default class Pagamento{}
. Dentro das chaves, colaremos o método pagaColaborador()
.
export default class Pagamento {
paraColaborador(colaborador: Colaborador) {
const salarioColaborador = this.calcularSalario(colaborador.cargo);
colaborador.saldo = salarioColaborador;
}
}
Como a classe já se refere a pagamento, renomearemos o método para apenas pagar()
. Precisaremos fazer a importação do tipo Colaborador
, que está marcado com erro. Em seguida, na linha 5, temos uma reclamação do TypeScript, porque vamos precisar de um método para calcular o salário, referente à classe que acabamos de criar.
Para isso, escreveremos nosso construtor(){}
e, nos parênteses, digitamos: private servicoCalculaSalario
, que é o nome que quero dar para essa classe, e ela será do tipo calculaSalario
. Depois, na const salarioColaborador
, podemos substituir o método por this.servicoCalculaSalario.calcularSalario
. Portanto, o servicoCalculaSalario
tem o método calculaSalario
.
import CalculaSalario from "./CalculaSalario";
import Colaborador from "./Colaborador";
export default class Pagamento {
constructor(private servicoCalculaSalario: CalculaSalario) { }
pagar(colaborador: Colaborador) {
const salarioColaborador = this.servicoCalculaSalario.calcularSalario(colaborador.cargo);
colaborador.saldo = salarioColaborador;
}
}
Podemos fazer uma na nossa classe calculaSalario
, mudando o nome do método calcularSalario
. Agora que resolvemos a coesão da classe, o método faz exatamente o que a classe diz, assumindo uma única responsabilidade. Sendo assim, mudaremos o nome do método para calcular()
, apenas, porque já sabemos que será calculado um salário.
import { Cargos } from "./enum/cargos";
export default class CalculaSalario {
protected salarioBase: number;
constructor(salarioBase: number = 1000) {
this.salarioBase = salarioBase;
}
calcular(cargo: Cargos) {
// código omitido
}
}
E agora quando voltarmos para o Pagamento.ts
, fica muito mais claro que o servicoCalculaSalario
possui o método calcular
. E assim finalizamos também o nosso pagamento
.
import CalculaSalario from "./CalculaSalario";
import Colaborador from "./Colaborador";
export default class Pagamento {
constructor(private servicoCalculaSalario: CalculaSalario) { }
pagar(colaborador: Colaborador) {
const salarioColaborador = this.servicoCalculaSalario.calcular(colaborador.cargo);
colaborador.saldo = salarioColaborador;
}
}
QuadroColaboradores
Agora que no Sistema.ts
temos o método contratarColaborador()
e demitirColaborador()
, podemos renomear a classe para QuadroColaboradores
, como tínhamos definido no diagrama. Para isso, no Explocador de Arquivos, à esquerda, selecionaremos Sistema.ts
e pressionaremos "F2".
Mudaremos o nome para QuadroColaboradores
e pressionaremos "Enter". Uma janela irá aparecer no centro da tela pedindo para atualizar as importações para QuadroColaboradores
. Clicaremos no botão "Não", no canto inferior direito da janela, porque faremos essas mudanças manualmente.
Começando pela linha 4 do QuadroColaboradores.ts
, porque o nome da classe agora será QuadroColaboradores
. Repassando o código, percebemos que, nessa classe, temos os métodos:
O gerarRelatorioJSON()
não tem relação com a classe QuadroColaboradores
, e sim com o arquivo GeraRelatorio.ts
, que criamos anteriormente. Então vamos recortar esse trecho de código e abrir o arquivo GeraRelatorio.ts
.
GeraRelatorio
No começo do arquivo, novamente vamos digitar export default class GeraRelatorio{}
. Dentro das chaves, colaremos o código que recortamos.
export default class GeraRelatorio {
gerarRelatorioJSON() {
let relatorio = this._colaboradores.map((colaborador) => {
return ({
nome: colaborador.nome,
cargo: colaborador.cargo,
salario: this.calculaSalario(colaborador.cargo),
})
})
return JSON.stringify(relatorio)
}
}
Precisamos dos colaboradores
e do método calculoSalario
, então faremos isso inserindo o nosso constructor(){}
logo abaixo do nome da classe. Nos parênteses, codamos private _colaboradores: Colaborador[]
. Após a array de Colaborador[]
, escrevemos uma vírgula, porque precisaremos também do private servicoCalculaSalario: CalculaSalario
.
export default class GeraRelatorio {
constructor(
private quadroDeColaboradores: Colaborador[],
private servicoCalculaSalario: CalculaSalario
) { }
//Código omitido
Agora basta importarmos nosso Colaborador
, que está marcado com erro, e modificar o método do salario
para this.servicoCalculaSalario.calcular()
. Assim conseguimos separar as funcionalidades do nosso relatório. Inclusive, podemos renomear o método de geraRelatorioJSON()
para apenas gerarJSON
.
import CalculaSalario from "./CalculaSalario"
import Colaborador from "./Colaborador"
export default class GeraRelatorio {
constructor(
private quadroDeColaboradores: Colaborador[],
private servicoCalculaSalario: CalculaSalario
) { }
gerarJSON() {
let relatorio = this.quadroDeColaboradores.map((colaborador) => {
return ({
nome: colaborador.nome,
cargo: colaborador.cargo,
salario: this.servicoCalculaSalario.calcular(colaborador.cargo),
})
})
return JSON.stringify(relatorio)
};
}
Voltando ao nosso QuadroColaboradores
, notamos que agora só possuímos funções relacionadas a pessoas colaboradoras. Assim, resolvemos o problema do autocoplamento e a coesão, porque agora o que é feito dentro da classe tem a ver com a definição dela.
main.js
para testarmos o códigoAgora podemos testar todas essas funcionalidades integradas. Para isso, no menu explorar, à esquerda, abriremos o arquivo main.ts
. Essa é a versão sem as alterações que fizemos, usando o Sistema
. Na linha 5, criamos uma instância desse Sistema
e criamos três colaboradores
diferentes:
const sistema = new Sistema();
const colaborador1 = new Colaborador("José", Cargos.Estagiario);
const colaborador2 = new Colaborador("Maria", Cargos.Junior);
const colaborador3 = new Colaborador("João", Cargos.Pleno);
// código omitido
Após isso, contratamos cada um deles, adicionando essas pessoas no nosso array Colaboradores
. Em seguida, mostramos um relatório de quem são as pessoas colaboradoras e fazemos uma operação para mostrar o salário do colaborador1
antes e depois de pagar o salário.
// código omitido
sistema.contratarColaborador(colaborador1);
sistema.contratarColaborador(colaborador2);
sistema.contratarColaborador(colaborador3);
console.log(sistema.gerarRelatorioJSON());
console.log(colaborador1);
sistema.pagaColaborador(colaborador1);
console.log(colaborador1);
Podemos agora alterar essa funcionalidade respeitando as responsabilidades que dividimos. Para isso vamos renomear onde tem sistema
para quadroColaboradores
e excluir o import Sistema from "./Sistema";
, na linha 3.
Eu já tinha feito uma "cola" do que mais precisamos alterar, então vou apenas substituir no código e depois importaremos as instâncias novas, com "Ctrl + Espaço". Lembrando que na GeraRelatorio()
, precisamos passar o quadroColaboradores
e a calculaSalario
. Já no Pagamento()
passamos apenas o calcularSalario
.
// importações omitidas
const quadroColaboradores = new QuadroColaboradores();
const calculaSalario = new CalculaSalario();
const geradorDeRelatorios = new GeraRelatorio(quadroColaboradores.colaboradores, calculaSalario);
const pagamento = new Pagamento(calculaSalario);
const colaborador1 = new Colaborador("José", Cargos.Estagiario);
const colaborador2 = new Colaborador("Maria", Cargos.Junior);
const colaborador3 = new Colaborador("João", Cargos.Pleno);
quadroColaboradores.contratarColaborador(colaborador1);
quadroColaboradores.contratarColaborador(colaborador2);
quadroColaboradores.contratarColaborador(colaborador3);
console.log(quadroColaboradores.gerarRelatorioJSON());
console.log(colaborador1);
quadroColaboradores.pagaColaborador(colaborador1);
console.log(colaborador1);
O que vamos fazer agora é, mantendo a criação dos colaboradores, que não precisamos alterar, assim como os códigos de quadroColaboradores.contratarColaborador()
, precisamos agora gerar os relatórios. Essa não é mais uma responsabilidade do quadroColaboradores
, e sim do nosso geradorDeRelatorios.geraJSON()
.
console.log(geradorDeRelatorios.gerarJSON());
Outra modificação que não é o quadroColaboradores
é no pagamento. Quem lida com isso agora é o Pagamento
, portanto, na linha 27, codamos pagamento.pagar()
, manteremos o colaborador1
como parâmetro.
// importações omitidas
const quadroColaboradores = new QuadroColaboradores();
const calculaSalario = new CalculaSalario();
const geradorDeRelatorios = new GeraRelatorio(quadroColaboradores.colaboradores, calculaSalario);
const pagamento = new Pagamento(calculaSalario);
const colaborador1 = new Colaborador("José", Cargos.Estagiario);
const colaborador2 = new Colaborador("Maria", Cargos.Junior);
const colaborador3 = new Colaborador("João", Cargos.Pleno);
quadroColaboradores.contratarColaborador(colaborador1);
quadroColaboradores.contratarColaborador(colaborador2);
quadroColaboradores.contratarColaborador(colaborador3);
console.log(geradorDeRelatorios.gerarJSON());
console.log(colaborador1);
pagamento.pagar(colaborador1);
console.log(colaborador1);
Agora para executarmos, eu deixei alguns scripts no arquivo package.jsom
. Entre eles, temos o tarefa-1
, que é referente a main
dessa tarefa que acabamos de concluir. Para testarmos, abriremos o terminal, com o atalho "Ctrl + Shift + '", e codaremos:
npm run tarefa-1
Após aguardarmos alguns instantes, até compilar o nosso código. O primeiro retorno que temos é o console.log
do relatório, onde temos os nossos três colaboradores retornados como JSON: José, Maria e João. Em seguida, temos o console.log
de pagamento do colaborador 1, que antes tinha o saldo igual a zero e, depois de fazermos o pagamento tem como saldo de 1.200, que é o valor do salário dele.
Portanto, conseguimos fazer as melhorias, as refatorações, sem afetar o comportamento que tínhamos antes. E essa foi a aplicação do primeiro princípio, o princípio da responsabilidade única, em que vamos entender mais detalhes na sequência.
O curso SOLID com TypeScript: aplicando boas práticas em orientação a objetos possui 89 minutos de vídeos, em um total de 54 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.