O encapsulamento ainda é um dos elefantes em que o OOP permanece?


97

O encapsulamento diz-me para tornar todos ou quase todos os campos privados e expô-los por getters / setters. Mas agora aparecem bibliotecas como o Lombok, que nos permitem expor todos os campos particulares com uma breve anotação @Data. Ele criará getters, setters e construtores de configuração para todos os campos particulares.

Alguém poderia me explicar qual é o sentido de ocultar todos os campos como privados e depois expor todos eles por alguma tecnologia extra? Por que simplesmente não usamos apenas campos públicos? Sinto que percorremos um caminho longo e difícil apenas para retornar ao ponto de partida.

Sim, existem outras tecnologias que funcionam através de getters e setters. E não podemos usá-los através de campos públicos simples. Mas essas tecnologias apareceram apenas porque tínhamos essas inúmeras propriedades - campos privados por trás de getters / setters públicos. Se não tivéssemos as propriedades, essas tecnologias se desenvolveriam de outra maneira e suportariam campos públicos. E tudo seria simples e não precisaríamos mais do Lombok agora.

Qual é o sentido de todo esse ciclo? E o encapsulamento tem realmente algum sentido agora na programação da vida real?


19
"It will create getters, setters and setting constructors for all private fields."- Do jeito que você descreve essa ferramenta, parece que ela está mantendo o encapsulamento. (Pelo menos em um sentido frouxo, automatizado e de certa forma anêmico). Então, qual é exatamente o problema?
David

78
O encapsulamento está ocultando internamente a implementação do objeto por trás de seu contrato público (geralmente, uma interface). Os getters e setters fazem exatamente o oposto - eles expõem as partes internas do objeto; portanto, o problema está nos getters / setters, não no encapsulamento.

22
As classes de dados @VinceEmigh não têm encapsulamento . Instâncias deles são valores em exatamente a sensação de que os primitivos são
Caleth

10
@VinceEmigh usando JavaBeans não é OO , é Procedural . Que a literatura os chame de "objetos" é um erro histórico.
Caleth 5/17

28
Eu pensei muito nisso ao longo dos anos; Penso que este é um caso em que a intenção do OOP difere da implementação. Depois de estudar o SmallTalk, ficou claro qual deveria ser o encapsulamento da OOP (ou seja, cada classe sendo como um computador independente, com métodos como o protocolo compartilhado), embora por razões até agora desconhecidas para mim, definitivamente tenha conquistado popularidade para torná-las objetos getter / setter que não oferecem encapsulamento conceitual (eles não escondem nada, gerenciam nada e não têm nenhuma responsabilidade além dos dados), e ainda assim usam propriedades.
JRH

Respostas:


82

Se você expõe todos os seus atributos com getters / setters, está obtendo apenas uma estrutura de dados que ainda é usada em C ou em qualquer outra linguagem processual. É não encapsulamento e Lombok é apenas fazer para trabalhar com código de procedimento menos doloroso. Getters / setters tão ruins quanto campos públicos simples. Realmente não há diferença.

E a estrutura de dados não é um objeto. Se você começar a criar um objeto escrevendo uma interface, nunca adicionará getters / setters à interface. A exposição de seus atributos leva ao código processual de espaguete, onde a manipulação com dados está fora de um objeto e se espalha por toda a base de código. Agora você está lidando com dados e com manipulações com dados em vez de falar com objetos. Com getters / setters, você terá uma programação procedural orientada a dados, onde a manipulação é feita de maneira absolutamente imperativa. Obtenha dados - faça alguma coisa - defina dados.

No POO, o encapsulamento é um elefante, se feito da maneira correta. Você deve encapsular os detalhes do estado e da implementação para que o objeto tenha controle total sobre isso. A lógica será focada no objeto e não será espalhada por toda a base de código. E sim - o encapsulamento ainda é essencial na programação, pois o código será mais sustentável.

EDITAR% S

Depois de ver as discussões, quero adicionar várias coisas:

  • Não importa quantos dos seus atributos você expõe por meio de getters / setters e com que cuidado você está fazendo isso. Ser mais seletivo não tornará seu código OOP com encapsulamento. Todo atributo exposto levará a algum procedimento que trabalha com esses dados nus de maneira imperativa. Você apenas espalhará seu código menos lentamente por ser mais seletivo. Isso não muda o núcleo.
  • Sim, nos limites do sistema, você obtém dados nus de outros sistemas ou banco de dados. Mas esses dados são apenas outro ponto de encapsulamento.
  • Objetos devem ser confiáveis . Toda a idéia de objetos é ser responsável, para que você não precise dar ordens diretas e imperativas. Em vez disso, você está pedindo a um objeto que faça o que faz bem através do contrato. Você delega com segurança parte ativa ao objeto. Os objetos encapsulam detalhes de estado e implementação.

Portanto, se voltarmos à pergunta por que deveríamos fazer isso. Considere este exemplo simples:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Aqui temos o documento expondo detalhes internos pelo getter e temos um código de procedimento externo em printDocumentfunção que funciona com o que está fora do objeto. Por que isso é ruim? Porque agora você só tem código no estilo C. Sim, está estruturado, mas qual é realmente a diferença? Você pode estruturar funções C em arquivos diferentes e com nomes. E as chamadas camadas estão fazendo exatamente isso. A classe de serviço é apenas um monte de procedimentos que funcionam com dados. Esse código é menos sustentável e tem muitas desvantagens.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

Compare com este. Agora temos um contrato e os detalhes de implementação deste contrato estão ocultos dentro do objeto. Agora você pode realmente testar essa classe e essa classe está encapsulando alguns dados. O modo como ele trabalha com esses dados é um objeto preocupante. Para falar com o objeto agora, você precisa solicitar que ele se imprima. Isso é encapsulamento e isso é um objeto. Você obterá todo o poder da injeção de dependência, zombaria, teste, responsabilidades únicas e muitos benefícios com o OOP.


Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
maple_shaft

1
"Getters / setters tão ruins quanto campos públicos simples" - isso não é verdade, pelo menos em muitos idiomas. Normalmente, você não pode substituir o acesso ao campo simples, mas pode substituir os getters / setters. Isso dá versatilidade às aulas infantis. Também facilita a alteração do comportamento das classes. por exemplo, você pode começar com um getter / setter apoiado por um campo privado e depois mover para um campo diferente ou calcular o valor de outros valores. Nem pode ser feito com campos simples. Algumas linguagens permitem que os campos possuam getters e setters essencialmente automáticos, mas o Java não é uma linguagem assim.
Kat

Ainda não está convencido. Seu exemplo é bom, mas apenas denota um estilo diferente de codificação, não o "verdadeiro certo" - que não existe. Lembre-se de que a maioria dos idiomas é multiparadigm hoje em dia e raramente é pura OO ou puro procedimento. Aderindo-se tanto aos "conceitos puros de OO". Como exemplo - você não pode usar DI no seu exemplo porque acoplou a lógica de impressão a uma subclasse. A impressão não deve fazer parte de um documento - um PrintableDocument exporia sua parte imprimível (via getter) a um PrinterService.
T. Sar

Uma abordagem OO adequada para isso, se formos para uma abordagem "Pure OO", usaria algo que implementa uma classe PrinterService abstrata solicitando, por meio de uma mensagem, o que diabos deveria ser impresso - usando uma espécie de GetPrintablePart. O que implementa o PrinterService pode ser qualquer tipo de impressora - imprimir em PDF, na tela ou no TXT ... Sua solução torna impossível trocar a lógica de impressão por outra coisa, ficando mais acoplada e menos sustentável do que o seu exemplo "errado".
T. Sar

