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: 1519



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.

Criando o esqueleto de um jogo de tiro 2D visto de cima usando P5.js

Usando lógicas matemáticas como trigonometria para criar e calcular o esqueleto de um jogo de tiro 2D em javascript

Integrando o PHP com Elasticsearch no desenvolvimento de um sistema de busca

Projeto de criação de um sistema de busca usando o framework Symfony e Elasticsearch. A integração com Kibana também é feito de modo remoto com um raspberrypi.

Integrando Laravel com o protocolo MQTT para comunicação entre dispositivos

Projeto de comunicação entre dois dispositivos ESP8266 e Raspberrypi4. Laravel irá funcionar como servidor e receptor de dados de temperatura e umidade coletados com o DHT11.

Criando um jogo de pacman usando javascript e pixi.js (parte 1)

Desenvolvimento dos conceitos mais básicos do clássico pacman, como: mapa, animação, deslocamento e detector de colisões.

Veja também

Os bancos estão sendo substituídos pelos robôs em transações online

As criptomoedas mudaram totalmente o modo das pessoas pensarem. Usar robôs para autentificar transações online, custa muito menos comparado com os bancos em relação às taxas...

Nunca se sabe quando tem alguém nos espionando no nosso computador

Um computador conectado à internet está exposto a diversos perigos. O spyware é um deles e é esse malware responsável por roubar contas de redes sociais.

Princípio de segregação de interfaces - Interface Segregation Principle

Os clientes não devem ser forçados a importar métodos que eles não usam. Os métodos devem ser segregados de modo abstrato em interfaces.

Framework no desenvolvimento de softwares

Conjunto de códigos prontos para a utilização no desenvolvimento de softwares, eliminando processos como planejamento de arquitetura de classes.

Pilha (stack) e fila (queue)

Pilha e fila são tipos de estrutura de dados que contribuem para um gerenciamento de dados mais inteligente e eficaz na programação

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.