Princípio aberto fechado - Open-Closed Principle

O código deve estar aberto para extensões e fechado para modificações. Podemos mudar o comportamento de uma classe adicionando mais código.

Categoria de Programação

Postado em 14 abril 2022

Atualizado em 03 junho 2023

Palavras-chave: arquitetura,open,closed,principle,principio,aberto,fechado,programação,programming

Visualizações: 1303



Quanto mais dependências uma classe possui, mais estável ela se torna. Uma modificação em uma classe estável poderia gerar um efeito cascata em suas dependências transitivas, causando comportamentos inesperados em classes dependentes sem ninguém perceber. Por isso é importante estar ciente do ciclo de dependências que uma classe possui. Porém, nem sempre podemos estar checando as dependências entre as classes, por isso queremos estabelecer regras de desenvolvimento para evitar que isso aconteça.

O princípio aberto fechado pode ser uma solução para o problema de estabilidade, conservando a estrutura de uma classe através de abstrações.

O que é o princípio aberto fechado (Open Closed Principle)?

O princípio aberto fechado é o segundo princípio abordado nos princípios SOLID. Seu criador é Bertrand Meyer e mais tarde foi adotado por Robert Martin, tendo mais destaque após a divulgação da arquitetura limpa.

Esse princípio diz que devemos ser capazes de modificar o comportamento de uma classe sem tocar no código existente. Em outras palavras, devemos ser capazes de estender abstrações para mudar o comportamento de uma classe.

Por exemplo, imagine que temos um software capaz de criar documentos em formato PDF. Entretanto, por exigência do cliente, foi solicitado uma modificação no formato dos documentos para Excel. Nesse caso, não há necessidade de modificar o código existente para implementar a criação de documentos Excel. Invés disso, apenas adiciona-se mais código e substitui-se o argumento atual.

Como aplicar o princípio aberto fechado?

Aplica-se o princípio aberto fechado criando dependências em abstrações. As abstrações podem ser classes abstratas ou interfaces. Uma abstração é um contrato contendo uma estrutura de métodos que devem ser implementados em uma classe que depende de uma abstração.

public interface DocumentPrinterInterface {
	void print(String fileName);
}

O exemplo acima é uma simples abstração. Mesmo não possuindo conteúdo, a interface acima pode ser definida como parâmetro.

public class Customer {
	DocumentPrinterInterface documentPrinter;
	public Customer(DocumentPrinterInterface documentPrinter) {
		this.documentPrinter = documentPrinter;
	}
	
	public void printCustomerStats() {
		// ...
		documentPrinter.print("customer_xx_stats");
	}
}

Veja como a interface pode ser definida como parâmetro mesmo sem ter conteúdo dentro dela. Quando usamos uma interface como parâmetro, isso quer dizer que já definimos o contrato que deve ser obedecido pelas classes. Isso pode ser bastante vantajoso quando queremos definir regras de implementação.

Porém, uma interface não pode ser instanciada. Para uma interface ser instanciada, ela deve ser implementada por uma classe contendo detalhes.

public class PrintPdf implements DocumentPrinterInterface {
	ViewModel data;
	PDFLibrary pdfLibrary;
	
	void print(String fileName) {
		pdfLibrary.print(fileName);
	}
}

A classe acima injeta uma biblioteca de PDF e obedece ao contrato da interface que ela implementa. Uma vez que uma classe implementa uma interface, ela é obrigada a implementar os métodos da interface.

Agora que a interface foi implementada, ela pode ser instanciada e passada como parâmetro para o construtor da classe “Customer”.

PrintPdf printPdf = new PrintPdf();
Customer customer = new Customer(printPdf);
customer.printCustomerStats();

Observe que “Customer” só aceita a classe “PrintPdf” como parâmetro, porque ela implementa a interface “DocumentPrinterInterface”. Isso significa que podemos passar qualquer classe como parâmetro se ela implementar essa interface.

// PrintPdf printPdf = new PrintPdf();
PrintExcel printExcel = new PrintExcel();
Customer customer = new Customer(printExcel);
customer.printCustomerStats();

Veja que conseguimos mudar o comportamento da classe “Customer” implementando a abstração que ela depende. Agora, ao invés de gerar documentos em PDF, a classe “Customer” irá gerar documentos em formato Excel.

Por quê usar o princípio aberto fechado?

