Iniciando com StructureMap

StructureMap é uma biblioteca open-source para fazer Injeção de dependência/ inversão de controle, ela é muito simples de configurar e utilizar, comparada com outras.  Simplificando bem para o que ela pode ser utilizada, ela é utilizada para criar um objeto de uma interface sem conhecer a classe concreta que será utilizada.

Para adicionar a biblioteca pode ser feita pelo Nuget com as versões mais atuais do visual studio ou pode ser feito o download da dll no próprio site da biblioteca.

Dentro da biblioteca a classe mais importante é a ObjectFactory, essa classe é utilizada para configurar qual objeto deve ser criado para qual interface, e quando for necessário retornar um objeto para uma interface ela também é utilizada.

Exemplo

Em uma solução normalmente temos projetos em contexto Web e outros locais(windows services, Console Applications entre outros) que utilizam as mesmas classes, porém é comum ocorrer o problema de dentro de uma dessas classes encontrar um comentários mais ou menos assim “Método deve ser chamado apenas por projetos Web” ou até mesmo diversas condições para que o método pegue as informações de um lugar ou de outro. Isso ocorre com diversas informações mas nesse caso utilizaremos apenas para algumas configurações exatamente para exemplificar.

Primeiro vamos definir o comportamento da nossa interface e uma classe que irá conter as configurações que desejamos buscar. Nossa interface IConfiguracao terá apenas um método chamado PegarConfiguracao que deve retornar uma classe com as configurações do ambiente, como o código abaixo demontra:

public interface IConfigurations
{
 ConfigurationsInfo PegarConfiguracao();
}

public class ConfigurationsInfo
{
 public TipoAmbiente Ambiente { get; set; }
 public string NomeUsuario { get; set; }
}

public enum TipoAmbiente
{
 Local,
 Web
}

Com a interface criada agora deve ser criado as classes concretas para essa interface, nesse caso vamos criar duas WebConfiguration e LocalConfiguration. Uma deverá retornar informações armazenadas na sessão utilizada pelo ASP.Net e a outra deverá retornar informações armazenadas em uma variável estática. A implementação das classe fica assim

public class WebConfiguration : IConfigurations
{
	public ConfigurationsInfo PegarConfiguracao()
	{
		if (HttpContext.Current.Session["NomeUsuario"] == null)
		{
			HttpContext.Current.Session["NomeUsuario"] = "Nome do usuario na Web";
		}
		return new ConfigurationsInfo()
			   {
				   Ambiente = TipoAmbiente.Web,
				   NomeUsuario = HttpContext.Current.Session["NomeUsuario"].ToString()
			   };
	}
}

public class LocalConfiguration : IConfigurations
{
	private static string nomeUsuario;
	public ConfigurationsInfo PegarConfiguracao()
	{
		if (nomeUsuario == null)
		{
			nomeUsuario = "Nome do usuario Local";
		}
		return new ConfigurationsInfo()
			   {
				   Ambiente = TipoAmbiente.Local,
				   NomeUsuario = nomeUsuario
			   };
	}
}

Até agora a unica coisa utilizada foi o básico de orientação a objetos. Agora nos vamos começar com o StructureMap, cria uma classe estática chamada Mapeador(Nome besta exatamente para fixar), essa classe será responsável por fazer a configuração de qual classe concreta deve ser criada para a interface, e também será utilizada para criar um objeto a partir do tipo da interface passada. A configuração será no método chamado Map, dentro dele será chamado o método configure o ObjectFactory, que permite passar a interface e qual o tipo concreto que deve ser criado. O outro método criado será o método Get que irá retornar o objeto a partir da interface. A classe irá ficar assim:

public static class Mapeador
{
	public static void Map()
	{
		
		if (HttpContext.Current == null)
		{
			ObjectFactory.Configure(x =>
			 x.For(typeof(IConfigurations)).Use(typeof(LocalConfiguration)));
		}
		else
		{
			ObjectFactory.Configure(x =>
			 x.For(typeof(IConfigurations)).Use(typeof(WebConfiguration)));
		}

	}

	public static T Get<T>()
	{
		return ObjectFactory.GetInstance<T>();
	}
}