A linha inferior: Getters e Setters não são maus ou quebram OOP - Pessoas que não entendem como usá-los são. Seu exemplo de caso é um exemplo de livro didático de pessoas que não entendem exatamente como o DI funciona. O exemplo que você "corrigiu" já estava habilitado para DI, desacoplado, poderia ser ridicularizado facilmente ... Realmente - lembra-se de toda a coisa "Preferir composição sobre herança"? Um dos pilares da OO? Você apenas jogou pela janela pela maneira como refatorou o código. Isso não afetaria nenhuma revisão séria de código.
T. Sar

76

Alguém poderia me explicar, qual é o sentido de ocultar todos os campos como privados e depois expor todos eles por alguma tecnologia extra? Por que simplesmente não usamos apenas campos públicos?

A sensação é de que você não deveria fazer isso .

Encapsulamento significa que você expõe apenas os campos que realmente precisa de outras classes para acessar e que é muito seletivo e cuidadoso.

Você não apenas dar todos os campos getters e setters por padrão!

Isso é totalmente contra o espírito da especificação JavaBeans, que é ironicamente a origem do conceito de getters e setters públicos.

Mas se você olhar para as especificações, verá que a criação de getters e setters é muito seletiva e fala sobre propriedades "somente leitura" (sem setter) e propriedades "somente leitura" (sem getter).

Outro fator é que getters e setters não são necessariamente simples acesso ao campo privado . Um getter pode calcular o valor que retorna de uma maneira arbitrariamente complexa, talvez armazená-lo em cache. Um setter pode validar o valor ou notificar os ouvintes.

Então aí está: encapsulamento significa que você expõe apenas a funcionalidade que realmente precisa expor. Mas se você não pensa no que precisa expor e apenas quer expor tudo seguindo alguma transformação sintática, é claro que isso não é realmente um encapsulamento.


20
"[G] etters e setters não são necessariamente acesso simples" - acho que esse é um ponto-chave. Se o seu código estiver acessando um campo pelo nome, você não poderá alterar o comportamento posteriormente. Se você estiver usando obj.get (), você pode.
Dan Ambrogio 5/10

4
@jrh: Eu nunca ouvi falar de uma prática ruim em Java, e é bastante comum.
Michael Borgwardt

2
@MichaelBorgwardt Interessante; um pouco fora do tópico, mas sempre me perguntei por que a Microsoft não recomendou isso em C #. Eu acho que essas diretrizes implicam que, em C #, a única coisa em que os setters podem ser usados ​​é converter um valor fornecido para outro valor usado internamente, de uma maneira que não possa falhar ou ter um valor inválido (por exemplo, ter uma propriedade PositionInches e uma propriedade PositionCentimeter onde ele converte automaticamente internamente para mm)? Não é um ótimo exemplo, mas é o melhor que posso apresentar no momento.
Jrh #

2
@jrh essa fonte diz para não fazer isso por getters, ao contrário de setters.
Captain Man

3
@ Gangnus e você realmente vê que alguém está escondendo alguma lógica dentro do getter / setter? Estamos tão acostumados a isso que getFieldName()se tornam um contrato automático para nós. Não esperaremos algum comportamento complexo por trás disso. Na maioria dos casos, é apenas uma quebra direta do encapsulamento.
Izbassar Tolegen

17

Eu acho que o cerne da questão é explicado pelo seu comentário:

Eu concordo totalmente com o seu pensamento. Mas em algum lugar temos que carregar objetos com dados. Por exemplo, do XML. E as plataformas atuais que o suportam fazem isso através de getters / setters, degradando assim a qualidade do nosso código e nossa maneira de pensar. Lombok, na verdade, não é ruim por si só, mas sua própria existência mostra que temos algo ruim.

O problema que você tem é que você está misturando o modelo de dados de persistência com o modelo de dados ativo .

Um aplicativo geralmente terá vários modelos de dados:

  • um modelo de dados para conversar com o banco de dados,
  • um modelo de dados para ler o arquivo de configuração,
  • um modelo de dados para conversar com outro aplicativo,
  • ...

além do modelo de dados que ele realmente usa para realizar seus cálculos.

Em geral, os modelos de dados usados ​​para comunicação com o exterior devem ser isolados e independentes do modelo de dados interno (modelo de objeto de negócios, BOM) no qual os cálculos são executados:

  • independente : para que você possa adicionar / remover propriedades na lista técnica conforme suas necessidades, sem precisar alterar todos os clientes / servidores, ...
  • isolado : para que todos os cálculos sejam executados na BOM, onde vivem os invariantes, e que a mudança de um serviço para outro ou a atualização de um serviço não cause ondulações em toda a base de código.

Nesse cenário, é perfeitamente aceitável que os objetos usados ​​nas camadas de comunicação tenham todos os itens públicos ou expostos por getters / setters. Esses são objetos simples, sem invariáveis.

Por outro lado, sua lista técnica deve ter invariantes, o que geralmente impede muitos montadores (getters não afetam os invariantes, embora reduzam o encapsulamento em um grau).


3
Eu não criaria getters e setters para objetos de comunicação. Eu apenas tornaria os campos públicos. Por que criar mais trabalho do que é útil?
usar o seguinte comando

3
@immibis: Eu concordo em geral, no entanto, conforme observado pelo OP, algumas estruturas exigem getters / setters, caso em que você apenas precisa cumprir. Observe que o OP está usando uma biblioteca que os cria automaticamente com a aplicação de um único atributo, portanto, é pouco trabalho para ele.
Matthieu M.

1
@ Gangnus: A idéia aqui é que os getters / setters estejam isolados na camada de limite (E / S), onde é normal ficar sujo, mas o restante do aplicativo (código puro) não está contaminado e permanece elegante.
Matthieu M.

2
@kubanczyk: a definição oficial é Business Object Model, tanto quanto eu sei. Corresponde aqui ao que chamei de "modelo interno de dados", o modelo no qual você executa a lógica no núcleo "puro" do seu aplicativo.
Matthieu M.

1
Essa é a abordagem pragmática. Vivemos em um mundo híbrido. Se as bibliotecas externas pudessem saber quais dados passar para os construtores de BOM, teríamos apenas BOMs.
Adrian Iftode

12

Considere o seguinte..

Você tem uma Userclasse com uma propriedade int age:

class User {
    int age;
}

Você quer ampliar isso para Userque a data de nascimento seja oposta a apenas uma idade. Usando um getter:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Podemos trocar o int agecampo por um mais complexo LocalDate dateOfBirth:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

Sem violações de contrato, sem quebra de código. Nada mais do que escalar a representação interna em preparação para comportamentos mais complexos.

O próprio campo é encapsulado.


Agora, para esclarecer as preocupações ..

A @Dataanotação de Lombok é semelhante às classes de dados de Kotlin .

Nem todas as classes representam objetos comportamentais. Quanto à quebra do encapsulamento, depende do uso do recurso. Você não deve expor todos os seus campos por meio de getters.

Encapsulamento é usado para ocultar os valores ou o estado de um objeto de dados estruturado dentro de uma classe

