Princípio de Responsabilidade Única(Single responsibility principle – SRP)

O Princípio de Responsabilidade Única define:

“Uma classe deve ter apenas um motivo para mudar”

Em muitas literaturas é estudado a coesão de uma classe, isso define todos os elementos de uma classes que possui responsabilidades únicas tendem a se relacionar, esse termo é bem utilizado e abordado atualmente, porém é uma outra visão para o mesmo problema de colocar muita responsabilidade em apenas uma classe.

Em uma classe cada responsabilidade dela é uma eixo de mudança. Quando um requisito sofre mudança as mudanças afetam as responsabilidades das classes.Se uma classe possui mais de uma responsabilidade implementada, ela possui mais de uma razão para sofrer mudanças.

Quando uma classe possui mais de uma responsabilidade, suas responsabilidades se tornam acopladas. Mudanças em uma responsabilidade podem ocasionar a capacidade da classe de cumprir suas outras responsabilidades.Isso torna um projeto frágil e que ocorre problemas inesperados.

Sintomas mais aparentes da quebra deste principio:

  • Classes muito extensas
  • Alteração de um requisito ocasiona a quebra de uma outra responsabilidade ou requisito
  • Trecho do sistema com Dono
  • Camadas “mágicas” no sistema(sistemas que tem apenas uma camada para fazer tudo)

Exemplos de casos e sua refatoração:

Exemplo: A persistência de dados junta

Vamos ao cenário: Criar uma entidade de empregado com os campos( nome, idade, valor_hora) e persistir na base de dados esses campos. O calculo do salario do empregado é feito ao final do mês, por isso é necessário ter uma forma de passar a quantidade de horas trabalhadas do funcionário e retornar  o valor do salário que deve ser pago. (Vou utilizar todas os valores como inteiros e não vou atentar a arquitetura ou qualquer outra boa pratica apenas quero exemplificar esse princípio)

Algumas pessoas já pensam em uma classe mais ou menos assim:

         public class EmployeeWithoutSRP
         {
             private int _id;
             private string _name;
             private int _age;
             private int _hourlyPay;
             private SqlConnection _connection;

             public EmployeeWithoutSRP()
             {

             }

             public EmployeeWithoutSRP(string name, int age, int hourlyPay)
             {
                 Name = name;
                 Age = age;
                 HourlyPay = hourlyPay;
             }

             public int Id
             {
                 get { return _id; }
                 set { _id = value; }
             }

             /// <summary>
             /// Nome do Empregado
             /// </summary>
             public string Name
             {
                 get { return _name; }
                 set { _name = value; }
             }

             /// <summary>
             /// Idade
             /// </summary>
             public int Age
             {
                 get { return _age; }
                 set { _age = value; }
             }

             /// <summary>
             /// Valor hora
             /// </summary>
             public int HourlyPay
             {
                 get { return _hourlyPay; }
                 set { _hourlyPay = value; }
             }

             /// <summary>
             /// Regra de negocio
             /// </summary>
             /// <returns></returns>
             public int CalculatePayment(int hoursWorkedInMonth)
             {
                 return HourlyPay * hoursWorkedInMonth;
             }

             public void Save()
             {
                 using (_connection = new SqlConnection())
                 {
                     _connection.Open();
                     SqlCommand cmd = _connection.CreateCommand();
                     if (Id <= 0)//Insert
                     {
                         cmd.CommandText = "INSERT INTO EMPLOYEE (DE_NAME, NU_AGE, NU_HOURLY_PAY) VALUES (@NAME, @AGE, @HOURLY_PAY); SELECT @@IDENTITY;";

                         cmd.Parameters.Add(new SqlParameter("@NAME", Name));
                         cmd.Parameters.Add(new SqlParameter("@AGE", Age));
                         cmd.Parameters.Add(new SqlParameter("@HOURLY_PAY", HourlyPay));
                         int idEmployee = (int)cmd.ExecuteScalar();
                         Id = idEmployee;
                     }
                     else //Update
                     {
                         cmd.CommandText = "UPDATE EMPLOYEE set DE_NAME = @NAME, NU_AGE= @AGE, NU_HOURLY_PAY = @HOURLY_PAY  WHERE id = @ID";

                         cmd.Parameters.Add(new SqlParameter("@NAME", Name));
                         cmd.Parameters.Add(new SqlParameter("@AGE", Age));
                         cmd.Parameters.Add(new SqlParameter("@HOURLY_PAY", HourlyPay));
                         cmd.Parameters.Add(new SqlParameter("@ID", Id));
                         cmd.ExecuteNonQuery();
                     }
                 }
             }
         }