Agora a melhor forma de testar é criar duas aplicações uma Local(no nosso caso será uma console) e uma Web e efetuar a criação o objeto e ver se está funcionando como deve. Primeiro crie uma aplicação console, nela é apenas necessário chamar primeiro o metodo Map da classe Mapeador e depois o Metodo Get. O codigo da aplicação console deve ficar assim:

	Mapeador.Map();
	IConfigurations configurations = Mapeador.Get<IConfigurations>();
	var config = configurations.PegarConfiguracao();
	Console.WriteLine(config.Ambiente + " - Usuario:" + config.NomeUsuario);

	Console.ReadKey();

A unica diferença entre a aplicação console e a aplicação web é o local que deve ser chamado o metodo Map, pois não é uma boa pratica sempre executar o metodo map, isso pode gerar repetição de codigo desnecessário e maior processamento no servidor. O melhor lugar para colocar a configuração é no arquivo Global.asax no metodo Application_Start que irá ser executado apenas quando o site é iniciado.

 

Todo o código deste post está no github neste link.

Anúncios

Princípio de Substituição de Liskov (Liskov substitution principle – LSP)

O principio de substituição de Liskov define :

“Os subtipos devem ser substituíveis pelos seus tipos base”

Esse é um principio q confunde muitos programadores, porém é bem simples depois que se entende ele. Um termo muito utilizado no aprendizado do conceito de herança é o termo “É-um”. Esse termo é muito amplo para definição de um subtipo. A melhor forma de definição para subtipo é substituível, quando a substituição é definida por uma interface explicitamente ou implicitamente.

Desing by contract é uma pratica que ajuda a seguir esse principio. Ela define para que não seja criado variáveis de classes concretas, deve ser criado as variáveis a partir de contratos explícitos.

Esse principio alerta com o uso incorreto de polimorfismo, é necessário ter uma visão muito correta para generalizar ou especificar uma propriedade ou um parâmetro de um método em uma classe. Para deixar os termos que estou usando mais claros:

  • Generalizar: Quando a variável em questão é um tipo base, uma interface, uma classe abstrata ou qualquer outro tipo de objeto que é possível passar um objeto derivado dele.
  • Especializar: Quando a variável em questão é o objeto concreto, só é possível passar um objeto daquele determinado tipo.

A parte mais importante para entender esse principio, é que não se utilizar uma classe base quando alguma classe derivada dele não pode ser substituível por ele. Caso a classe derivada remova alguma funcionalidade da classe base ou ocasione um erro nas funcionalidades da classe base.

Exemplo:Vou tentar mostrar em uma cenário bem bestinha, mas é para entender realmente o conceito. Voce tem uma classe pessoa e ela tem dois métodos CalcarPeDireito e CalcarPeEsquerdo. Os dois metodos recebem dois parametros um Sapato(classe abstrata) e uma Meia(classe abstrata), fazendo o uso de generalização.  A estrutura de classes do Sapato e da Meia são essas:

    /// <summary>
    /// Meia
    /// </summary>
    public abstract class Sock
    {
        protected Sock()
        {

        }
        public abstract string Side { get; }

    }

    public  class RightSock : Sock
    {
        public RightSock()
        {

        }
        public override string Side
        {
            get { return "Right"; }
        }
    }

    public  class LeftSock : Sock
    {
        public LeftSock()
        {

        }
        public override string Side
        {
            get { return "Left"; }
        }
    }

    /// <summary>
    /// Sapato
    /// </summary>
    public abstract class Shoe
    {
        public abstract string Side { get; }

        protected Shoe()
        {

        }
    }

    public class LeftShoe : Shoe
    {
        public LeftShoe()
        {

        }
        public override string Side
        {
            get { return "Left"; }
        }
    }

    public class RightShoe : Shoe
    {
        public RightShoe()
        {

        }
        public override string Side
        {
            get { return "Right"; }
        }
    }

A classe pessoa está desenvolvida assim:

    public static class Person
    {
        /// <summary>
        /// Calçar Pé esquerdo
        /// </summary>
        /// <param name="shoe"> Can't be any show only the left</param>
        /// <param name="sock"> There is no diference</param>
        public static void PuttingOnLeftFootBreakingLSP(Shoe shoe, Sock sock)
        {
            Console.WriteLine("Putting socks {0} on the left foot!", sock.Side);
            if (shoe.Side != "Left")
            {
                Console.WriteLine("Wrong foot!");
            }
            else
            {
                Console.WriteLine("Putting shoes {0} on the left foot!", shoe.Side);
            }
        }

        /// <summary>
        /// Calçar Pé Direito
        /// </summary>
        /// <param name="shoe"></param>
        /// <param name="sock"> There is no diference</param>
        public static void PuttingOnRightFootBreakingLSP(Shoe shoe, Sock sock)
        {
            Console.WriteLine("Putting socks {0} on the righ foot!", sock.Side);
            if (shoe.Side != "Right")
            {
                Console.WriteLine("Wrong foot!");
            }
            else
            {
                Console.WriteLine("Putting shoes {0} on the righ foot!", shoe.Side);
            }
        }
    }

