Usando final público em vez de getters privados

51

Eu vejo os POJOs mais imutáveis ​​escritos assim:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

No entanto, tenho a tendência de escrevê-los assim:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Observe que as referências são finais, portanto o objeto ainda é imutável. Deixa-me escrever menos código e permite mais curto (por 5 caracteres: o gete ()) acesso.

A única desvantagem que vejo é que, se você deseja alterar a implementação do getFoo()caminho para fazer algo louco, não pode. Mas realisticamente, isso nunca acontece porque o Objeto é imutável; você pode verificar durante a instanciação, criar cópias defensivas imutáveis ​​durante a instanciação (consulte Goiabas, ImmutableListpor exemplo) e preparar os objetos fooou barpara a getchamada.

Faltam algumas desvantagens?

EDITAR

Suponho que outra desvantagem que estou perdendo são as bibliotecas de serialização usando reflexão sobre os métodos que começam com getor is, mas essa é uma prática bastante terrível ...

Cory Kendall
fonte
Que diabos? finalnão torna uma variável o objeto imutável. Eu normalmente uso um design em que defino finalcampos antes de criar retorno de chamada para que o retorno de chamada possa acessar esses campos. Obviamente, ele pode chamar todos os métodos, incluindo quaisquer setXmétodos.
Tomáš Zato
@ TomášZato Não há métodos setX sobre String, intou MyObject. Na primeira versão, finalé apenas para garantir que outros métodos além do construtor da classe não tentem bar = 7;, por exemplo. Na segunda versão, finalé necessário evitar que os consumidores a fazer: MyObject x = new MyObject("hi", 5); x.bar = 7;.
Cory Kendall #
11
Bem, seu " Observe que as referências são finais, portanto Objectainda é imutável " é enganoso - dessa maneira, parece que você acha que algum final Objecté imutável, o que não é. Desculpe pelo mal entendido.
Tomáš Zato
3
Se os valores subjacentes fossem mutáveis, a estrada privada + de acessadores também não a impediria - myObj.getFoo().setFrob(...).
Beni Cherniavsky-Paskin

Respostas:

38

Quatro desvantagens que consigo pensar:

  1. Se você deseja ter uma forma somente leitura e mutável da mesma entidade, um padrão comum é ter uma classe imutável Entidade que exponha apenas acessadores com variáveis ​​de membro protegidas, crie uma MutableEntity que a estenda e adicione setters. Sua versão evita isso.
  2. O uso de getters e setters segue a convenção JavaBeans. Se você quiser usar sua classe como um bean em tecnologias baseadas em propriedades, como JSTL ou EL, precisará expor getters públicos.
  3. Se você quiser alterar a implementação para derivar os valores ou procurá-los no banco de dados, precisará refatorar o código do cliente. Uma abordagem de acessador / mutador permite alterar apenas a implementação.
  4. Menos espanto - quando vejo variáveis ​​de instância pública, procuro imediatamente quem pode estar mudando e tenho medo de abrir a caixa de pandora porque o encapsulamento está perdido. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

Dito isto, sua versão é definitivamente mais concisa. Se essa era uma classe especializada usada apenas dentro de um pacote específico (talvez o escopo do pacote seja uma boa idéia aqui), posso considerar isso para casos pontuais. Mas eu não exporia grandes APIs como esta.

Brandon
fonte
3
Ótimas respostas; Discordo de alguns deles, mas não tenho certeza de que este site seja o melhor fórum para discussão. Em suma, 1) eu posso quebrar os outros usando a forma mutável onde eles assumem que é imutável (talvez eu deva adicionar final à classe em meus exemplos), 2) eu concordo, 3) eu argumentaria que não é mais um POJO nesse sentido 4) Concordo por enquanto, mas espero que discussões como essa possam mudar o que é menos surpreendente :).
Cory Kendall
2
5) Você não pode expor com segurança classes / tipos inerentemente mutáveis ​​- como matrizes. A maneira segura é retornar uma cópia de um getter toda vez.
Clockwork-Muse
@ Clockwork-Muse você pode, se você presumir que as pessoas não são idiotas. Ao acessar someObj.thisList.add (), é óbvio que você está modificando outros estados de objetos. Com someObj.getThisList (). Add () é desconhecido. Você precisa pedir a documentação ou procurar a fonte, mas apenas a partir da declaração do método é impossível saber.
precisa saber é o seguinte
2
@kritzikratzi - O problema é que você não pode garantir que seu código não seja idiota. Em algum momento, isso acontecerá (que pode até acabar sendo você mesmo!), E a surpresa poderá ser um enorme problema.
Clockwork-Muse
11
Bem, deixar qualquer tipo mutável herdar de um tipo imutável é inerentemente errado. Lembre-se de que um tipo imutável dá a garantia "Eu não mudo. Sempre".
Deduplicator
26