Porém essa classe quebra o principio de responsabilidade unica, pois ela possui a responsabilidade de fazer o calculo de pagamento do empregado e também de persistir seus dados em um SQL Server. Uma forma que muitas gente pensa é que fazendo isso adiciona comportamento para a classe, pois é natural pegar uma classe e chamar o método Save para persistir. Adicionar comportamento a uma classe é fazer um método para que dentro dele as propriedades ou campos sejam alterados da forma que aquele comportamento necessite. Por exemplo Em uma classe Usuário colocar o método Ativar() é um comportamento, ele altera uma propriedade para falar que está ativo, porém o método salvar não altera nada apenas persiste isso é uma responsabilidade.

Bom já falamos demais disso agora vamos ao código refatorado:

         public class Employee
         {
             private int _id;
             private string _name;
             private int _age;
             private int _hourlyPay;

             public Employee()
             {

             }
             public Employee(string name, int age, int hourlyPay)
             {
                 Name = name;
                 Age = age;
                 HourlyPay = hourlyPay;
             }

             public int Id
             {
                 get { return _id; }
                 set { _id = value; }
             }

             /// <summary>
             /// Nome do Empregado
             /// </summary>
             public string Name
             {
                 get { return _name; }
                 set { _name = value; }
             }

             /// <summary>
             /// Idade
             /// </summary>
             public int Age
             {
                 get { return _age; }
                 set { _age = value; }
             }

             /// <summary>
             /// Valor hora
             /// </summary>
             public int HourlyPay
             {
                 get { return _hourlyPay; }
                 set { _hourlyPay = value; }
             }

             /// <summary>
             /// Regra de negocio
             /// </summary>
             /// <returns></returns>
             public int CalculatePayment(int hoursWorkedInMonth)
             {
                 return HourlyPay * hoursWorkedInMonth;
             }
         }

     public class RepEmployee
         {
             private SqlConnection _connection;

             public RepEmployee()
             {

             }

             public void Save(Employee employee)
             {
                 using (_connection = new SqlConnection())
                 {
                     _connection.Open();
                     SqlCommand cmd = _connection.CreateCommand();
                     if (employee.Id <= 0)//Insert
                     {
                         cmd.CommandText = "INSERT INTO EMPLOYEE (DE_NAME, NU_AGE, NU_HOURLY_PAY) VALUES (@NAME, @AGE, @HOURLY_PAY); SELECT @@IDENTITY;";

                         cmd.Parameters.Add(new SqlParameter("@NAME", employee.Name));
                         cmd.Parameters.Add(new SqlParameter("@AGE", employee.Age));
                         cmd.Parameters.Add(new SqlParameter("@HOURLY_PAY", employee.HourlyPay));
                         int idEmployee = (int) cmd.ExecuteScalar();
                         employee.Id = idEmployee;
                     }
                     else //Update
                     {
                         cmd.CommandText = "UPDATE EMPLOYEE set DE_NAME = @NAME, NU_AGE= @AGE, NU_HOURLY_PAY = @HOURLY_PAY  WHERE id = @ID";

                         cmd.Parameters.Add(new SqlParameter("@NAME", employee.Name));
                         cmd.Parameters.Add(new SqlParameter("@AGE", employee.Age));
                         cmd.Parameters.Add(new SqlParameter("@HOURLY_PAY", employee.HourlyPay));
                         cmd.Parameters.Add(new SqlParameter("@ID", employee.Id));
                         cmd.ExecuteNonQuery();
                     }
                 }
             }

         }

Sim eu refatorei mais o método de calcular pagamento ainda está na classe empregado isso não seria uma quebra do princípio?
Não, pois a responsabilidade da classe empregado é gerenciar as informações do empregado a regra de negocio faz parte disso, o que se deve tomar cuidado para não inchar essas classes, a ideia é manter classes pequenas e métodos bem pequenos também. Esse principio é facilmente entendido ao praticar TDD(Test Driven Development).
Todo o código deste post está no github neste link.
Anúncios