Com isso nada impede de eu fazer isso no codigo:

     Person.PuttingOnLeftFootBreakingLSP(new RightShoe(), new RightSock());
     Person.PuttingOnRightFootBreakingLSP(new LeftShoe(), new RightSock());

Ok, agora para resolver esse problema e seguir o principio de Substituição de Liskov como podemos alterar isso? Muito simples, utilizaremos a generalização ainda é possível, no caso no paramento da meia, pois a meia não tem problema se colocar invertido. E usaremos a especialização onde é necessário, no caso no sapato, utilizaremos em em seus respectivos  métodos o sapato do lado correto, como mostra o código a seguir:

 public static class Person
    {
        /// <summary>
        /// Calçar Pé esquerdo
        /// </summary>
        /// <param name="shoe"></param>
        /// <param name="sock"></param>
        public static void PuttingOnLeftFootUsingLSP(LeftShoe shoe, Sock sock)
        {
            Console.WriteLine("Putting socks {0} on the left foot!", sock.Side);
            Console.WriteLine("Putting shoes {0} on the left foot!", shoe.Side);
        }

        /// <summary>
        /// Calçar Pé Direito
        /// </summary>
        /// <param name="shoe"></param>
        /// <param name="sock"></param>
        public static void PuttingOnRightFootUsingLSP(RightShoe shoe, Sock sock)
        {
            Console.WriteLine("Putting socks {0} on the righ foot!", sock.Side);
            Console.WriteLine("Putting shoes {0} on the righ foot!", shoe.Side);
        }

    }

Assim quando um desenvolvedor utilizar essa classe ele pode utilizar o polimorfismo com os parâmetros possíveis, mais os que não é possível generalizar ele é obrigado a passar a classe concreta corretamente, isso da muito mais segurança na codificação. Isso faz com que aquele código de forma incorreta na classe Pessoa não seja mais permitido.

Todo o código deste post está no github neste link.

Princípio de Segregação de Interface(Interface segregation principle – ISP)

O principio de segregação de interface e:

“Os clientes não devem ser obrigados a depender de métodos

que não utilizam

Esse principio ajuda a evitar a criação de fat interfaces(interfaces gordas), termo utilizado para interfaces com mais funcionalidades do que o necessário. Classes que implementam uma interface assim não são coesas. As interfaces podem ser divididas em grupos de métodos, e cada grupo  atende uma conjunto diferentes de classes, cada classe pode implementar apenas as funcionalidades que fazem sentido;

Uma dos indicadores para identificar a quebra deste principio é no seguinte cenário você ter uma interface com 4 funcionalidades porém ao implementar essa interface em uma classe faz sentido todas os métodos. Porém em outra classes uma funcionalidade ou outra não faz sentido ser implementada.

Uma boa dica para evitar a quebra desse principio é sempre que adicionar um método em uma interface, analise quem implementa essa interface e se aquele método faz sentido para todas as classes que implementam. Se tiver sentido adicione nessa interface sem nenhum problema, caso não faça sentido crie um outra interface(ou verifique se faz sentido em outra interface já existente) para adicionar o método que você precisa implementar.

Indicadores de quebra do principio:

  • Métodos de classes, implementados com base em uma interface, retornando valores padrões ou jogando exceções
  • Implementações de métodos que não fazem sentido para a classe
  • Pouco sentido nas interfaces que existem no sistema
  • Muita alterações no código para adicionar um novo método na interface.
  • Ao chamar um método em uma classe não ter certeza se ele foi realmente implementado(isso acontece e bastante)