Uma classe que possui muitas dependências se torna estável pelo fato de poder afetar o funcionamento das suas dependências. Quando alteramos alguma coisa dentro de uma classe estável, devemos estar checando as suas dependências para ter certeza que ainda continuam funcionando. Esse teste inclui as dependências transitivas dessa classe estável.

Muitas dependências podem acabar exigindo muito mais tempo para o teste do que para a modificação. Se o processo de teste for automatizado, pode-se economizar muito mais tempo. Porém, dependendo da alteração do código, erros inesperados podem passar despercebidos pelo teste automático. Logo, a melhor solução para evitar erros inesperados é fazer com que o código esteja aberto para estensão e fechado para modificação. Em outras palavras, mudamos o comportamento de uma funcionalidade apenas adicionando mais código.

Analisando o princípio aberto fechado no nível arquitetural de um software

Uma grande vantagem do princípio aberto fechado é que podemos visualizar novas funcionalidades facilmente no nível arquitetural. Usando o código acima como base, veja a arquitetura abaixo como as dependências entre as classes funcionam.

arquitetura principio aberto fechado

Veja como todas as classes apontam para a interface “DocumentPrinterInterface”. As flechas representam as dependências. Na imagem acima, todas as classes dependem da interface. Logo, a interface é estável. Mas pelo fato da interface ser uma abstração, dificilmente modificamos ela. Isso é maravilhoso, pois não queremos mudar uma classe estável.

Robert Martin, autor dos princípio SOLID, cita no livro dele a regra de dependências. A regra de dependências diz que os detalhes devem depender de abstrações. A nossa arquitetura acima, está de acordo com a regra de dependências, pois as classes (detalhes) dependem da interface (abstração).

Quando obedecemos a regra de dependências, adquirimos uma grande vantagem: conseguimos aplicar o princípio aberto fechado. Perceba que se quiséssemos adicionar um novo formato de documento, poderíamos apenas adicionar mais código. Isso é possível apenas implementando a interface “DocumentPrinterInterface”. Assim, o nosso código se tornará aberto para estensões e fechado para modificações.

Conclusão

O princípio aberto fechado possibilita alterar o comportamento de uma classe estável sem modificar o código existente. Isso é possível fazendo com que os detalhes dependam das abstrações. Para que o OCP sejá aplicável, é necessário aplicar a regra mais básica da arquitetura limpa: a regra de dependências.

Projetos práticos

Criando um sistema de integração contínua (CI/CD)

Fazendo a integração contínua de Jenkins, Sonatype Nexus, Sonatype, JUnit e Gradle para automatizar processos repetitivos. Prática bastante usada em tecnologias de DevOps.

Usando dados fornecidos pelo TSE para simular o gráfico das eleições presidenciais de 2022

Simulação dos gráficos do segundo turno das eleições presidenciais, utilizando python e ferramentas de análise de dados, pandas e jupyter.

Criando artes de texto usando imagens

Convertendo imagens para ascii art usando o valor da intensidade das cores cinzentas.

Implementando um algoritmo de pathfinding

Implementando um programa que encontra a menor distância entre dois pontos dentro de um labirinto usando o algoritmo A* (a-estrela).

Criando um sistema de mini garagem automatizada integrada com um sistema de monitoramento independente

Desenvolvimento de um sistema de monitoramento que exibi todos os eventos que acontecem na garagem automatizada, como abertura de portões ou ocupação de vagas.

Veja também

Estar antenado pode te salvar de cair em algum golpe

A engenharia social é a forma mais fácil de roubar os dados da vítima, principalmente aqueles que não estão antenados..

Os computadores usam a numeração binária para a manipulação de dados em circuitos elétricos

A ausência e a presença de energia são dois estados que podem ser usados como valores. Esses valores são respectivamente zero e um.

OSS Software de código aberto

O código aberto é disponível para a visualização, modificação e utilização, podendo ser utilizado por terceiros para fins comerciais.

Arquitetura limpa - Clean Architecture

Eficiente quando aplicada em softwares de grande porte que necessitam de manutenção ao longo prazo. Criada em 2012 por Robert Martin.

Princípio da responsabilidade única - Single Responsibility Principle

Princípio que diz que um módulo só deve mudar por um único motivo. Esse motivo pode ser o conteúdo de um módulo ou os atores que dependem dele.

Princípio de substituição de Liskov - Liskov Substitution Principle

Esse princípio diz que uma classe derivada deve ser substituível pela sua classe base sem apresentar comportamentos inesperados.