Em um sentido mais geral, encapsulamento é o ato de ocultar informações. Se você abusar @Data, é fácil supor que você provavelmente está quebrando o encapsulamento. Mas isso não quer dizer que não tenha propósito. O JavaBeans, por exemplo, é desaprovado por alguns. No entanto, é amplamente utilizado no desenvolvimento empresarial.

Você concluiria que o desenvolvimento da empresa é ruim devido ao uso de beans? Claro que não! Os requisitos diferem dos do desenvolvimento padrão. O feijão pode ser abusado? Claro! Eles são abusados ​​o tempo todo!

O Lombok também suporta @Gettere @Setterindependentemente - use o que seus requisitos exigem.


2
Isto não está relacionado com tapa a @Dataanotação em um tipo, que por design desencapsula todos os campos
Caleth

1
Não, os campos não estão ocultos . Porque qualquer coisa pode vir esetAge(xyz)
Caleth

9
@Caleth Os campos estão ocultos. Você está agindo como se os setters não pudessem ter condições pré e pós, que é um caso de uso muito comum para o uso de setters. Você está agindo como se uma propriedade field ==, que mesmo em idiomas como C #, não fosse verdadeira, pois ambos tendem a ser suportados (como em C #). O campo pode ser movido para outra classe, que pode ser trocado por uma representação mais complexa ...
Vince Emigh

1
E o que eu estou dizendo é que não é importante
Caleth

2
@ Caleth Como isso não é importante? Isso não faz nenhum sentido. Você está dizendo que o encapsulamento não se aplica porque você acha que não é importante nessa situação, mesmo que se aplique por definição e tenha casos de uso?
Vince Emigh

11

O encapsulamento diz-me para tornar todos ou quase todos os campos privados e expô-los por getters / setters.

Não é assim que o encapsulamento é definido na programação orientada a objetos. Encapsulamento significa que cada objeto deve ser como uma cápsula, cuja concha externa (a API pública) protege e regula o acesso ao seu interior (os métodos e campos privados) e a oculta da vista. Ao ocultar internos, os chamadores não dependem de internos, permitindo que os internos sejam alterados sem alterar (ou mesmo recompilar) os chamadores. Além disso, o encapsulamento permite que cada objeto imponha seus próprios invariantes, disponibilizando apenas operações seguras para os chamadores.

O encapsulamento é, portanto, um caso especial de ocultação de informações, no qual cada objeto oculta suas partes internas e aplica seus invariantes.

A geração de getters e setters para todos os campos é uma forma bastante fraca de encapsulamento, porque a estrutura dos dados internos não está oculta e os invariantes não podem ser impostos. Ele tem a vantagem de poder alterar a maneira como os dados são armazenados internamente (desde que você possa converter de e para a estrutura antiga) sem precisar alterar (ou mesmo recompilar) os chamadores.

Alguém poderia me explicar qual é o sentido de ocultar todos os campos como privados e depois expor todos eles por alguma tecnologia extra? Por que simplesmente não usamos apenas campos públicos? Sinto que percorremos um caminho longo e difícil apenas para retornar ao ponto de partida.

Parcialmente, isso ocorre devido a um acidente histórico. A primeira é que, em Java, as expressões de chamada de método e de acesso ao campo são sintaticamente diferentes no site da chamada, ou seja, a substituição de um acesso ao campo por uma chamada getter ou setter quebra a API de uma classe. Portanto, se você precisar de um acessador, deverá escrever um agora ou poder quebrar a API. Essa ausência de suporte a propriedades no nível da linguagem contrasta fortemente com outras linguagens modernas, principalmente C # e EcmaScript .

A greve 2 é que a especificação JavaBeans definiu propriedades como getters / setters, os campos não eram propriedades. Como resultado, a maioria das estruturas corporativas anteriores suportava getters / setters, mas não campos. Isso já faz muito tempo ( API de persistência Java (JPA) , validação de bean , arquitetura Java para ligação a XML (JAXB) , Jacksontodos os campos de suporte estão bem agora), mas os tutoriais e livros antigos continuam demorando, e nem todo mundo sabe que as coisas mudaram. A ausência de suporte a propriedades no nível do idioma ainda pode ser um problema em alguns casos (por exemplo, porque o carregamento lento de JPA de entidades únicas não é acionado quando os campos públicos são lidos), mas a maioria dos campos públicos funciona muito bem. Ou seja, minha empresa grava todos os DTOs para suas APIs REST com campos públicos (afinal, não é mais público transmitido pela Internet :-).

Dito isto, Lombok de @Datafaz mais do que gerar getters / setters: Gera também toString(), hashCode()e equals(Object), o que pode ser bastante valiosa.

E o encapsulamento tem realmente algum sentido agora na programação da vida real?

O encapsulamento pode ser inestimável ou totalmente inútil, depende do objeto que está sendo encapsulado. Geralmente, quanto mais complexa a lógica dentro da classe, maior o benefício do encapsulamento.

Os getters e setters gerados automaticamente para cada campo geralmente são usados ​​em excesso, mas podem ser úteis para trabalhar com estruturas herdadas ou para usar o recurso ocasional da estrutura não suportado para campos.

O encapsulamento pode ser alcançado com getters e métodos de comando. Os setters geralmente não são apropriados, porque é esperado que eles alterem apenas um único campo, enquanto a manutenção de invariantes pode exigir que vários campos sejam alterados ao mesmo tempo.

Sumário

getters / setters oferecem um encapsulamento bastante ruim.

A prevalência de getters / setters em Java decorre da falta de suporte no nível da linguagem para propriedades e opções de design questionáveis ​​em seu modelo de componente histórico que foram consagrados em muitos materiais de ensino e nos programadores ensinados por eles.

Outras linguagens orientadas a objetos, como o EcmaScript, suportam propriedades no nível da linguagem, para que os getters possam ser introduzidos sem interromper a API. Nessas linguagens, os getters podem ser introduzidos quando você realmente precisa deles, e não antes do tempo, apenas no caso de precisar de um dia, o que contribui para uma experiência de programação muito mais agradável.


1
reforça seus invariantes? É inglês? Você poderia explicar isso para um não inglês, por favor? Não seja muito complicado - algumas pessoas aqui não sabem inglês o suficiente.
Gangnus

Gosto da sua base, gosto dos seus pensamentos (+1), mas não do resultado prático. Prefiro usar o campo privado e alguma biblioteca especial para carregar dados para eles por reflexão. Só não entendo por que você está definindo sua resposta como argumento contra mim?
Gangnus

@ Gangnus Uma invariante é uma condição lógica que não muda (o significado não varia e o significado variável varia / muda). Muitas funções têm regras que devem ser verdadeiras antes e depois de executadas (chamadas pré-condições e pós-condições); caso contrário, há um erro no código. Por exemplo, uma função pode exigir que seu argumento não seja nulo (uma pré-condição) e pode gerar uma exceção se seu argumento for nulo, porque esse caso seria um erro no código (ou seja, na linguagem da ciência da computação, o código quebrou um invariante).
Pharap

@ Gangus: Não tenho muita certeza de onde entra a reflexão nesta discussão; Lombok é um processador de anotação, ou seja, um plugin para o compilador Java que emite código adicional durante a compilação. Mas claro, você pode usar o lombok. Estou apenas dizendo que, em muitos casos, os campos públicos funcionam tão bem e são mais fáceis de configurar (nem todos os compiladores detectam os processadores de anotações automaticamente ...).
meriton - em greve

