“Tell, don’t ask” é uma das práticas mais importantes da orientação a objetos, pois tem por maior objetivo reforçar a ideia de encapsulamento, conceito fundamental desse paradigma.
O nome vem do fato de que devemos dizer (tell) ao objeto o que fazer ao invés de perguntarmos (ask) ao objeto sobre seu estado e tomarmos alguma decisão.
Vamos a um exemplo onde “Tell, don’t ask” não é aplicado:
// ---- código-consumidor do objeto "carro" ---- carro.AcelerarPara(150); // violando o "Tell, don't ask" if (carro.VelocidadeAtual > carro.VelocidadeMaxima) throw new Exception("Velocidade superior à velocidade máxima!");
Observem que, apesar de “dizer” para o carro acelerar, o código ainda pede ao carro sua velocidade atual e a velocidade máxima para fazer uma validação.
Por que não deixar que o carro “se vire” com suas próprias regras de aceleração?
// na classe Carro fica assim: public class Carro { private int _velocidadeAtual; private int _velocidadeMaxima; // outros membros da classe public void AcelerarPara(int velocidade) { if (velocidade > _velocidadeMaxima) throw new Exception("Velocidade superior à velocidade máxima!"); _velocidadeAtual = velocidade; } } // ---- código-consumidor do objeto carro ---- carro.AcelerarPara(150);
Agora sim! Veremos alguns problemas da primeira abordagem mais abaixo. Antes vamos a outro exemplo. Neste, o objeto possui uma coleção, que é manipulada fora dele.
// ---- código-consumidor do objeto "pedido" ---- // criando o item novo var novoItem = new ItemDoPedido(produto, quantidade); // violando o "Tell, don't ask" pedido.Itens.Add(novoItem);
Percebam que estamos pedindo a lista de itens ao pedido e, então, adicionando um item novo a essa lista. Isso quebra claramente o encapsulamento da classe Pedido.
Por que não dizer ao pedido que adicione um item para nós? Vejamos uma forma de se fazer isso:
// na classe Pedido fica assim: public class Pedido { private IList<ItemDoPedido> _itens = new List<ItemDoPedido>(); // outros membros da classe public void Adicionar(Produto produto, int quantidade) { var novoItem = new ItemDoPedido(produto, quantidade); _itens.Add(novoItem); } } // ---- código-consumidor do objeto "pedido" ---- // agora seguindo o "Tell, don't ask" pedido.Adicionar(produto, quantidade);
Muito melhor, não? Agora é responsabilidade do pedido adicionar um item novo. O encapsulamento dos itens foi preservado. Qualquer alteração na regra de adicionar um item – como uma validação – é feita somente em um único lugar do software.
(Embora não seja foco do exemplo nem do artigo em si, notem que eu também passei a responsabilidade de criar o item para o próprio pedido, no método Adicionar, imaginando que isso fosse necessário somente ali.)
ISSO NÃO É OO!
Códigos que não consideram o “Tell, don’t ask” são códigos PROCEDURAIS. Um objeto de verdade possui dados e comportamento para manipular esses dados (princípio básico de orientação a objetos).
Código procedural não expressa claramente que ação está sendo executada. Ao invés de algo objetivo como “EncerrarConta”, podemos ter uma série de condições e atribuições em cima de um ou mais dados do objeto.
Além disso, damos margem para que uma mesma lógica se repita em mais de um lugar do software. E sabemos que duplicação de código é do mal!
Esses problemas são potencializados a medida que a base de código cresce e se torna mais complexa.
Centralizando dados e comportamento em um mesmo lugar, evitamos os problemas acima e facilitamos a manutenção do código.
RESUMINDO TUDO…
Diga ao objeto o que você quer que ele faça!
No caso do método [Adicionar], não seria melhor ele pedir um objeto do tipo [ItemDoPedido] já preenchido como parâmetro ao invés de pedir dezenas de parâmetros?
CurtirCurtir
Olá, Jonatan!
É uma opção, porém estou assumindo que ali é o único lugar que um item será usado e como ele é parte do pedido, nada mais justo que o pedido saiba como criá-lo. O “mundo externo” nem precisa saber da existência de um item.
——————————————————-
Qto à qtd de parâmetros, são apenas 2 e bem coesos com o propósito do método.. Se a qtd de parâmetros fosse maior, aí poderia ser o caso de encapsulá-los em 1 (ou mais) objetos ou ainda poderia haver um problema maior: o método estar fazendo coisa demais.
[]s
CurtirCurtir
Post excelente Robson!!!
Até hoje, nos meus 8 anos de experiência eu vi apenas 1 software realmente OO, e eu já trabalhei em várias empresas com C#.É possível ver que
as pessoas ainda não entenderam OO, por isso, temos pessoas programando procedural em linguagens que fornecem todos os meios para programar bem
orientado a objeto.
Só como exemplo do que eu já vi muitas vezes, o programador cria uma classe cliente, e dentro dela “SOCA” qualquer método que faça referência ao
cliente, deixando a classe com mais de 1000 linhas, sem seguir o SPR, DRY, estado, comportamente e qualquer outro conceito que faça com que aquele
código seja OO.
CurtirCurtir
Obrigado, Roger
Escrever código “direito” é bem difícil e só há poucos anos que as melhores práticas se tornaram mais difundidas. Entretanto, o mal já estava feito (legados e mais legados mal-escritos que ainda formam a maior parte do código mantido hoje em dia).
[]s!
CurtirCurtir
Ótima postagem, assunto de grande utilidade para escrevermos código de melhor qualidade. Pena que estas boas práticas não foram difundidas antes e vamos sofrer um bom tempo com código legado OO escrito de forma procedural.
CurtirCurtir
Obrigado, Kauan.
Verdade… nossa área é nova e ainda está se desenvolvendo. Vamos continuar propagando boas práticas!
[]s
CurtirCurtir