Livre-se dos getters / setters também, e você está bem!

Este é um tópico altamente controverso entre os programadores Java.

De qualquer forma, existem duas situações em que eu uso variáveis ​​públicas em vez (!) De getters / setters:

  1. final pública Para mim, isso significa "eu sou imutável" muito melhor do que apenas um espectador. A maioria dos IDEs indicará o modificador final com um 'F' durante o preenchimento automático. Ao contrário dos getters / setters, onde você deve procurar a ausência de um setXXX.
  2. público não final Adoro isso para classes de dados. Acabei de expor todos os campos publicamente. Sem getters, setters, construtores. Não há nada. Menos que um pojo. Para mim, isso imediatamente indica "olha, eu sou burro. Eu guardo dados, isso é tudo. É SEU trabalho colocar os dados certos dentro de mim". Gson / JAXB / etc. lidar com essas classes muito bem. Eles são uma felicidade de escrever. Não há dúvida sobre seu objetivo ou capacidade. E o mais importante: você sabe que não há efeitos colaterais ao alterar uma variável. IMHO isso resulta em modelos de dados muito concisos, com poucas ambiguidades, enquanto getters e setters têm esse enorme problema, onde às vezes a mágica acontece dentro deles.
kritzikratzi
fonte
5
É bom ver alguma sanidade de vez em quando :)
Navin
2
Até que chegue o dia em que seu modelo evolua e um dos campos antigos possa ser derivado de outro. Como você deseja manter a compatibilidade com versões anteriores, o campo antigo deve permanecer, mas, em vez de apenas alterar o código getter e setter, você precisará alterar em qualquer outro lugar onde esse campo antigo foi usado para garantir que segue a nova representação do modelo. Fácil de escrever .. sim, claro ... pesadelo para manter a longo prazo ... É por isso que temos getter / setter ou, melhor ainda, em acessadores de propriedade C #.
Newtopian 31/08/16
9

Nas palavras dos leigos:

  • Você viola o encapsulamento para salvar algumas linhas de código. Isso derrota o propósito do OOD.
  • O código do cliente será fortemente associado aos nomes dos alunos. O acoplamento está ruim. Todo o objetivo do OOD é impedir o acoplamento.
  • Você também tem certeza de que sua turma nunca precisará ser mutável. As coisas mudam. Mudança é a única coisa que é constante.
Tulains Córdova
fonte
6
Como isso é uma violação do encapsulamento? Ambas as versões são tão expostas ao mundo externo quanto entre si (com acesso somente leitura). Você está correto sobre o acoplamento rígido e a inflexibilidade da mudança, porém, não vou discutir isso.
KChaloux
4
@KChaloux Você confunde "violação de encapsulamento" com "código que falha na compilação e perda de tempo para corrigi-lo", acontece primeiro. O segundo acontece depois. Para deixar claro: o encapsulamento já está violado no presente. As entranhas da sua classe não são mais encapsuladas no momento. Mas o código do cliente será interrompido no futuro se a classe mudar no futuro, porque o encapsulamento foi quebrado no passado. Existem duas "quebras" diferentes: encapsulamento hoje, código do cliente amanhã, devido à falta de encapsulamento.
Tulains Córdova
8
Tecnicamente, quando você usa getters / etc. o código do cliente será fortemente associado aos nomes dos métodos. Veja quantas bibliotecas precisam incluir versões antigas de métodos com uma tag / anotação "obsoleta", porque o código mais antigo ainda depende delas (e algumas pessoas ainda podem usá-las porque as consideram mais fáceis de trabalhar, mas você não é deveria fazer isso).
JAB
5
Pragmático: com que frequência os programadores realmente refatoram nomes de campos privados sem renomear os getters / setters públicos? Pelo que sei, existe uma convenção em toda a comunidade para nomeá-los da mesma forma, mesmo que essa convenção possa resultar apenas de ferramentas IDE que geram getters e setters a partir de nomes de campos. Teórico: No entanto, de uma perspectiva de modelagem orientada a objetos, qualquer coisa pública faz parte do contrato público e não um detalhe interno da implementação. Então, se alguém decide tornar público um campo final, eles não estão dizendo que este é o contrato que prometem cumprir?
Thomas Jung
3
@ user949300 Diga-me quais para que eu possa aprender.
Tulains Córdova
6

Uma possível desvantagem que vejo de imediato é que você está vinculado à representação interna dos dados na classe. Isso provavelmente não é um grande negócio, mas se você usar os setters e decidir que foo e bar serão retornados de alguma outra classe definida em outro lugar, as classes que consomem MyObject não precisam mudar. Você só precisaria tocar em MyObject. No entanto, se você usar os valores nus, terá que tocar em todos os lugares que o MyObject eu usei.

ipaul
fonte