1
@ Gangnus: Os invariantes são iguais em matemática e em CS, mas em CS há uma perspectiva adicional: Da perspectiva de um objeto, os invariantes devem ser impostos e estabelecidos. Da perspectiva de um chamador desse objeto, os invariantes são sempre verdadeiros.
meriton - em greve

7

Eu realmente me fiz essa pergunta.

Não é totalmente verdade embora. A prevalência de IMO de getters / setters é causada pela especificação do Java Bean, que exige isso; portanto, é praticamente um recurso não da programação orientada a objetos, mas da programação orientada a Bean, se você preferir. A diferença entre os dois é a da camada de abstração em que eles existem; O feijão é mais uma interface do sistema, ou seja, em uma camada superior. Eles abstraem das bases OO, ou pretendem, pelo menos - como sempre, as coisas estão sendo levadas com muita frequência.

Eu diria que é um pouco lamentável que essa coisa do Bean, onipresente na programação Java, não seja acompanhada pela adição de um recurso correspondente da linguagem Java - estou pensando em algo como o conceito de Propriedades em C #. Para quem não está ciente disso, é uma construção de linguagem que se parece com isso:

class MyClass {
    string MyProperty { get; set; }
}

De qualquer forma, o âmago da questão da implementação real ainda se beneficia muito do encapsulamento.


O Python possui um recurso semelhante, no qual as propriedades podem ter getters / setters transparentes. Tenho certeza de que existem ainda mais idiomas com recursos semelhantes.
quer

1
"A prevalência de IMO de getters / setters é causada pela especificação do Java Bean, que exige isso" Sim, você está certo. E quem disse que isso era necessário? O motivo, por favor?
Gangnus

2
A maneira C # é absolutamente a mesma que a Lombok. Não vejo nenhuma diferença real. Mais conveniência prática, mas obviamente ruim na concepção.
Gangnus

1
@Gangnis A especificação exige. Por quê? Confira minha resposta para um exemplo concreto de por que getters não são iguais a expor campos - tente obter o mesmo sem um getter.
precisa saber é o seguinte

@VinceEmigh gangnUs, por favor :-). (andar de gangue, nus - noz). E o que dizer de usar get / set somente onde precisamos especificamente e carregar em massa em campos privados por reflexão em outros casos?
Gangnus

6

Alguém poderia me explicar, qual é o sentido de ocultar todos os campos como privados e depois expor todos eles por alguma tecnologia extra? Por que simplesmente não usamos apenas campos públicos? Sinto que percorremos um caminho longo e difícil apenas para retornar ao ponto inicial.

A resposta simples aqui é: você está absolutamente certo. Os getters e setters eliminam a maioria (mas não todos) do valor do encapsulamento. Isso não quer dizer que sempre que você tiver um método get e / ou set que esteja quebrando o encapsulamento, mas se estiver adicionando cegamente acessadores a todos os membros particulares da sua classe, estará fazendo errado.

Sim, existem outras tecnologias que funcionam através de getters e setters. E não podemos usá-los através de campos públicos simples. Mas essas tecnologias apareceram apenas porque tínhamos essas inúmeras propriedades - campos privados por trás de getters / setters públicos. Se não tivéssemos as propriedades, essas tecnologias se desenvolveriam de outra maneira e suportariam campos públicos. E tudo seria simples e não precisaríamos mais do Lombok agora. Qual é o sentido de todo esse ciclo? E o encapsulamento tem realmente algum sentido agora na programação da vida real?

Getters são setters são onipresentes na programação Java porque o conceito JavaBean foi empurrado como uma maneira de vincular dinamicamente a funcionalidade ao código pré-criado. Por exemplo, você pode ter um formulário em um applet (alguém se lembra deles?) Que inspecionaria seu objeto, encontraria todas as propriedades e exibisse os campos como. Em seguida, a interface do usuário pode modificar essas propriedades com base na entrada do usuário. Você, como desenvolvedor, apenas se preocupa em escrever a classe e coloca qualquer validação ou lógica de negócios lá etc.

insira a descrição da imagem aqui

Usando Beans de Exemplo

Essa não é uma idéia terrível, por si só, mas nunca fui um grande fã da abordagem em Java. Está apenas indo contra a corrente. Use Python, Groovy etc. Algo que suporte esse tipo de abordagem de maneira mais natural.

A coisa do JavaBean meio que ficou fora de controle porque criou o JOBOL, ou seja, desenvolvedores escritos em Java que não entendem OO. Basicamente, os objetos se tornaram nada mais do que pacotes de dados e toda a lógica foi escrita fora em métodos longos. Por ser visto como normal, pessoas como você e eu, que questionamos isso, eram consideradas malucas. Ultimamente eu tenho visto uma mudança e isso não é tanto uma posição de fora

A coisa da ligação XML é muito difícil. Provavelmente, este não é um bom campo de batalha para se posicionar contra o JavaBeans. Se você precisar criar esses JavaBeans, tente mantê-los fora do código real. Trate-os como parte da camada de serialização.


2
Os pensamentos mais concretos e sábios. +1. Mas por que não escrever um grupo de classes, das quais cada uma carregará / salvará massivamente campos particulares de / para alguma estrutura externa? JSON, XML, outro objeto. Poderia funcionar com POJOs ou, talvez, apenas com campos, marcados por uma anotação para isso. Agora estou pensando nessa alternativa.
Gangnus

@ Gangnus Em suposição, porque isso significa muita escrita extra de código. Se a reflexão for usada, apenas um pedaço de código de serialização deverá ser gravado e ele poderá serializar qualquer classe que siga o padrão específico. O C # suporta isso por meio do [Serializable]atributo e de seus parentes. Por que escrever 101 métodos / classes específicos de serialização quando você pode apenas escrever um, aproveitando a reflexão?
Pharap

@ Sharap Sinto muito, mas seu comentário é muito curto para mim. "alavancando a reflexão"? E qual é a sensação de esconder coisas tão diferentes em uma classe? Você poderia explicar o que quer dizer?
Gangnus

@ Gangnus Quero dizer reflexão , a capacidade do código de inspecionar sua própria estrutura. Em Java (e outras linguagens), você pode obter uma lista de todas as funções que uma classe expõe e usá-las como uma maneira de extrair os dados da classe necessária para serializá-los para, por exemplo, XML / JSON. Usar a reflexão que seria um trabalho único, em vez de criar constantemente novos métodos para salvar estruturas por classe. Aqui está um exemplo simples de Java .
Pharap

@ Sharap Isso é exatamente o que eu estava falando. Mas por que você chama isso de "alavancagem"? E a alternativa que mencionei no primeiro comentário aqui permanece - os campos (des) compactados devem ter anotações especiais ou não?
Gangnus

5

Quanto podemos fazer sem getters? É possível removê-los completamente? Que problemas isso cria? Poderíamos até banir a returnpalavra - chave?

Acontece que você pode fazer muito se estiver disposto a fazer o trabalho. Como, então, as informações saem desse objeto totalmente encapsulado? Através de um colaborador.

Em vez de permitir que o código faça perguntas, você deve dizer o que deve fazer. Se essas coisas também não retornam coisas, você não precisa fazer nada sobre o que elas retornam. Portanto, quando estiver pensando returnem buscar um, tente procurar algum colaborador da porta de saída que fará o que resta a ser feito.

Fazer as coisas dessa maneira tem benefícios e consequências. Você tem que pensar mais do que apenas o que você teria retornado. Você precisa pensar em como enviar isso como uma mensagem para um objeto que não pediu. Pode ser que você desmaie o mesmo objeto que retornaria, ou simplesmente chamar um método é suficiente. Fazer esse pensamento tem um custo.

