.NET
- Princípio TAD : 'Tell don't ask'
![]() |
Neste artigo vou recordar o princípio 'Tell don´t ask'. |
O princípio "Tell, Don't Ask" nos incentiva a estruturar nosso código de forma que os objetos recebam comandos em vez de expor seus dados para que outro código decida o que fazer. Isso reforça o encapsulamento e reduz o acoplamento, tornando o código mais orientado a objetos
Vejamos a seguir um código que viola esse princípio onde temos o serviço ContaBancariaService que pode informações de Saldo da conta representa pela classe ContaBancaria e depois toma a decisão sobre a operação.
1- ContaBancaria
public class ContaBancaria { public decimal Saldo { get; private set; } public ContaBancaria(decimal saldoInicial) { Saldo = saldoInicial; } public void Depositar(decimal valor) { Saldo += valor; } public void Sacar(decimal valor) { if (Saldo >= valor) Saldo -= valor; else throw new InvalidOperationException("Saldo insuficiente!"); } } |
2- ContaBancariaService
public class ContaBancariaService { public void ProcessarSaque(ContaBancaria conta, decimal valor) { if (conta.Saldo >= valor) { conta.Sacar(valor); } else { Console.WriteLine("Saldo insuficiente!"); } } } |
Neste cenário a classe ContaBancariaService está perguntando o saldo (conta.Saldo) e decidindo se pode fazer o saque. Isso quebra o princípio "Tell, Don’t Ask", pois o comportamento deveria estar dentro da própria ContaBancaria.
Vejamos como fica o código ajustado para que ContaBancaria seja responsável por suas próprias regras de negócio, sem expor o saldo diretamente
public class ContaBancaria { private decimal _saldo; public ContaBancaria(decimal saldoInicial) { _saldo = saldoInicial; } public bool TentarSacar(decimal valor) { if (_saldo >= valor) { _saldo -= valor; return true; } return false; } } |
A seguir temos o código da classe ContaBancariaService :
public class ContaBancariaService { public void ProcessarSaque(ContaBancaria conta, decimal valor) { if (conta.TentarSacar(valor)) { Console.WriteLine("Saque realizado com sucesso!"); } else { Console.WriteLine("Saldo insuficiente!"); } } } |
O que mudou ?
✔ Agora, ContaBancaria decide se o saque pode ser feito (TentarSacar) e realiza a operação.
✔ O serviço ContaBancariaService diz à conta o que fazer, em vez de pedir informações para tomar decisões.
✔ O encapsulamento foi preservado, evitando exposição indevida de dados.
Conclusão
O princípio "Tell, Don't Ask" nos ajuda a estruturar o código de forma que os próprios objetos sejam responsáveis por suas operações, em vez de expor dados para que outra classe tome decisões. Isso melhora a coesão, reduz o acoplamento e torna o código mais alinhado com os princípios da Programação Orientada a Objetos.
Naturalmente podemos melhorar o código incluindo o validações adicionais, logs ou notificações e o lançamento de exxceções ao invés de retornar um booleano.
Código ajustado:
public class ContaBancaria { private decimal _saldo; private decimal _limiteDiario; private decimal _saqueHoje; public ContaBancaria(decimal saldoInicial, decimal limiteDiario) { _saldo = saldoInicial; _limiteDiario = limiteDiario; _saqueHoje = 0; } public void Sacar(decimal valor) { if (valor <= 0) throw new ArgumentException("O valor do saque deve ser maior que zero."); if (_saqueHoje + valor > _limiteDiario) throw new InvalidOperationException("Limite diário de saque excedido!"); if (_saldo < valor) throw new InvalidOperationException("Saldo insuficiente!"); _saldo -= valor; _saqueHoje += valor; Console.WriteLine($"Saque de R${valor:F2} realizado com sucesso."); } public decimal ObterSaldo() => _saldo; } |
Código do serviço ajustado:
public class ContaBancariaService { public void ProcessarSaque(ContaBancaria conta, decimal valor) { try { conta.Sacar(valor); RegistrarLog($"Saque de R${valor:F2} realizado."); } catch (Exception ex) { RegistrarLog($"Erro ao tentar sacar R${valor:F2}: {ex.Message}"); Console.WriteLine($"Operação falhou: {ex.Message}"); } } private void RegistrarLog(string mensagem) { // Simulando log (poderia ser para um arquivo, banco de dados, etc.) Console.WriteLine($"[LOG] {DateTime.Now}: {mensagem}"); } } |
Para testar podermos usar o código a seguir na classe Program:
class Program { static void Main() { var conta = new ContaBancaria(1000, 500); // Saldo inicial de R$1000, limite diário de R$500 var service = new ContaBancariaService(); service.ProcessarSaque(conta, 200); service.ProcessarSaque(conta, 400); // Deve falhar por exceder o limite diário service.ProcessarSaque(conta, -50); // Deve falhar por valor inválido service.ProcessarSaque(conta, 900); // Deve falhar por saldo insuficiente } } |
O que melhoramos ?
✔ Lançamento de exceções → Garante que erros
sejam tratados corretamente.
✔ Limite diário de saque → Protege contra saques
excessivos.
✔ Registro de logs → Ajuda a monitorar operações e identificar
falhas.
✔ Melhoria na validação → Evita saques negativos e torna o código
mais robusto.
Agora, ContaBancaria é totalmente responsável por suas regras, enquanto ContaBancariaService apenas coordena as operações e trata exceções.
Agora temos um design sólido, seguindo Tell, Don’t Ask, encapsulamento adequado, e boas práticas de logging e validação.
E estamos conversados...
"Porquanto não há diferença entre judeu e grego; porque um mesmo
é o Senhor de todos, rico para com todos os que o invocam."
Romanos
10:12
Referências:
NET - Unit of Work - Padrão Unidade de ...