Exemplo: Digamos que você tenha uma interface IVeiculo e uma classe Carro, como descrito abaixo e agora é necessário implementar no sistema um objeto moto. Nesse exemplo a criação do objeto moto deve  implementar a interface IVeiculo. O Exemplo que quebra o principio ISP é assim:

    public interface IVehicleBreakingISP
    {
        void TurnOn();
        void TurnOff();

        void OpenDoor();
        void CloseDoor();
    }

    public class CarBreakingISP : IVehicleBreakingISP
    {

        public void TurnOn()
        {
            Console.WriteLine("Turn ON the car!!!!");
        }

        public void OpenDoor()
        {
            Console.WriteLine("Door is OPEN!!!!");
        }

        public void CloseDoor()
        {
            Console.WriteLine("Door is CLOSE!!!!");

        }

        public void TurnOff()
        {
            Console.WriteLine("Turn OFF the car!!!!");
        }
    }

    public class MotorcycleBreakingISP : IVehicleBreakingISP
    {
        public void TurnOn()
        {
            Console.WriteLine("Turn ON the Motorcycle!!!!");
        }

        public void TurnOff()
        {
            Console.WriteLine("Turn OFF the Motorcycle!!!!");

        }

        /// <summary>
        /// Do Not Call this
        /// </summary>
        public void OpenDoor()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Do Not Call this
        /// </summary>
        public void CloseDoor()
        {
            throw new NotImplementedException();
        }
    }

Uma refatoração viável para esse necessário ficar de acordo com o principio é assim:

    public interface IHasDoors
    {
        void OpenDoor();
        void CloseDoor();
    }
    public interface IVehicle
    {
        void TurnOn();
        void TurnOff();
    }

    public class Car : IVehicle, IHasDoors
    {

        public void TurnOn()
        {
            Console.WriteLine("Turn ON the car!!!!");
        }

        public void OpenDoor()
        {
            Console.WriteLine("Door is OPEN!!!!");
        }

        public void CloseDoor()
        {
            Console.WriteLine("Door is CLOSE!!!!");

        }

        public void TurnOff()
        {
            Console.WriteLine("Turn OFF the car!!!!");
        }
    }

    public class Motorcycle : IVehicle
    {
        public void TurnOn()
        {
            Console.WriteLine("Turn ON the Motorcycle!!!!");
        }

        public void TurnOff()
        {
            Console.WriteLine("Turn OFF the Motorcycle!!!!");

        }

    }

Foi separado o grupo de responsabilidades de veículos e veículos com portas, assim o veiculo moto apenas implementa a interface de IVeiculo, enquanto que o carro precisa implementar a interface IVeiculo e ITemPortas, pois faz sentido.

Caso tenha curiosidade de como pensar assim separando as interfaces, tente implementar essas duas funcionalidades:

  • Quero poder abrir o porta malas no meu objeto carro.
  • Quero que seja implantando outro veiculo com nome de avião e colocado as devidas responsabilidades nele a partir de interface.

Apenas uma dica para o uso desse principio, ele ajuda muito a organizar o código, porém é necessário fazer o máximo para programar para a interface. Isso quer dizer que você não deve declarar uma variável com o tipo final e sempre tentar usar as interfaces para diminuir o acoplamento do código. Isso deve ser feito quando possível, tem casos que isso pode não parecer valido.

Todo o código deste post está no github neste link.

Princípio de Aberto e Fechado(Open/closed principle – OCP)

O Princípio de Aberto e Fechado define:

“As entidades de software(classes, módulos, funções etc) devem

ser abertas para ampliação, mas fechadas para modificação”

A palavra chave deste princípio é a abstração quer qualquer linguagem de programação orientada a objetos possui esse recurso.Imagine o seguinte cenário você tem dois sistemas o Sistema A e o Sistema B, eles possuem uma integração aonde o Sistema A chama um determinado método de um determinado objeto do Sistema B. Se existir uma abstração dessa integração(interface), é possível implementar uma nova funcionalidade no Sistema B sem que seja alterado a chamada do Sistema A, pois a alteração feita no sistema B irá seguir a mesma interface de comunicação.

Um sistema que quebra esse princípio se torna um sistema dificilmente modificado, pois ao alterar uma parte do sistema pode afetar outras partes, isso mostra muita rigidez nas relações do sistema.

É necessário muito cuidado com ao seguir esse princípio, muitas classes devem sofrer uma refatoração e deverão ser alteradas para uma abstração do problema, mas não é recomendável aplicar abstração desenfreada em todas as partes do aplicativo. É necessário muita dedicação para aplicar abstração só nas partes do sistema que exibem alterações frequentes.Segundo o Robert C Martin “Resistir à abstração precipitada é tão importante quando a abstração em si”.

Sintomas mais aparentes da quebra deste principio:

  • Dificuldade nas alterações
  • Relações feitas sem abstração
  • Alterações em código funcionado para implementação de novas funcionalidades
  • Rigidez do sistema

