O OCP (Open/Closed Principle) é um dos 5 princípios SOLID que todo bom programador e/ou arquiteto de software deveria conhecer. A aplicação do Princípio da Responsabilidade Única é extremamente importante para que possamos separar as classes que podem mudar por razões diferentes e vai nos ajudar muito na implementação do Princípio Aberto/Fechado.
Definição do OCP
Este princípio pode ser definido através da seguinte frase:
"Um artefato de software deve estar aberto para extensão e fechado para modificação."
Em outras palavras, o principal objetivo deste princípio é fazer com que o nosso sistema possa receber modificações através de extensões sem que novas solicitações de mudança possam eventualmente nos forçar a realizar alterações no software todo.
Provavelmente você também já passou por isso, não é mesmo? 👀
Violação do OCP
Aqui temos um exemplo muito simples criado apenas para fins didáticos e, pensando em uma API, vamos começar criando nossos endpoints para download de um relatório em PDF e outro em CSV:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Endpoint para retornar PDF
app.MapGet("/download/pdf", async () =>
{
var reportGenerator = new ReportGenerator();
var bytes = reportGenerator.Generate(new ReportContent(), ReportType.PDF);
return Results.File(bytes, "application/pdf", "xpto.pdf");
});
// Endpoint para retornar CSV
app.MapGet("/download/csv", () =>
{
var reportGenerator = new ReportGenerator();
var bytes = reportGenerator.Generate(new ReportContent(), ReportType.CSV);
return Results.File(csvBytes, "text/csv", "xpto.csv");
});
app.Run();
Para que o código das nossas classes não fiquem muito extensos, use a sua imaginação para visualizar o conteúdo das classes ReportContent
e ReportGenerator
:
public class ReportContent
{
// Atributos referente ao conteúdo do relatório
}
public enum ReportType
{
PDF = 1,
CSV = 2
}
public class ReportGenerator
{
public byte[] Generate(ReportContent reportContent, ReportType reportType)
{
if (reportType == ReportType.PDF)
{
// Lógica para gerar o relatório PDF
}
else
{
// Lógica para gerar o relatório CSV
}
}
}
Como podemos ver neste exemplo de código, a classe ReportGenerator
recebe dois parâmetros. Um deles (ReportContent
) é o conteúdo que será utilizado para compor o relatório e o outro (ReportType
) indica qual o tipo de relatório desejado pelo usuário/ator.
O cenário está armado para que, sempre que surgir um novo tipo de relatório, seja necessário incluir novas condições nesta classe. Pode acontecer ainda (e acontece muito) de todas as dependencias necessárias para cada tipo de relatório estarem sendo importadas no mesmo arquivo, deixando o corpo desta classe ainda mais complexo e com baixa coesão.
Mas nem tudo está perdido, pois este artigo está aqui para te ajudar a resolver e evitar este tipo de problema.
Aplicando o OCP
Considerando o código do exemplo anterior, vamos começar removendo o enum ReportType
pois ele não será mais necessário. Além disso vamos criar uma interface denominada IReportGenerator
com a assinatura do método Generate
e que recebe apenas a classe que representa o conteúdo do nosso relatório como parâmetro.
public class ReportContent
{
// Atributos referente ao conteúdo do relatório
}
public interface IReportGenerator
{
byte[] Generate(ReportContent reportContent);
}
Feito isto, vamos criar uma classe para cada tipo de relatório e ambas devem implementar a nossa nova interface e aplicar a lógica necessária para gerar o relatório.
public class PdfReportGenerator : IReportGenerator
{
public byte[] Generate(ReportContent reportContent)
{
// Lógica para gerar o relatório PDF
}
}
public class CsvReportGenerator : IReportGenerator
{
public byte[] Generate(ReportContent reportContent)
{
// Lógica para gerar o relatório CSV
}
}
Agora vamos criar um classe chamada ReportService
e ela precisa receber a nossa abstração (interface) como parâmetro em seu construtor:
public class ReportService(IReportGenerator reportGenerator)
{
public byte[] GenerateReport(ReportContent reportContent)
{
return reportGenerator.Generate(reportContent);
}
}
Vamos finalizar atualizando o código dos nossos endpoints:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Endpoint para retornar PDF
app.MapGet("/download/pdf", async () =>
{
var reportService = new ReportService(new PdfReportGenerator());
var bytes = reportService.GenerateReport(new ReportContent());
return Results.File(bytes, "application/pdf", "xpto.pdf");
});
// Endpoint para retornar CSV
app.MapGet("/download/csv", () =>
{
var reportService = new ReportService(new CsvReportGenerator());
var bytes = reportService.GenerateReport(new ReportContent());
return Results.File(csvBytes, "text/csv", "xpto.csv");
});
app.Run();
Com isto, qualquer novo tipo de relatório não irá forçar você e os seus colegas desenvolvedores a realizarem alterações de ponta a ponta no software, pois a implementação de uma nova extensão é mais do que suficiente.
Ainda não está convencido? Vamos implementar agora um relatório XLSX:
// Nova classe referente ao tipo de arquivo XLSX
public class XlsxReportGenerator : IReportGenerator
{
public byte[] Generate(ReportContent reportContent)
{
// Lógica para gerar o relatório XLSX
}
}
//--
// Novo endpoint para retornar XLSX
app.MapGet("/download/xslx", () =>
{
var reportService = new ReportService(new XlsxReportGenerator());
var bytes = reportService.GenerateReport(new ReportContent());
return Results.File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xpto.xlsx");
});
Prontinho, tarefa finalizada. 😊
Essa é a magia por trás do OCP, pois todas as nossas classes que contém a lógica para gerar relatórios estão fechadas para modificações e o nosso software está aberto para novas extensões. Muito lindo, não é mesmo?
Antes de ir embora, conta aí: você já aplica ou ao menos já conhecia o OCP?
Obrigado pela sua atenção e espero que este artigo tenha sido útil para você.
Me siga para receber mais conteúdos como este. ❤️
Recomendação de leitura:
Arquitetura Limpa - O Guia do Artesão para Estrutura e Design de Software
Robert Cecil Martin, 2019.
Top comments (0)