O benefício é que agora você está falando de frente para a interface. Isso significa que você obtém todos os benefícios da abstração.

Também fornece despacho polimórfico, porque enquanto você sabe o que está dizendo, não precisa saber exatamente para o que está dizendo.

Você pode pensar que isso significa que você precisa percorrer apenas um caminho através de uma pilha de camadas, mas acontece que você pode usar o polimorfismo para retroceder sem criar dependências cíclicas loucas.

Pode ser assim:

Digite a descrição da imagem aqui

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Se você pode codificar assim, usar getters é uma opção. Não é um mal necessário.


Sim, esses getters / setters têm um grande senso. Mas você entende que é sobre outra coisa? :-) +1 de qualquer maneira.
Gangnus 6/10/19

1
@Gangnus Obrigado. Há várias coisas sobre as quais você poderia estar falando. Se você gostaria de pelo menos nomeá-los, eu poderia entrar neles.
Candied_orange # 6/17

5

Esta é uma questão controversa (como você pode ver), porque um monte de dogmas e mal-entendidos se confunde com as preocupações razoáveis ​​em relação à questão dos caçadores e levantadores. Mas, em resumo, não há nada de errado com @Dataisso e não quebra o encapsulamento.

Por que usar getters e setters em vez de campos públicos?

Porque getters / setters fornecem encapsulamento. Se você expõe um valor como um campo público e depois muda para o cálculo em tempo real, precisará modificar todos os clientes que acessam o campo. Claramente isso é ruim. É um detalhe de implementação se alguma propriedade de um objeto é armazenada em um campo, é gerada em tempo real ou é buscada em outro lugar, portanto, a diferença não deve ser exposta aos clientes. Getter / setter setter resolvem isso, pois ocultam a implementação.

Mas se o getter / setter reflete apenas um campo privado subjacente, não é tão ruim assim?

Não! O ponto é que o encapsulamento permite alterar a implementação sem afetar os clientes. Um campo ainda pode ser uma maneira perfeitamente adequada de armazenar valor, desde que os clientes não precisem saber ou se importar.

Mas os geradores / setters de geração automática de campos não estão quebrando o encapsulamento?

Não, o encapsulamento ainda está lá! @Dataas anotações são apenas uma maneira conveniente de escrever pares getter / setter que usam um campo subjacente. Para o ponto de vista de um cliente, isso é exatamente como um par getter / setter comum. Se você decidir reescrever a implementação, ainda poderá fazê-lo sem afetar o cliente. Assim, você obtém o melhor dos dois mundos: encapsulamento e sintaxe concisa.

Mas alguns dizem que os getter / setters são sempre ruins!

Há uma controvérsia separada, na qual alguns acreditam que o padrão getter / setter é sempre ruim, independentemente da implementação subjacente. A idéia é que você não defina ou obtenha valores de objetos, mas qualquer interação entre objetos deve ser modelada como mensagens em que um objeto pede que outro objeto faça alguma coisa. Isso é principalmente um dogma dos primeiros dias do pensamento orientado a objetos. O pensamento agora é que, para certos padrões (por exemplo, objetos de valor, objeto de transferência de dados), os getters / setters podem ser perfeitamente apropriados.


O encapsulamento deve nos permitir polimorfismo e segurança. Obtém / define permitir os primeiros, mas são fortemente contra o segundo.
Gangnus

Se você diz que eu uso um dogma em algum lugar, por favor, diga, o que do meu texto é dogma. E não esqueça que alguns pensamentos que estou usando não gostam ou não concordam. Estou usando-os para demonstrar uma contradição.
Gangnus

1
"GangNus" :-). Há uma semana, trabalhei em um projeto literalmente cheio de getters e setters de chamadas em várias camadas. É absolutamente inseguro e, pior ainda, suporta pessoas em codificação insegura. Fico feliz por ter trocado de emprego com rapidez suficiente, pois as pessoas se acostumam a isso. Então, eu não acho, eu vejo .
Gangnus

2
@jrh: Eu não sei se existe uma explicação formal como tal, mas o C # possui suporte interno para propriedades (getters / setters com uma sintaxe melhor) e para tipos anônimos que são tipos com apenas propriedades e sem comportamento. Portanto, os designers de C # divergiram deliberadamente da metáfora da mensagem e do princípio "diga, não pergunte". O desenvolvimento do C # desde a versão 2.0 parece ter sido mais inspirado por linguagens funcionais do que pela pureza tradicional do OO.
JacquesB

1
@jrh: Eu estou supondo agora, mas acho que o C # é bastante inspirado por linguagens funcionais em que dados e operações são mais separados. Em arquiteturas distribuídas, a perspectiva também mudou de RPC, SOAP (Orientado a Mensagens) para REST (Orientado a Dados). E os bancos de dados que expõem e operam com dados (não com objetos de "caixa preta") prevaleceram. Em suma, eu acho que o foco mudaram de mensagens entre caixas pretas para os modelos de dados expostos
JacquesB

3

O encapsulamento tem um propósito, mas também pode ser mal utilizado ou abusado.

Considere algo como a API do Android, que possui classes com dezenas (se não centenas) de campos. Ao expor esses campos, o consumidor da API dificulta a navegação e o uso, além de fornecer ao usuário a falsa noção de que ele pode fazer o que quiser com aqueles campos que podem entrar em conflito com a forma como devem ser usados. Portanto, o encapsulamento é ótimo nesse sentido para manutenção, usabilidade, legibilidade e evitar bugs malucos.

Por outro lado, POD ou tipos de dados antigos simples, como uma estrutura do C / C ++, na qual todos os campos são públicos, também podem ser úteis. Ter getters / setters inúteis como os gerados pela anotação @data no Lombok é apenas uma maneira de manter o "Padrão de encapsulamento". Uma das poucas razões pelas quais fazemos getters / inúteis "inúteis" em Java é que os métodos fornecem um contrato .

Em Java, você não pode ter campos em uma interface; portanto, você usa getters e setters para especificar uma propriedade comum que todos os implementadores dessa interface possuem. Em idiomas mais recentes, como Kotlin ou C #, vemos o conceito de propriedades como campos para os quais você pode declarar um setter e um getter. No final, getters / setters inúteis são mais um legado que o Java tem que conviver, a menos que a Oracle adicione propriedades a ele. O Kotlin, por exemplo, que é outra linguagem da JVM desenvolvida pela JetBrains, possui classes de dados que basicamente fazem o que a anotação @data faz no Lombok.

Também aqui estão alguns exemplos:

class DataClass 
{
    private int data;

    public int getData() { return data; }
    public void setData(int data) { this.data = data; } 
}

Este é um caso ruim de encapsulamento. O getter e o setter são efetivamente inúteis. O encapsulamento é usado principalmente porque esse é o padrão em linguagens como Java. Na verdade, não ajuda, além de manter a consistência na base de código.

class DataClass implements IDataInterface
{
    private int data;

    @Override public int getData() { return data; }
    @Override public void setData(int data) { this.data = data; }
}

Este é um bom exemplo de encapsulamento. Encapsulamento é usado para impor um contrato, neste caso IDataInterface. O objetivo do encapsulamento neste exemplo é fazer com que o consumidor dessa classe use os métodos fornecidos pela interface. Embora o getter e o setter não façam nada sofisticado, agora definimos uma característica comum entre o DataClass e outros implementadores do IDataInterface. Assim, eu posso ter um método como este:

void doSomethingWithData(IDataInterface data) { data.setData(...); }

Agora, ao falar sobre encapsulamento, acho importante abordar também o problema de sintaxe. Muitas vezes vejo pessoas reclamando da sintaxe necessária para impor o encapsulamento em vez do próprio encapsulamento. Um exemplo que vem à mente é de Casey Muratori (você pode ver o discurso dele aqui ).

Suponha que você tenha uma classe de jogador que usa encapsulamento e deseja mover sua posição em 1 unidade. O código ficaria assim:

player.setPosX(player.getPosX() + 1);

Sem encapsulamento, ficaria assim:

player.posX++;

Aqui ele argumenta que os encapsulamentos levam a muito mais digitação sem benefícios adicionais e isso pode, em muitos casos, ser verdade, mas observe alguma coisa. O argumento é contra a sintaxe, não o próprio encapsulamento. Mesmo em linguagens como C que não possuem o conceito de encapsulamento, muitas vezes você vê variáveis ​​em estruturas pré-expressas ou sufixadas com '_' ou 'my' ou qualquer outra coisa que signifique que elas não devem ser usadas pelo consumidor da API, como se fossem privado.

O fato é que o encapsulamento pode ajudar a tornar o código muito mais sustentável e fácil de usar. Considere esta classe:

class VerticalList implements ...
{
    private int posX;
    private int posY;
    ... //other members

    public void setPosition(int posX, int posY)
    {
        //change position and move all the objects in the list as well
    }
}

Se as variáveis ​​fossem públicas neste exemplo, um consumidor dessa API ficaria confuso sobre quando usar posX e posY e quando usar setPosition (). Ao ocultar esses detalhes, você ajuda o consumidor a usar melhor sua API de maneira intuitiva.

A sintaxe é uma limitação em muitos idiomas. No entanto, os idiomas mais novos oferecem propriedades que nos dão a boa sintaxe dos membros da publicação e os benefícios do encapsulamento. Você encontrará propriedades em C #, Kotlin, mesmo em C ++, se você usar o MSVC. Aqui está um exemplo no Kotlin.

classe VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {campo = y; ...}}

Aqui conseguimos o mesmo que no exemplo Java, mas podemos usar posX e posY como se fossem variáveis ​​públicas. Porém, quando tento alterar o valor deles, o corpo do setter set () será executado.

No Kotlin, por exemplo, isso seria o equivalente a um Java Bean com getters, setters, hashcode, equals e toString implementados:

data class DataClass(var data: Int)

Observe como essa sintaxe nos permite fazer um Java Bean em uma linha. Você percebeu corretamente o problema de uma linguagem como Java na implementação do encapsulamento, mas isso é culpa do Java, não do próprio encapsulamento.

Você disse que usa o @Data do Lombok para gerar getters e setters. Observe o nome, @Data. Ele deve ser usado principalmente em classes de dados que armazenam apenas dados e devem ser serializadas e desserializadas. Pense em algo como um arquivo salvo de um jogo. Mas em outros cenários, como em um elemento de interface do usuário, você definitivamente deseja setters, pois apenas alterar o valor de uma variável pode não ser suficiente para obter o comportamento esperado.


Você poderia colocar aqui um exemplo sobre o uso indevido do encapsulamento? Isso pode ser interessante. Seu exemplo é bastante contra a falta de encapsulamento.
Gangnus

1
@ Gangnus eu adicionei alguns exemplos. Geralmente, o encapsulamento inútil é mal utilizado e, por minha experiência, os desenvolvedores de API se esforçam demais para forçar você a usar uma API de uma certa maneira. Não coloquei um exemplo disso porque não tinha um exemplo fácil para apresentar. Se eu encontrar um, eu definitivamente o adicionarei. Eu acho que a maioria dos comentários contra o encapsulamento é, na verdade, contra a sintaxe do encapsulamento, e não o próprio encapsulamento.
precisa saber é o seguinte

Obrigado pela edição. No entanto, encontrei um erro de digitação menor: "publice" em vez de "public".
JRH

2

O encapsulamento oferece flexibilidade . Ao separar estrutura e interface, permite alterar a estrutura sem alterar a interface.

Por exemplo, se você achar que precisa calcular uma propriedade com base em outros campos, em vez de inicializar o campo subjacente na construção, basta alterar o getter. Se você tivesse exposto o campo diretamente, teria que alterar a interface e fazer alterações em todos os sites de uso.


Embora seja uma boa idéia, lembre-se de que fazer com que a versão 2 de uma API gere exceções que a versão 1 não quebraria terrivelmente os usuários de sua biblioteca ou classe (e possivelmente silenciosamente!); Estou um pouco cético de que qualquer mudança importante possa ser feita "nos bastidores" na maioria dessas classes de dados.
JRH

Desculpe, não é uma explicação. O fato de que o uso de campos é proibido nas interfaces vem da ideia de encapsulamento. E agora estamos falando sobre isso. Você está se baseando nos fatos que estão sendo discutidos. (note, eu não estou falando pro ou contra seus pensamentos, apenas cerca de eles não podem ser usados como argumentos aqui)
Gangnus

@Gangnus A palavra "interface" tem significado fora da palavra-chave Java: dictionary.com/browse/interface
glennsl 5/17/17

@ jrh Essa é uma questão totalmente diferente. Você pode usá-lo para argumentar contra o encapsulamento em certos contextos , mas de forma alguma invalida os argumentos a favor.
glennsl

1
O antigo encapsulamento, sem obter / definir massa, nos deu não apenas o polimorfismo, mas também a segurança. E conjuntos de massa / ensina-nos a praticar más práticas.
Gangnus

2

Tentarei ilustrar o espaço problemático do encapsulamento e do design da classe e responderei sua pergunta no final.

Como mencionado em outras respostas, o objetivo do encapsulamento é ocultar detalhes internos de um objeto atrás de uma API pública, que serve como contrato. É seguro alterar o objeto interno porque sabe que é chamado apenas através da API pública.

Se faz sentido ter campos públicos, getters / setters ou métodos de transação de nível superior ou passagem de mensagens, depende da natureza do domínio que está sendo modelado. No livro Akka Concurrency (que eu posso recomendar, mesmo que esteja um pouco desatualizado), você encontra um exemplo que ilustra isso, que abreviarei aqui.

Considere uma classe de usuário:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

Isso funciona bem em um contexto de thread único. O domínio que está sendo modelado é o nome de uma pessoa e a mecânica de como esse nome é armazenado pode ser perfeitamente encapsulada pelos setters.

No entanto, imagine que isso deve ser fornecido em um contexto multithread. Suponha que um segmento esteja lendo periodicamente o nome:

System.out.println(user.getFirstName() + " " + user.getLastName());

E dois outros tópicos estão lutando contra um cabo de guerra, definindo-o para Hillary Clinton e Donald Trump, por sua vez. Cada um deles precisa chamar dois métodos. Principalmente isso funciona bem, mas de vez em quando você vê uma Hillary Trump ou um Donald Clinton passando.

Você não pode resolver esse problema adicionando uma trava dentro dos setters, porque a trava é mantida apenas durante o período de configuração do nome ou sobrenome. A única solução através do bloqueio é adicionar um bloqueio ao redor do objeto inteiro, mas isso interrompe o encapsulamento, pois o código de chamada deve gerenciar o bloqueio (e pode causar conflitos).