Exemplos de casos e sua refatoração:

Exemplo: Digamos que tenha uma funcionalidade que desenha formas geométricas em uma aplicação console,  e já existe um código porém ele está difícil de adicionar novas formas para serem desenhadas assim é necessário uma refatoração para ficar de forma mais fácil a implementação de novas formas para desenhas. O código atual esta assim:

public class DrawingShapesWithoutOcp
    {
        public static void DrawShape(string shape)
        {
            switch (shape)
            {
                case "square":
                    Console.WriteLine("SQUARE");
                    Console.WriteLine(" -----------");
                    Console.WriteLine("|           |");
                    Console.WriteLine("|           |");
                    Console.WriteLine("|           |");
                    Console.WriteLine("|           |");
                    Console.WriteLine(" -----------");
                    break;

                case "rectangle":
                    Console.WriteLine("RETANGLE");
                    Console.WriteLine("     -------------");
                    Console.WriteLine("    /              \\");
                    Console.WriteLine("  /                  \\");
                    Console.WriteLine("/                      \\");
                    Console.WriteLine("------------------------");
                    break;
            }
        }
    }

Um código dessa forma que tem previsão de crescimento é muito importante que seja refatorado para nao sofrer dificuldade ou problemas no futuro, isso um exemplo que acontece muito em sistemas existentes. Isso é realmente um indicio para iniciar a refatorar o sistema!
Esse código até pode parecer simples rápido e pode ser facilmente implementado novas formas, porém imagina você criar mais de 100 formas com essa estrutura de código, ai se torna inviável manter um código assim. Esse código também quebra totalmente o principio de aberto e fechado, pois ele esta fechado para extensão, todas vez é necessário alterar o código que já esta funcionando para adicionar um novo comportamento ou funcionalidade.
Primeiro vamos separar o comportamento então criaremos uma abstração do comportamento que queremos com a interface IShape e depois criaremos as implementações dessa interface para cada forma que precisamos que precisamos:

    public interface IShape
    {
        //behavior
        void DrawShape();
    }

    public class RectangleShape : IShape
    {
        public void DrawShape()
        {
            Console.WriteLine("RETANGLE");
            Console.WriteLine("     -------------");
            Console.WriteLine("    /              \\");
            Console.WriteLine("  /                  \\");
            Console.WriteLine("/                      \\");
            Console.WriteLine("------------------------");
        }
    }

    public class SquareShape : IShape
    {
        public void DrawShape()
        {
            Console.WriteLine("SQUARE");
            Console.WriteLine(" -----------");
            Console.WriteLine("|           |");
            Console.WriteLine("|           |");
            Console.WriteLine("|           |");
            Console.WriteLine("|           |");
            Console.WriteLine(" -----------");
        }
    }
Depois disso vamos criar uma classe para executar esse comportamento, ela irá utilizar a interface para garantir uma abstração do problema, assim deve ficar a classe.
    public class DrawingShapes
    {
        IList<IShape> _shapes = new List<IShape>();

        public void AddShape(IShape shape)
        {
            _shapes.Add(shape);
        }
        public void RemoveShape(IShape shape)
        {
            _shapes.Remove(shape);
        }

        public void DrawImages()
        {
            foreach (var shape in _shapes)
            {
                shape.DrawShape();
            }
        }
    }
Apos isso você pode utilizar a classe para ver como ficaria a chamada como mostrado nesse código:
 static void Main(string[] args)
        {
            SampleOfOCP();
        }

        private static void SampleOfOCP()
        {
            //Create shapes
            IShape rectangle = new RectangleShape();
            IShape square = new SquareShape();
            DrawingShapes d = new DrawingShapes();

            //add feature to draw rectangle
            d.AddShape(rectangle);
            //drawing images
            d.DrawImages();

            Console.WriteLine("Press Any key to Contunue the sample of OCP");
            Console.ReadKey();

            //add feature to draw square
            d.AddShape(square);
            d.DrawImages();
            Console.ReadKey();
        }

Isso não traz uma complexidade muito alta de forma desnecessária?

Não, por dois motivos, mantendo o código da mesma forma, efetuar testes automatizados se torna realmente difícil efetuar testes e também é necessário alterar código em uma classe que esta funcionando para adicionar nova funcionalidade.

Se quiser fazer algo para entender melhor, implemente outras formas nos dois códigos para entender as dificuldade em uma forma e outra.

Todo o código deste post está no github neste link.

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.