7
respostas

Como não cobrar 20 cents para transferência, apenas para saques

    @Override 
    //método saca reescrito na classe conta corrente
    public boolean saca(double valor) {
        return super.saca(valor+0.2); //cobra 20 centavos para cada saque
    }

No exemplo com conta corrente e poupança da aula 4 sobre herança e polimorfismo, é solicitado que o banco cobre 20 centavos para cada saque. Porém também é descontado 20 cents nas transferências, visto que o método transfere aciona o método saca e o método deposita, e estes que alteram o atributo diretamente. A única forma de corrigir isso é fazendo com que o método transfere modifique diretamente o atributo saldo? Eu sei que não é indicado que ambos os métodos (saca e transfere) acionem o atributo saldo diretamente, mas não encontrei outra solução.

7 respostas

Olá André,

Uma solução pode ser fazer isso que você falou, mas também consigo ver outras duas, como por exemplo re-escrever o método transfere de forma que ele não fique completamente dependente do "saca" e portanto fazendo com que ele possua sua própria "regra de negócio", ou então fazendo a operação reversa do que você fez no saque ao retirar o dinheiro da conta que está transferindo o valor; por exemplo: se em todo saque você pede para tirar 0.2 além do "valor" basta pedir que ao transferir você retire o que o "saca" pedir menos os 0.2 adicionais que estão vinculados a ele.

Não sei se era isso que queria mas espero ter ajudado!

Boa tarde André!

Manipular diretamente o atributo saldo seria uma forma de fazer. Outra maneira seria criar um método sacaSemTarifa e utilizar esse método dentro do método transfere.

Fala André, tudo bem ?

Bom, a gente pode levar esse pensamento mais pro lado do conceito do negócio do que pro conceito técnico (que traz a vontade de reutilizar o código).

Pensando no conceito, a transferência é pensada inicialmente realmente como uma operação constituída (de forma integral) por duas sub-operações que são saque e depósito (com suas regras originais garantidas). A partir desse momento é que se deve concluir (do ponto de vista técnico) que a melhor prática de implementação é reaproveitar os códigos invocando-os dentro do transfere.

class Conta {
    private double saldo;

    public boolean saca(double valor) {
        if(this.saldo >= valor) {
            this.saldo -= valor;
            return true;
        }
        return false;
    }

    public void deposita(double valor) {
        this.saldo += valor;
    }

    public boolean transferePara(Conta destino, double valor) {
        if( this.saca(valor) ) {
            destino.deposita(valor);
            return true;
        }
        return false;
    }
}

class ContaCorrente extends Conta {

    @Override
    public boolean saca(double valor) {
            return super.saca(valor+0.2);
        }
}

Essa prática, além de deixar as coisas bem alinhadas conceitualmente, vem na verdade resolver um possível problema de repetição de código (e não necessariamente de muitos métodos acessarem o mesmo atributo diretamente).

Se não aplicássemos a técnica teríamos o seguinte possível trecho de implementação..

    public boolean saca(double valor) {
        if(this.saldo >= valor) {
            this.saldo -= valor;
            return true;
        }
        return false;
    }

    public boolean transferePara(Conta destino, double valor) {
        if(this.saldo >= valor) {
            this.saldo -= valor;
            destino.deposita(valor); // ou mesmo destino.saldo += valor =/
            return true;
        }
        return false;
    }

O real problema é a repetição do código. Se consideramos conceitualmente a tranferência como uma saque + deposito toda vez que o código do saque mudar o código do transfere fica desatualizado, e precisaremos dar manutenção em dois pontos.

Agora uma coisa é importante discutirmos. Quando começamos a mudar o pensamento conceitual dizendo que a taxa deveria ser cobrada apenas para o saque e não para a transferência estamos chegando a conclusão que a regra de negócio da transferência não é mais satisfeita por um saque e depois um deposito. Portanto, não faz sentido fazer mais o código do transfere chamar o saca. Se a ideia é geral pra todas as contas, se aplicaria a alteração nas implementações da classe mais generica Conta, já se a ideia é aplicar esse cenário apenas para a ContaCorrente as alterações poderiam vir na própria ContaCorrente através de mais um override dessa vez para o transfere.


class ContaCorrente extends Conta {