Como se vê, não há solução limpa através do bloqueio. A solução limpa é encapsular os internos novamente, tornando-os mais grosseiros:

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

O nome em si tornou-se imutável e você vê que seus membros podem ser públicos, porque agora é um objeto de dados puro, sem capacidade de modificá-lo depois de criado. Por sua vez, a API pública da classe User tornou-se mais grossa, com apenas um único setter restante, para que o nome só possa ser alterado como um todo. Ele encapsula mais de seu estado interno por trás da API.

Qual é o sentido de todo esse ciclo? E o encapsulamento tem realmente algum sentido agora na programação da vida real?

O que você vê neste ciclo são tentativas de aplicar soluções boas para um conjunto específico de circunstâncias de maneira muito ampla. Um nível adequado de encapsulamento requer a compreensão do domínio que está sendo modelado e a aplicação do nível correto de encapsulamento. Às vezes, isso significa que todos os campos são públicos, outras (como nos aplicativos Akka) significa que você não possui uma API pública, exceto por um único método para receber mensagens. No entanto, o próprio conceito de encapsulamento, que significa ocultar os internos por trás de uma API estável, é essencial para programar software em escala, especialmente em sistemas multithread.


Fantástico. Eu diria que você elevou a discussão um nível mais alto. Talvez dois. Realmente, pensei que entendia o encapsulamento e, às vezes, melhor do que os livros comuns, e tinha muito medo de praticamente perdê-lo devido a alguns costumes e tecnologias aceitos, e esse era o assunto real da minha pergunta (talvez, não formulado em um bom caminho), mas você me mostrou realmente novos lados do encapsulamento. Definitivamente, não sou bom em multitarefa e você me mostrou como isso pode ser prejudicial para a compreensão de outros princípios básicos.
Gangnus

Mas eu não posso marcar o seu posto como a resposta, pois não se trata de carregar dados em massa de / para objetos, o problema devido a que aparecem plataformas como o feijão e Lombok. Talvez você possa elaborar seus pensamentos nessa direção, por favor, se tiver tempo? Eu mesmo ainda tenho que repensar as consequências que seu pensamento traz para esse problema. E eu não tenho certeza se estou apto o suficiente para isso (lembre-se, mau fundo multithreading: - [)
Gangnus

Eu não usei o lombok, mas, pelo que entendi, é uma maneira de implementar um nível específico de encapsulamento (getters / setters em todos os campos) com menos digitação. Ele não altera a forma ideal da API para o domínio do seu problema, é apenas uma ferramenta para escrevê-la mais rapidamente.
Joeri Sebrechts 10/10

1

Posso pensar em um caso de uso em que isso faz sentido. Você pode ter uma classe que acessa originalmente por meio de uma simples API getter / setter. Posteriormente, você estende ou modifica para que não use mais os mesmos campos, mas ainda suporte a mesma API .

Um exemplo um tanto artificial: um ponto que começa como um par cartesiano com p.x()e p.y(). Mais tarde, você cria uma nova implementação ou subclasse que usa coordenadas polares, para que você também possa chamar p.r()e p.theta(), mas seu código de cliente que chama p.x()e p.y()permanece válido. A própria classe converte de forma transparente a partir da forma polar interna, isto é, y()faria agora return r * sin(theta);. (Neste exemplo, definir apenas x()ou y()não faz tanto sentido, mas ainda é possível.)

Nesse caso, você pode estar dizendo: “Ainda bem que me preocupei em declarar automaticamente getters e setters em vez de tornar os campos públicos, ou teria que interromper minha API lá”.


2
Não é tão bom quanto você vê. Mudar a representação física interna realmente torna o objeto diferente. E é ruim que eles pareçam da mesma maneira, pois têm qualidades diferentes. O sistema de Descartes não possui pontos especiais, o sistema polar possui um.
Gangnus

1
Se você não gosta desse exemplo específico, certamente pode pensar em outros que realmente têm várias representações equivalentes ou em uma representação mais nova que pode ser convertida para e de uma mais antiga.
Davislor

Sim, você pode usá-los de maneira muito produtiva para apresentação. Na interface do usuário, ele pode ser amplamente utilizado. Mas você não está me fazendo responder à minha própria pergunta? :-)
Gangnus

Mais eficiente assim, não é? :)
Davislor

0

Alguém poderia me explicar, qual é o sentido de ocultar todos os campos como privados e depois expor todos eles por alguma tecnologia extra?

Não há absolutamente nenhum sentido. No entanto, o fato de você fazer essa pergunta demonstra que você não entendeu o que Lombok faz e que não entende como escrever código OO com encapsulamento. Vamos voltar um pouco ...

Alguns dados para uma instância de classe sempre serão internos e nunca devem ser expostos. Alguns dados para uma instância de classe precisarão ser configurados externamente e alguns dados poderão precisar ser transferidos de volta para uma instância de classe. No entanto, podemos mudar a forma como a classe faz as coisas sob a superfície, então usamos funções para nos permitir obter e definir dados.

Alguns programas desejam salvar o estado para instâncias de classe, portanto, eles podem ter alguma interface de serialização. Adicionamos mais funções que permitem que a instância da classe armazene seu estado em armazenamento e recupere seu estado do armazenamento. Isso mantém o encapsulamento porque a instância da classe ainda está no controle de seus próprios dados. Podemos estar serializando dados privados, mas o restante do programa não tem acesso a eles (ou, mais precisamente, mantemos um muro chinês escolhendo não corromper deliberadamente esses dados privados), e a instância da classe pode (e deve) faça verificações de integridade na desserialização para garantir que seus dados voltem bem.

Às vezes, os dados precisam de verificações de alcance, de integridade ou coisas assim. Escrever essas funções por nós mesmos nos permite fazer tudo isso. Nesse caso, não queremos ou precisamos do Lombok, porque estamos fazendo tudo isso sozinhos.

Porém, freqüentemente, você descobre que um parâmetro definido externamente é armazenado em uma única variável. Nesse caso, você precisaria de quatro funções para obter / configurar / serializar / desserializar o conteúdo dessa variável. Escrever você mesmo essas quatro funções sempre que você o atrasa e é propenso a erros. A automação do processo com o Lombok acelera o seu desenvolvimento e elimina a possibilidade de erros.

Sim, seria possível tornar pública essa variável. Nesta versão específica do código, seria funcionalmente idêntico. Mas volte ao motivo pelo qual estamos usando funções: "Podemos mudar a maneira como a classe faz coisas sob a superfície ..." Se você tornar sua variável pública, estará restringindo seu código agora e para sempre a ter essa variável pública como a interface. No entanto, se você usar funções ou usar o Lombok para gerar automaticamente essas funções, poderá alterar os dados subjacentes e a implementação subjacente a qualquer momento no futuro.

Isso torna mais claro?


Você está falando sobre as perguntas do aluno do primeiro ano do ensino médio. Já estamos em outro lugar. Eu nunca diria isso e até lhe daria uma vantagem por respostas inteligentes, se você não fosse tão indelicado na forma de sua resposta.
Gangnus

0

Na verdade, não sou desenvolvedor de Java; mas o seguinte é praticamente independente de plataforma.

