Como visto nos post anteriores, os princípios de projeto e programação conhecidos como SOLID têm como objetivo orientar nas atividades de projeto e desenvolvimento de sistemas que possam exibir um longo ciclo de vida, ou seja, além de funcionarem adequadamente tais sistemas devem ser fáceis de manter e de ampliar. Além disso, estes princípios também devem auxiliar nas abordagens ágeis de construção de software.
São cinco os princípios que formam o acrônimo SOLID:
- Single Responsability Principle
- Open-Closed Principle
- Liskov`s Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
OCP::Open-Closed Principle
Módulos devem ser abertos para extensão, mas fechados para modificação.
O Open-Closed Principle ou Princípio aberto-fechado ou apenas OCP foi inicialmente proposto por Bertrand Meyer, em 1998, e indica que qualquer classe, módulo ou componente de software deveria ser construído de maneira que possa ser usado como base a criação de outros por meio dos mecanismos de extensão disponíveis (p.e., a herança na programação orientada a objetos); ao mesmo tempo restringindo e limitando as possibilidades de sua alteração direta.
A essência do OCP é a capacidade de estender o comportamento ou o funcionamento de um módulo sem que seja necessária sua modificação. Assim o projeto de uma classe é feito de modo que novas funcionalidades possam ser adicionadas, à medida em apareçam novos requisitos, mas exclusivamente por meio da extensão da própria classe (aberto para extensão). Assim, uma vez criada, a classe nunca deve ser modificada, exceto para correções nas funcionalidades existentes, isto é, que estavam presentes na sua origem (fechado para modificação).
As classes devem, portanto, ser projetadas de tal maneira que, quando surgem demandas para a modificação seu funcionamento (fluxo de controle ou comportamento), tudo que é requerido é a criação uma subclasse na qual sejam substituídas as operações adequadas. Para isto se combinam os mecanismos da herança (vista como especialização de um tipo) e de substituição de métodos (method override).
A figura que segue ilustra uma situação não aderente ao OCP, que pode ser denominada de cliente fechado.
Uma classe é um cliente (Client) quando se utiliza de funcionalidades presentes em outra classe, aqui denominada servidora (Server). Se for necessário trocar o Server, a classe Client também deverá ser alterada, o que é, claramente um inconveniente.
Outra situação, bastante distinta, é ilustrada abaixo.
A classe cliente (Client) utiliza-se de uma classe denominada AbstractServer, que serve de base para a construção de uma classe servidora (Server) específica. Esta situação, denominada cliente aberto é aderente ao OCP, pois: a troca de Server não afeta a classe Client; além do fato de que AbstractServer poderia atender outros clientes distintos de Server.
Uma implementação-conceito de AbstractServer poderia ser como na classe Java de mesmo nome. As operações abstratas poderiam existir em qualquer número e com qualquer assinatura e tipo de retorno.
public abstract
class AbstractServer {
public
abstract void operacao1();
public
abstract void operacao2();
}
public class
Server extends AbstractServer {
public
Server() {
System.out.println("Server<init>");
}
@Override
public void
operacao1() {
// implementação específica
System.out.println("Server.operacao1()");
}
@Override
public void operacao2() {
// implementação específica
System.out.println("Server.operacao2()");
}
}
Note que poderiam existir diferentes implementações de AbstractServer, cada uma contendo as especificidades necessárias a cada tipo de cliente ou grupo de clientes.
Uma fábrica, como ServerFactory, poderia prover instâncias de uma implementação selecionada para os clientes.
public final
classe ServerFactory {
public
static AbstractServer createServer() {
System.out.println("AbstractServer.CreateServer()");
return new
Server();
}
}
Assim, qualquer cliente, como Client, poderia utilizar implementações concretas de AbstractServer, sem conhecer diretamente tais classes, obtendo as instâncias necessárias da fábrica ServerFactory.
public class Client {
public
static void main(String args[]) {
AbstractServer server = ServerFactory.createServer();
server.operacao1();
server.operacao2();
}
Este princípio só é, de fato, útil, quando uma mudança se apresenta como necessária. Assim, uma recomendação válida é: num primeiro momento, codifique da maneira mais simples possível; e, quando surgir a necessidade de modificação, construa uma abstração que o proteja de modificações similares no futuro.
Segundo R. Martin, devemos manter as coisas que mudam com frequência separadas daquelas que não mudam. Além disso, se existirem dependências entre elas, as coisas que mudam com frequência devem depender das que não mudam! (E não o contrário!)
Como, em geral, as mudanças são comuns e frequentes do que desejamos, o OCP agrega muito valor. Novamente o trabalho de dividir a abstração de classe em partes é compensado pelas facilidades em evoluir.
Para Saber Mais
- LARMAN, Craig. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados à objetos e ao Processo Unificado. Porto Alegre: Bookman, 2007.
- MARTIN, R. C.; et. al. Clean Code: a handbook of agile software craftsmanship. Boston: Pearson Education, 2009.
- MARTIN, R. C. The Clean Coder. Upper Sadle River: Prentice-Hall, 2011.
- MARTIN, R. C. Principles of OOD. Disponível em http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, recuperado em 17/02/2017.
- MARTIN, R. C. Get a SOLID start. Disponível em http://objectmentor.com, recuperado em 17/02/2017.
- PAGE-JONES, Meilir. Fundamentos do Desenho Orientado a Objetos. São Paulo: Makron Books, 2001.
- SOMMERVILLE, I. Software Engineering. 9th. Ed. Boston: Addison-Wesley, 2011.