    @Override
    public boolean saca(double valor) {
        return super.saca(valor + 0.2);
    }

    // não chama o super pois se ele for chamado o saca vai ser usado cobrando taxa dessa operação
    // manipula o atributo de acordo com sua própria regra de negócio que (nesse novo pensamento) não tem mais relação com a regra do saque
    @Override
    public boolean transferePara(Conta destino, double valor) {
        if(this.saldo >= valor) {
            this.saldo -= valor;
            destino.deposita(valor);
            return true;
        }
        return false;

    }

Dessa forma a ideia seria justamente o transfere ter todo o código necessário para garantir sua responsabilidade. Não existe problema em mais de um método manipular o mesmo atributo, nenhuma regra está sendo afetada ao fazermos isso. Por exemplo, tanto saca como deposita manipulam o saldo diretamente, e não há nenhum problema com isso, muito pelo contrário, alguns métodos no geral vão manipular o(s) mesmo(s) atributos de uma classe. O problema é deixá-los descrevendo as mesmas tarefas sobre esse atributo e tendo repetição como citado antes.

Ainda é discutível se não poderíamos tentar extrair a pequena repetição gerada na implementação do override do transferePara (if verificando se pode fazer o saque, com a condição => se a condição mudar, temos que mudar no saca da Conta e no transfere da conta corrente). Poderíamos ter um método que isola essa responsabilidade com os métodos saca e transfere reaproveitando quando for necessário. Algo como um conta.podeSacar(valor) (ou algum outro nome que descreva bem a responsabilidade).

Aí o saca do Conta faz:

    public boolean saca(double valor) {
        if (this.podeSacar(valor)) {
            this.saldo -= valor;
            return true;
        }
        return false;
    }

e o transfere especifico da ContaCorrente:

    @Override
    public boolean transferePara(Conta destino, double valor) {
        if(this.podeSacar(valor)) {
            this.saldo -= valor;
            destino.deposita(valor);
            return true;
        }
        return false;    
    }

Aqui vai ser inevitável essa similaridade.

PS: Deve-se usar exceptions ao invés de retorno pra controlar pontos de falha'

Espero ter ajudado no pensamento. Abraço!

Opa e aí pessoal?

Só pra completar a discussão (que é sempre bom), temos que tomar um cuidado e com o trade off de cada implementação.

A possibilidade de a reescrita do transfere na ContaCorrente "devolver" a taxa (subtraindo os 0.2 antes do saca somar o valor da taxa) quebra o encapsulamento. Os módulos em geral (sejam serviços, classes, métodos, etc, etc) devem considerar apenas o que o método faz e não como o método faz. Para garantir esse princípio método transfere não deveria ser escrito levando em consideração detalhes de implementação do saca (ele nem deveria saber que la dentro soma-se 0.2 ao valor solicitado). O problema disso pode ser pensado se supormos uma futura manutenção na regra do saque. Se o saca passar a cobrar um valor diferente (seja mais, menos, ou simplesmente volta a não cobrar) o transfere automaticamente passa a operar de forma errada descontando um valor errado sem nem a gente perceber ( se não tivermos uma bateria confiável de testes unitários =] ). Tem acoplamento aí.

A questão do sacaSemTarifa temos que tomar um cuidado também. Se a regra do banco é cobrar as tarifas das ContaCorrente faz sentido ter um método que permite sacar sem essa cobrança? Uma outra coisa que devemos ter cuidado também é que criando uma lógica adicional no código podemos comprometer o funcionamento de códigos que tentem tirar proveito das implementações de forma polimorfica.

Enfim. Toda escolha traz uma consequência futura ao projeto. A parte boa é que essas discussões são legais e acrescentam várias formas de pensar pra evitar problemas.

Abraços!

Show de bola Rafael! Muito obrigado pelas explicações!

Pelo que o André postou, ele não explicitou diferença entre ContaCorrente e ContaPoupanca, seria uma regra geral, logo, a lógica seria implementar na Classe Conta.

Realmente essas discussões são muito boas! Aprendemos muito com elas!

E aí André! Qual rumo você vai seguir? :-)

Muito bom Rafael, obrigado pela explicação completaça!

Boa pessoal.. Parabéns pela participação na comunidade do fórum e por ajudar os colegas.

Abraços!