Quase tudo o que escrevemos usa getters e setters públicos que acessam variáveis ​​privadas. A maioria dos getters e setters são triviais. Mas quando decidimos que o setter precisa recalcular algo ou o validador faz alguma validação ou precisamos encaminhar a propriedade para uma propriedade de uma variável membro dessa classe, isso é completamente ininterrupto para todo o código e é compatível com binários. pode trocar esse módulo.

Quando decidimos que essa propriedade realmente deve ser computada em tempo real, todo o código que olha para ela não precisa ser alterado e apenas o código que grava nela precisa ser alterado e o IDE pode encontrá-la para nós. Quando decidimos que é um campo computável gravável (só tivemos que fazer isso algumas vezes), também podemos fazer isso. O bom é que algumas dessas mudanças são compatíveis com binários (a mudança para o campo computado somente leitura não está na teoria, mas pode estar na prática de qualquer maneira).

Acabamos com muitos e muitos getters triviais com setters complicados. Também acabamos com alguns getters de cache. O resultado final é que você pode assumir que os getters são razoavelmente baratos, mas os setters podem não ser. Por outro lado, somos sábios o suficiente para decidir que os setters não persistem no disco.

Mas eu tive que rastrear o cara que estava mudando cegamente todas as variáveis ​​de membro para propriedades. Ele não sabia o que era a adição atômica, então mudou a coisa que realmente precisava ser uma variável pública para uma propriedade e quebrou o código de maneira sutil.


Bem-vindo ao mundo Java. (C # também). *suspiro*. Mas, quanto ao uso de get / set para ocultar a representação interna, tenha cuidado. É apenas sobre o formato externo, está OK. Mas se é sobre matemática, ou, pior, física ou outra coisa real, diferentes representações internas têm qualidades diferentes. viz meu comentário a softwareengineering.stackexchange.com/a/358662/44104
Gangnus

@ Gangnus: Esse exemplo é muito infeliz. Tivemos algumas delas, mas o cálculo sempre foi exato.
1937 Joshua

-1

Getters e setters são uma medida "just in case" adicionada para evitar a refatoração no futuro se a estrutura interna ou os requisitos de acesso mudarem durante o processo de desenvolvimento.

Digamos, alguns meses após o lançamento, seu cliente informa que um dos campos de uma classe às vezes é definido como valores negativos, mesmo que no máximo esse valor seja 0, nesse caso. Usando campos públicos, você precisaria procurar todas as atribuições desse campo em toda a sua base de código para aplicar a função de fixação ao valor que você definirá. Lembre-se de que sempre será necessário fazer isso ao modificar esse campo, mas isso é péssimo. Em vez disso, se você já estivesse usando getters e setter, seria capaz de modificar apenas o método setField () para garantir que esse grampo seja sempre aplicado.

Agora, o problema com linguagens "antiquadas" como Java é que elas incentivam o uso de métodos para esse fim, o que apenas torna seu código infinitamente mais detalhado. É difícil escrever e difícil de ler, e é por isso que usamos o IDE que atenua esse problema de uma maneira ou de outra. A maioria dos IDE gera automaticamente getters e setters para você e também os oculta, a menos que seja dito o contrário. O Lombok vai um passo além e simplesmente os gera processualmente durante o tempo de compilação para manter seu código ainda mais elegante. No entanto, outras linguagens mais modernas simplesmente resolveram esse problema de uma maneira ou de outra. Linguagens Scala ou .NET, por exemplo,

Por exemplo, no VB .NET ou C #, você poderia simplesmente fazer com que todos os campos tivessem campos simples, sem efeitos colaterais e setters, apenas campos públicos e, posteriormente, torná-los privados, alterar seu nome e expor uma propriedade com o nome anterior de o campo, onde você pode ajustar o comportamento de acesso do campo, se necessário. Com o Lombok, se você precisar ajustar o comportamento de um getter ou setter, basta remover essas tags quando necessário e codificar as suas com os novos requisitos, sabendo com certeza que você não precisará refatorar nada em outros arquivos.

Basicamente, a maneira como seu método acessa um campo deve ser transparente e uniforme. As linguagens modernas permitem definir "métodos" com a mesma sintaxe de acesso / chamada de um campo, para que essas modificações possam ser feitas sob demanda sem pensar muito nisso durante o desenvolvimento inicial, mas o Java força você a fazer esse trabalho com antecedência, porque não possui esse recurso. Tudo o que o Lombok faz é economizar algum tempo, porque o idioma que você está usando não deseja economizar tempo para um método "just in case".


Nessas linguagens "modernas", a API parece um acesso de campo, foo.barmas pode ser manipulada por uma chamada de método. Você alega que isso é superior à maneira "antiquada" de ter uma API parecida com chamadas de método foo.getBar(). Parece que concordamos que os campos públicos são problemáticos, mas afirmo que a alternativa "antiquada" é superior à alternativa "moderna", pois toda a API é simétrica (todas as chamadas de método). Na abordagem "moderna", temos que decidir quais coisas devem ser propriedades e quais devem ser métodos, o que complica tudo (especialmente se usarmos a reflexão!).
Warbo 5/10

1
1. Esconder a física nem sempre é tão bom. veja meu comentário em softwareengineering.stackexchange.com/a/358662/44104 . 2. Não estou falando sobre o quão difícil ou simples é a criação do getter / setter. O C # tem absolutamente o mesmo problema que estamos falando. A falta de incapsulação devido à má solução do carregamento de informações em massa. 3. Sim, a solução pode ser baseada em sintaxe, mas, por favor, de maneiras diferentes das mencionadas. Isso é ruim, temos aqui uma página grande, cheia de explicações. 4. A solução não precisa se basear na sintaxe. Isso poderia ser feito nos limites do Java.
Gangnus

-1

Alguém poderia me explicar qual é o sentido de ocultar todos os campos como privados e depois expor todos eles por alguma tecnologia extra? Por que simplesmente não usamos apenas campos públicos? Sinto que percorremos um caminho longo e difícil apenas para retornar ao ponto de partida.

Sim, é contraditório. Eu primeiro encontrei propriedades no Visual Basic. Até então, em outros idiomas, minha experiência, não havia invólucros de propriedades em torno dos campos. Apenas campos públicos, privados e protegidos.

Propriedades era um tipo de encapsulamento. Entendi que as propriedades do Visual Basic eram uma maneira de controlar e manipular a saída de um ou mais campos, ocultando o campo explícito e até mesmo seu tipo de dados, por exemplo, emitindo uma data como uma string em um formato específico. Mas, mesmo assim, não se está "ocultando o estado e expondo a funcionalidade" da perspectiva maior do objeto.

Mas as propriedades se justificam por causa de getters e setters de propriedades separados. Expor um campo sem propriedade era tudo ou nada - se você pudesse lê-lo, poderia alterá-lo. Portanto, agora é possível justificar um design de classe fraco com setters protegidos.

Então, por que nós / eles não usamos apenas métodos reais? Porque era Visual Basic (e VBScript ) (oooh! Aaah!), Codificando para as massas (!), E era toda a raiva. E assim uma idiotocracia acabou dominando.


Sim, as propriedades da interface do usuário têm um senso especial, são representações públicas e bonitas dos campos. Bom ponto.
Gangnus 6/10/19

"Então, por que nós / eles não usamos apenas métodos reais?" Tecnicamente, eles fizeram na versão .Net. Propriedades são açúcar sintático para funções get e set.
Pharap

"idiotocracia" ? Você não quer dizer " idiossincrasia "?
Peter Mortensen

Eu quis dizer idiocracia
radarbob
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.