C# - Aplicando o princípio Open-Closed


Hoje vamos recordar como aplicar o princípio Open-Closed para criar classes fechadas para modificação e aberta para extensão.

Você lembra do princípio Aberto-Fechado ou Open-Closed ?

De maneira simples e objetiva o princípio Open-Closed afirma que :

"Classes deverão ser abertas para extensão mas fechadas para modificação" Bertrand Meyer (1988)

Criando uma classe sem atentar para princípio Open-Closed

Ao criar classes precisamos garantir que ela não permita qualquer alteração em seu código em implementações futuras

Com isso dizemos que a classe esta fechada e se precisarmos alterá-la de alguma forma podemos fazer isso estendendo a classe.

Para ilustrar esse conceito vamos dar um exemplo prático.

Vamos supor que em um programa de um game de batalha foi criada uma classe que determina as habilidades de um elemento da tropa usando a classe Tropa.

O código da classe é visto a seguir:

using System.Collections.Generic;

namespace OpenCloseDemo
{
    public class Tropa
    {
        public enum Atuacao { Soldado, Medico, Engenheiro }

        private List<string> Habilidades;

        public List<string> GetHabilidades(Atuacao atuacao)
        {
            switch (atuacao)
            {
                case Atuacao.Soldado:
                    return Habilidades = new List<string>(new string[] { "Tático", "Artilharia", "Infantaria" });
                case Atuacao.Medico:
                    return Habilidades = new List<string>(new string[] { "Clínico", "Cirurgião" });
                case Atuacao.Engenheiro:
                    return Habilidades = new List<string>(new string[] { "Comunicações", "Explosivos", "Mecânico" });
                default:
                    return Habilidades = new List<string>(new string[] { "nenhum" });

            }
        }
    }
}

Vamos entender a classe:

1- A classe possui um enumerador chamado Atuacao que identifica o tipo de atuação para a qual desejamos obter as habilidades;

2- A classe possui uma variável Habilidades que uma lista de strings (List<string>) que contém as habilidades específicas de um elemento da tropa;

3- A classe possui o método GetHabilidades() que retorna o conjunto específico de habilidades para classe Tropa;

Essa classe apresenta uma estrutura de código muito comum e fácil de ser encontrada. O que pode variar é que ao invés de instruções switch você pode encontrar instruções if/else.

No código não encontramos nenhum erro, e, embora a funcionalidade do código esteja clara, se precisarmos incluir novas atuações e/ou novas habilidades específicas para uma atuação teríamos que alterar o código da classe incluindo mais um valor na enumeração e também no bloco switch.

Esse tipo de alteração no código pode fazer com que você introduza bugs no código que antes estava funcionando bem, além de ter que alterar o módulo onde a classe foi criada.

Ajustando a classe ao princípio Open-Closed

Como podemos alterar o código desta classe de forma a deixá-la aderente ao princípio Open-Close ?

Para isso vamos reescrever a classe Tropa e usar a herança.

Vamos criar uma classe Tropa definindo um método virtual GetHabilidades():

using System.Collections.Generic;

namespace Solucao1
{
    public class Tropa
    {
        public virtual List<string> GetHabilidades()
        {
            return new List<string>(new string[] { "nenhum" });
        }
    }
}

A seguir vamos criar classes derivadas para Soldado,  Medico e Engenheiro que vão herdar da classe Tropa :

1- Soldado

using System.Collections.Generic;

namespace Solucao1
{
    public class Soldado : Tropa
    {
        public override List<string> GetHabilidades()
        {
            return new List<string>(new string[] { "Tático", "Artilharia", "Infantaria" });
        }
    }
}

2- Medico

using System.Collections.Generic;

namespace Solucao1
{
    public class Medico : Tropa
    {
        public override List<string> GetHabilidades()
        {
            return new List<string>(new string[] { "Clínico", "Cirurgião" });
        }
    }
}

3- Engenheiro

using System.Collections.Generic;

namespace Solucao1
{
    public class Engenheiro : Tropa
    {
        public override List<string> GetHabilidades()
        {
            return new List<string>(new string[] { "Comunicações", "Explosivos", "Mecânico" });
        }
    }
}

As classes derivadas da classe Tropa são extensões da classe e,  podemos  dizer que cada classe está fechada, porque modificá-la não exige a mudança do código original.

A classe Tropa também é extensível porque fomos capazes de facilmente estender a classe criando classes derivadas dela. Outro subproduto desse design é um código menor, mais gerenciável e mais fácil de ler e entender.

Assim com a decisão de usar herança e criar classes derivadas tornamos o nosso código mais robusto.

Aguarde mais artigos sobre os princípios S.O.L.I.D. : SRP , OCP, LSP, ISP e DIP.

"Como também nos elegeu nele antes da fundação do mundo, para que fôssemos santos e irrepreensíveis diante dele em amor;"
Efésios 1:4

Referências:
  • Padrões de Projeto
  • Padrões de Projeto - O modelo MVC - Model View Controller
  • O padrão Singleton
  • VB.NET - Permitindo uma única instância da sua aplicação
  • Design Patterns - o padrão Factory
  • Conceitos sobre projetos - Decomposição
  • Usando o padrão Strategy
  • SRP - O princípio da responsabilidade única
  • Boas Práticas - O padrão inversão de controle (IoC)
  • http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
  • Conceitos sobre projetos - Decomposição
  • Seção Padrões de Projeto do site Macoratti.net

  • José Carlos Macoratti