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



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 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.

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

Caixa eletrônico usando arquitetura limpa

Usando JavaFX e arquitetura limpa para criar um aplicativo de caixa eletrônico extremamente simples.

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).

Veja também

Ainda nos primórdios da internet discada, o maior medo de um usuário era o telefone tocar...

Esperávamos horas para baixar apenas alguns megabytes. Nessas horas, quando o telefone tocava nós fazíamos de tudo para não atender o telefone. Mas infelizmente nem sempre dava certo....

Uma das vantagens de ter um robô trabalhando para você é o aumento da produtividade

Hoje em dia não é mais necessário gastar o nosso tempo com tarefas que podem se automatizadas. Os robôs estão aqui para nos ajudar...

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.

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.

Princípio da inversão de dependências - Dependency Inversion Principle

Ao ser aplicado permite que os detalhes passem a depender de abstrações, respeitando a direção da regra de dependências.

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.