L'incapsulamento è ancora uno degli elefanti su cui sta OOP?


97

L'incapsulamento mi dice di rendere privati ​​tutti o quasi tutti i campi e di esporli tramite getter / setter. Ma ora appaiono biblioteche come Lombok che ci consentono di esporre tutti i campi privati ​​con una breve annotazione @Data. Creerà getter, setter e costruttori di impostazioni per tutti i campi privati.

Qualcuno potrebbe spiegarmi qual è il senso di nascondere tutti i campi come privati ​​e dopo di esporli tutti con qualche tecnologia in più? Perché semplicemente non usiamo solo i campi pubblici allora? Sento che abbiamo percorso una strada lunga e difficile solo per tornare al punto di partenza.

Sì, ci sono altre tecnologie che funzionano attraverso getter e setter. E non possiamo usarli attraverso semplici campi pubblici. Ma queste tecnologie sono apparse solo perché possedevamo quelle numerose proprietà: campi privati ​​dietro getter / setter pubblici. Se non avessimo le proprietà, queste tecnologie si svilupperebbero in un altro modo e sosterrebbero i campi pubblici. E tutto sarebbe semplice e non avremmo bisogno di Lombok ora.

Qual è il senso di tutto questo ciclo? E l'incapsulamento ha davvero senso nella programmazione della vita reale?


19
"It will create getters, setters and setting constructors for all private fields."- Il modo in cui lei descrive questo strumento, suona come si mantiene l'incapsulamento. (Almeno in senso libero, automatizzato, un po 'anemico.) Qual è esattamente il problema?
David,

78
L'incapsulamento nasconde gli interni dell'implementazione dell'oggetto dietro il suo contratto pubblico (di solito, un'interfaccia). Getter e setter fanno esattamente la cosa opposta: espongono gli interni dell'oggetto, quindi il problema è nei getter / setter, non nell'incapsulamento.

22
Le classi di dati @VinceEmigh non hanno incapsulamento . Le loro istanze sono valori esattamente nel senso che sono i primitivi
Caleth,

10
@VinceEmigh utilizzando JavaBeans non è OO , è procedurale . Che la letteratura li chiama "Oggetti" è un errore della storia.
Caleth,

28
Ho riflettuto molto su questo nel corso degli anni; Penso che questo sia un caso in cui l'intenzione di OOP differiva dall'attuazione. Dopo aver studiato SmallTalk è chiaro ciò che OOP di incapsulamento è stato destinato ad essere (Per esempio, ogni classe è come un computer di indipendente, con metodi come il protocollo in comune), anche se per ragioni finora a me sconosciute, si è definitivamente preso piede in popolarità per fare questi oggetti getter / setter che non offrono incapsulamenti concettuali (non nascondono nulla, non gestiscono nulla e non hanno alcuna responsabilità oltre ai dati) e tuttavia usano ancora le proprietà.
jrh

Risposte:


82

Se esponi tutti i tuoi attributi con getter / setter stai acquisendo solo una struttura di dati che è ancora usata in C o in qualsiasi altro linguaggio procedurale. Si Non incapsulamento e Lombok è solo fare per lavorare con codice procedurale meno doloroso. Getter / setter cattivi come semplici campi pubblici. Non c'è differenza davvero.

E la struttura dei dati non è un oggetto. Se inizierai a creare un oggetto scrivendo un'interfaccia non aggiungerai mai getter / setter all'interfaccia. Esporre i tuoi attributi porta a un codice procedurale di spaghetti in cui la manipolazione dei dati è al di fuori di un oggetto e si diffonde su tutto il codebase. Ora hai a che fare con i dati e con le manipolazioni dei dati invece di parlare con gli oggetti. Con i getter / setter, avrai una programmazione procedurale basata sui dati in cui la manipolazione verrà eseguita in modo assolutamente indispensabile. Ottieni dati - fai qualcosa - imposta i dati.

In OOP l'incapsulamento è un elefante se fatto nel modo giusto. È necessario incapsulare i dettagli di stato e implementazione in modo che l'oggetto abbia il controllo completo su di esso. La logica sarà focalizzata all'interno dell'oggetto e non sarà diffusa su tutta la base di codice. E sì - l'incapsulamento è ancora essenziale nella programmazione poiché il codice sarà più mantenibile.

EDITS

Dopo aver visto le discussioni in corso, voglio aggiungere diverse cose:

  • Non importa quanti dei tuoi attributi esponi attraverso getter / setter e con quanta attenzione lo stai facendo. Essere più selettivi non renderà il tuo codice OOP incapsulato. Ogni attributo che esponi porterà a una procedura che lavora con quei dati nudi in modo procedurale imperativo. Spargerai il tuo codice meno lentamente con l'essere più selettivo. Ciò non cambia il nocciolo.
  • Sì, nei limiti all'interno del sistema si ottengono dati nudi da altri sistemi o database. Ma questi dati sono solo un altro punto di incapsulamento.
  • Gli oggetti dovrebbero essere affidabili . L'intera idea degli oggetti è essere responsabile in modo da non dover dare ordini che siano diritti e imperativi. Invece stai chiedendo a un oggetto di fare ciò che fa bene attraverso il contratto. È tranquillamente delegare agendo parte al all'oggetto. Gli oggetti incapsulano lo stato e i dettagli di implementazione.

Quindi, se torniamo alla domanda sul perché dovremmo farlo. Considera questo semplice esempio:

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());
    }
}

Qui abbiamo Document che espone i dettagli interni per getter e abbiamo un codice procedurale esterno in printDocumentfunzione che funziona con quello esterno all'oggetto. Perché è così male? Perché ora hai solo il codice di stile C. Sì, è strutturato ma qual è la differenza? È possibile strutturare le funzioni C in diversi file e con nomi. E quei cosiddetti livelli stanno facendo esattamente questo. La classe di servizio è solo una serie di procedure che funzionano con i dati. Tale codice è meno gestibile e presenta molti inconvenienti.

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);
    }
}

Confronta con questo. Ora abbiamo un contratto e i dettagli di attuazione di questo contratto sono nascosti all'interno dell'oggetto. Ora puoi veramente testare quella classe e quella classe sta incapsulando alcuni dati. Come funziona con quei dati è un oggetto che riguarda. Per parlare con l'oggetto ora devi chiedergli di stampare se stesso. Questo è incapsulamento e questo è un oggetto. Otterrai la piena potenza dell'iniezione di dipendenze, derisione, test, singole responsabilità e molti benefici con OOP.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

1
"Getter / setter cattivi come semplici campi pubblici" - questo non è vero, almeno in molte lingue. In genere non è possibile ignorare l'accesso al campo semplice, ma è possibile ignorare i getter / setter. Questo dà versatilità alle classi dei bambini. Inoltre semplifica la modifica del comportamento delle classi. ad esempio, potresti iniziare con un getter / setter supportato da un campo privato e successivamente spostarti in un campo diverso o calcolare il valore da altri valori. Nessuno dei due può essere fatto con campi semplici. Alcune lingue consentono ai campi di avere getter e setter essenzialmente automatici, ma Java non è un linguaggio del genere.
Kat,

Eh, ancora non convinto. Il tuo esempio va bene, ma denota solo un diverso stile di codifica, non quello "vero giusto" - che non esiste. Tieni presente che la maggior parte delle lingue sono multiparadigm al giorno d'oggi e raramente sono OO puro o puro procedurale. Rimanendo così duro ai "concetti OO puri". Ad esempio, non è possibile utilizzare DI sul proprio esempio perché è stata accoppiata la logica di stampa a una sottoclasse. La stampa non dovrebbe far parte di un documento: un documento stampabile esponerebbe la sua parte stampabile (tramite un getter) a un PrinterService.
T. Sar - Ripristina Monica il

Un corretto approccio OO a questo, se stessimo andando ad un approccio "Pure OO", userebbe qualcosa che implementa una classe astratta PrinterService che richiede, tramite un messaggio, cosa diavolo dovrebbe essere stampato - usando una specie di GetPrintablePart. La cosa che implementa PrinterService potrebbe essere qualsiasi tipo di stampante: stampare su un PDF, sullo schermo, sul TXT ... La tua soluzione rende impossibile scambiare la logica di stampa con qualcos'altro, finendo per essere più accoppiata e meno manutenibile del tuo esempio "sbagliato".
T. Sar - Ripristina Monica il

La linea di fondo: Getter e setter non sono cattivi o rompono OOP - Le persone che non capiscono come usarli lo sono. Questo caso esemplificativo è un esempio da manuale di persone che non comprendono l'intero modo in cui DI funziona. L'esempio che hai "corretto" era già abilitato alla DI, disaccoppiato, poteva essere deriso facilmente ... Davvero - ricordi l'intera cosa "Preferisci la composizione sull'ereditarietà"? Uno dei pilastri OO? L'hai appena lanciato dalla finestra nel modo in cui hai riformattato il codice. Questo non volerebbe in nessuna seria revisione del codice.
T. Sar - Ripristina Monica il

76

Qualcuno potrebbe spiegarmi, che senso ha nascondere tutti i campi come privati ​​e, successivamente, esporli tutti con qualche tecnologia in più? Perché allora non utilizziamo semplicemente solo i campi pubblici?

La sensazione è che non dovresti farlo .

L'incapsulamento significa che esponi solo quei campi a cui in realtà hai bisogno di altre classi per accedere e che sei molto selettivo e attento a riguardo.

Do Non basta dare tutti i campi getter e setter per default!

Ciò è completamente contrario allo spirito delle specifiche JavaBeans, che ironicamente è il punto di partenza del concetto di getter e setter pubblici.

Ma se guardi le specifiche, vedrai che intendeva che la creazione di getter e setter fosse molto selettiva e che parla di proprietà "sola lettura" (no setter) e "sola scrittura" (no getter).

Un altro fattore è che getter e setter non sono necessariamente semplici accessi al campo privato . Un getter può calcolare il valore che restituisce in modo arbitrariamente complesso, magari memorizzarlo nella cache. Un setter può validare il valore o avvisare i listener.

Quindi eccoti qui: l' incapsulamento significa che esponi solo quella funzionalità che devi effettivamente esporre. Ma se non pensi a ciò di cui hai bisogno per esporre e semplicemente o volentieri esponi tutto seguendo una trasformazione sintattica, ovviamente non è proprio l'incapsulamento.


20
"[G] etters e setter non sono necessariamente semplici accessi" - Penso che sia un punto chiave. Se il codice accede a un campo in base al nome, non è possibile modificare il comportamento in un secondo momento. Se stai usando obj.get (), puoi.
Dan Ambrogio,

4
@jrh: non ho mai sentito dire che è una cattiva pratica in Java, ed è abbastanza comune.
Michael Borgwardt,

2
@MichaelBorgwardt Interessante; un po 'fuori tema ma mi sono sempre chiesto perché Microsoft abbia consigliato di non farlo per C #. Immagino che queste linee guida implicino che in C # l'unica cosa per cui i setter possono essere usati è la conversione di un valore dato ad un altro valore usato internamente, in un modo che non può fallire o avere un valore non valido (ad esempio, avere una proprietà PositionInches e una proprietà PositionCentimeters dove si converte automaticamente internamente in mm)? Non è un ottimo esempio, ma è il migliore che posso trovare in questo momento.
jrh

2
@jrh quella fonte dice di non farlo per i getter, al contrario dei setter.
Captain Man,

3
@Gangnus e vedi davvero che qualcuno sta nascondendo qualche logica dentro getter / setter? Ci siamo così abituati che getFieldName()diventano un contratto automatico per noi. Non ci aspettiamo un comportamento complesso dietro questo. Nella maggior parte dei casi si tratta semplicemente di rompere l'incapsulamento.
Izbassar Tolegen,

17

Penso che il nocciolo della questione sia spiegato dal tuo commento:

Sono totalmente d'accordo con il tuo pensiero. Ma da qualche parte dobbiamo caricare oggetti con dati. Ad esempio, da XML. E le piattaforme attuali che lo supportano lo fanno attraverso getter / setter, degradando così la qualità del nostro codice e il nostro modo di pensare. Lombok, in realtà, non è male da solo, ma la sua stessa esistenza dimostra che abbiamo qualcosa di brutto.

Il problema che hai è che stai mescolando il modello di dati di persistenza con il modello di dati attivo .

Un'applicazione avrà generalmente più modelli di dati:

  • un modello di dati per parlare con il database,
  • un modello di dati per leggere il file di configurazione,
  • un modello di dati per parlare con un'altra applicazione,
  • ...

in cima al modello di dati che effettivamente utilizza per eseguire i suoi calcoli.

In generale, i modelli di dati utilizzati per la comunicazione con l'esterno devono essere isolati e indipendenti dal modello di dati interno (modello a oggetti di business, distinta base) su cui vengono eseguiti i calcoli:

  • indipendente : in modo da poter aggiungere / rimuovere proprietà sulla DBA in base alle proprie esigenze senza dover cambiare tutti i client / server, ...
  • isolato : in modo che tutti i calcoli vengano eseguiti sulla DBA, dove vivono gli invarianti, e che il passaggio da un servizio all'altro o l'aggiornamento di un servizio non causi un'ondulazione in tutta la base di codice.

In questo scenario, è perfettamente corretto che gli oggetti utilizzati nei livelli di comunicazione abbiano tutti gli oggetti pubblici o esposti da getter / setter. Quelli sono semplici oggetti senza invarianti.

D'altra parte, la tua DBA dovrebbe avere degli invarianti, che generalmente precludono il numero di setter (i getter non influenzano gli invarianti, sebbene riducano l'incapsulamento in misura).


3
Non creerei getter e setter per oggetti di comunicazione. Renderei pubblici i campi. Perché creare più lavoro di quanto sia utile?
user253751,

3
@immibis: concordo in generale, tuttavia, come rilevato dall'OP, alcuni framework richiedono getter / setter, nel qual caso devi solo conformarti. Si noti che l'OP sta utilizzando una libreria che li crea automaticamente con l'applicazione di un singolo attributo, quindi per lui è poco lavoro.
Matthieu M.,

1
@Gangnus: L'idea qui è che i getter / setter siano isolati al livello limite (I / O), dove è normale sporcarsi, ma il resto dell'applicazione (codice puro) non è contaminato e rimane elegante.
Matthieu M.,

2
@kubanczyk: la definizione ufficiale è Business Model Model per quanto ne so. Corrisponde qui a quello che ho chiamato il "modello di dati interno", il modello di dati su cui esegui la logica nel nucleo "puro" della tua applicazione.
Matthieu M.,

1
Questo è l'approccio pragmatico. Viviamo in un mondo ibrido. Se le librerie esterne potessero sapere quali dati passare ai costruttori della distinta componenti, avremmo solo le distinte base.
Adrian Iftode,

12

Considera quanto segue ..

Hai una Userclasse con una proprietà int age:

class User {
    int age;
}

Vuoi ridimensionarlo in modo che Userabbia una data di nascita, al contrario di solo un'età. Utilizzando un getter:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Possiamo scambiare il int agecampo per un più complesso 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
}

Nessuna violazione del contratto, nessuna violazione del codice. Nient'altro che il ridimensionamento della rappresentazione interna in preparazione di comportamenti più complessi.

Il campo stesso è incapsulato.


Ora, per cancellare le preoccupazioni ..

L' @Dataannotazione di Lombok è simile alle classi di dati di Kotlin .

Non tutte le classi rappresentano oggetti comportamentali. Per quanto riguarda la rottura dell'incapsulamento, dipende dall'utilizzo della funzione. Non dovresti esporre tutti i tuoi campi tramite getter.

L'incapsulamento viene utilizzato per nascondere i valori o lo stato di un oggetto dati strutturato all'interno di una classe

In senso più generale, l'incapsulamento è l'atto di nascondere le informazioni. Se abusi @Data, allora è facile supporre che stai probabilmente rompendo l'incapsulamento. Ma questo non vuol dire che non ha scopo. JavaBeans, per esempio, sono disapprovati da alcuni. Eppure è ampiamente utilizzato nello sviluppo aziendale.

Concluderesti che lo sviluppo delle imprese è negativo a causa dell'uso di fagioli? Ovviamente no! I requisiti differiscono da quelli dello sviluppo standard. I fagioli possono essere abusati? Ovviamente! Sono abusati continuamente!

Lombok supporta inoltre @Gettere in modo @Setterindipendente: utilizza ciò che i tuoi requisiti richiedono.


2
Ciò non è correlato allo schiaffo @Datadell'annotazione su un tipo, che per impostazione predefinita non incapsula tutti i campi
Caleth,

1
No, i campi non sono nascosti . Perché tutto può succedere esetAge(xyz)
Caleth,

9
@Caleth I campi sono nascosti. Ti comporti come se i setter non potessero avere condizioni pre e post, che è un caso d'uso molto comune per l'uso dei setter. Ti stai comportando come se una proprietà field ==, che anche in lingue come C #, non fosse vera, dato che entrambe tendono ad essere supportate (come in C #). Il campo può essere spostato in un'altra classe, può essere scambiato per una rappresentazione più complessa ...
Vince Emigh,

1
E quello che sto dicendo è che non è importante
Caleth,

2
@Caleth Come non è importante? Non ha alcun senso. Stai dicendo che l'incapsulamento non si applica perché ritieni che non sia importante in questa situazione, anche se si applica per definizione e ha casi d'uso?
Vince Emigh,

11

L'incapsulamento mi dice di rendere privati ​​tutti o quasi tutti i campi e di esporli tramite getter / setter.

Non è così che l'incapsulamento è definito nella programmazione orientata agli oggetti. Incapsulamento significa che ogni oggetto dovrebbe essere come una capsula, il cui guscio esterno (l'api pubblica) protegge e regola l'accesso al suo interno (metodi e campi privati) e lo nasconde alla vista. Nascondendo gli interni, i chiamanti non dipenderanno dagli interni, consentendo agli interni di essere cambiati senza cambiare (o persino ricompilare) i chiamanti. Inoltre, l'incapsulamento consente a ciascun oggetto di imporre i propri invarianti, rendendo disponibili ai chiamanti solo operazioni sicure.

L'incapsulamento è quindi un caso speciale di occultamento delle informazioni, in cui ogni oggetto nasconde i suoi interni e rafforza i suoi invarianti.

La generazione di getter e setter per tutti i campi è una forma piuttosto debole di incapsulamento, poiché la struttura dei dati interni non è nascosta e gli invarianti non possono essere applicati. Ha il vantaggio di poter cambiare il modo in cui i dati sono archiviati internamente (purché sia ​​possibile convertirli nella e dalla vecchia struttura) senza dover cambiare (o persino ricompilare) i chiamanti, comunque.

Qualcuno potrebbe spiegarmi qual è il senso di nascondere tutti i campi come privati ​​e dopo di esporli tutti con qualche tecnologia in più? Perché semplicemente non usiamo solo i campi pubblici allora? Sento che abbiamo percorso una strada lunga e difficile solo per tornare al punto di partenza.

In parte, ciò è dovuto a un incidente storico. Il primo è che, in Java, le espressioni di invocazione del metodo e le espressioni di accesso ai campi sono sintatticamente diverse nel sito della chiamata, ovvero la sostituzione di un accesso al campo con una chiamata getter o setter interrompe l'API di una classe. Pertanto, se potrebbe essere necessario un programma di accesso, è necessario scriverne uno ora o essere in grado di interrompere l'API. Questa assenza di supporto delle proprietà a livello di linguaggio è in netto contrasto con altri linguaggi moderni, in particolare C # ed EcmaScript .

Strike 2 è che la specifica JavaBeans ha definito le proprietà come getter / setter, i campi non erano proprietà. Di conseguenza, la maggior parte dei framework aziendali iniziali supportava getter / setter, ma non campi. Ormai da molto tempo ( Java Persistence API (JPA) , Bean Validation , Java Architecture for XML Binding (JAXB) , Jacksonormai tutti i campi di supporto vanno bene), ma i vecchi tutorial e libri continuano a persistere, e non tutti sono consapevoli che le cose sono cambiate. L'assenza del supporto di proprietà a livello di lingua può ancora essere un problema in alcuni casi (ad esempio perché il caricamento pigro JPA di singole entità non si attiva quando vengono letti i campi pubblici), ma per lo più i campi pubblici funzionano bene. In altre parole, la mia azienda scrive tutti i DTO per le loro API REST con campi pubblici (dopo tutto non ottiene più pubblico che trasmette su Internet :-).

Detto questo, Lombok's @Datanon si limita a generare getter / setter: genera anche toString(), hashCode()e equals(Object), che può essere piuttosto prezioso.

E l'incapsulamento ha davvero senso nella programmazione della vita reale?

L'incapsulamento può essere prezioso o del tutto inutile, dipende dall'oggetto incapsulato. In generale, più complessa è la logica all'interno della classe, maggiore è il vantaggio dell'incapsulamento.

Getter e setter generati automaticamente per ogni campo sono generalmente abusati, ma possono essere utili per lavorare con framework legacy o per utilizzare la funzionalità di framework occasionale non supportata per i campi.

L'incapsulamento può essere ottenuto con getter e metodi di comando. I setter di solito non sono appropriati, perché si prevede che cambieranno solo un singolo campo, mentre mantenere gli invarianti può richiedere che diversi campi vengano cambiati contemporaneamente.

Sommario

Getter / setter offrono incapsulamenti piuttosto scadenti.

La prevalenza di getter / setter in Java deriva da una mancanza di supporto a livello di linguaggio per le proprietà e da discutibili scelte di progettazione nel suo modello di componente storico che sono state sancite in molti materiali didattici e dai programmatori da loro insegnati.

Altri linguaggi orientati agli oggetti, come EcmaScript, supportano le proprietà a livello di linguaggio, quindi i getter possono essere introdotti senza interrompere l'API. In tali lingue, i getter possono essere introdotti quando ne hai effettivamente bisogno, piuttosto che in anticipo, un giorno per caso, potresti averne bisogno un giorno, il che rende un'esperienza di programmazione molto più piacevole.


1
applica i suoi invarianti? È inglese? Potresti spiegarlo a un non inglese, per favore? Non essere troppo complicato - alcune persone qui non conoscono abbastanza bene l'inglese.
Gangnus,

Mi piacciono le tue basi, mi piacciono i tuoi pensieri (+1), ma non il risultato pratico. Preferirei utilizzare il campo privato e qualche libreria speciale per caricare i dati per loro tramite la riflessione. Solo non capisco perché stai impostando la tua risposta come argomento contro di me?
Gangnus,

@Gangnus Un invariante è una condizione logica che non cambia (intendo no e il significato -variante varia / cambia). Molte funzioni hanno regole che devono essere vere prima e dopo l'esecuzione (chiamate pre-condizioni e post-condizioni), altrimenti c'è un errore nel codice. Ad esempio, una funzione potrebbe richiedere che il suo argomento non sia nullo (una pre-condizione) e potrebbe generare un'eccezione se il suo argomento è nullo perché tale caso sarebbe un errore nel codice (ad esempio, nel campo dell'informatica, il codice ha rotto un invariante).
Pharap,

@Gangus: non sono sicuro di dove la riflessione entri in questa discussione; Lombok è un processore di annotazione, ovvero un plug-in per il compilatore Java che emette codice aggiuntivo durante la compilazione. Ma certo, puoi usare lombok. Sto solo dicendo che in molti casi i campi pubblici funzionano altrettanto bene e sono più facili da configurare (non tutti i compilatori rilevano automaticamente i processori di annotazioni ...).
meriton - in sciopero

1
@Gangnus: gli invarianti sono gli stessi in matematica e CS, ma in CS c'è una prospettiva aggiuntiva: dalla prospettiva di un oggetto, gli invarianti devono essere fatti valere e stabiliti. Dal punto di vista di un chiamante di quell'oggetto, gli invarianti sono sempre veri.
meriton - in sciopero

7

Mi sono davvero posto questa domanda.

Non è del tutto vero però. La prevalenza di getter / setter IMO è causata dalla specifica Java Bean, che lo richiede; quindi è praticamente una caratteristica non della programmazione orientata agli oggetti, ma della programmazione orientata al bean, se vuoi. La differenza tra i due è quella dello strato di astrazione in cui esistono; I fagioli sono più di un'interfaccia di sistema, cioè su un livello superiore. Estraggono dalle basi dell'OO, o almeno sono pensate - come sempre, le cose vengono spinte troppo spesso abbastanza spesso.

Direi che è un po 'sfortunato che questa cosa di Bean che sia onnipresente nella programmazione Java non sia accompagnata dall'aggiunta di una corrispondente funzione del linguaggio Java - Sto pensando a qualcosa come il concetto di Proprietà in C #. Per coloro che non ne sono consapevoli, è un costrutto linguistico che assomiglia a questo:

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

Ad ogni modo, il nocciolo dell'attuazione effettiva beneficia ancora moltissimo dell'incapsulamento.


Python ha una funzione simile in cui le proprietà possono avere getter / setter trasparenti. Sono sicuro che ci sono anche più lingue con funzionalità simili.
JAB,

1
"La prevalenza di getter / setter IMO è causata dalla specifica Java Bean, che lo richiede" Sì, hai ragione. E chi l'ha detto che deve essere richiesto? La ragione, per favore?
Gangnus,

2
Il modo C # è assolutamente lo stesso di quello di Lombok. Non vedo alcuna vera differenza. Più praticità pratica, ma ovviamente cattiva idea.
Gangnus,

1
@Gangnis La specifica lo richiede. Perché? Controlla la mia risposta per un esempio concreto del perché i getter non sono gli stessi dei campi di esposizione: prova a ottenere lo stesso senza un getter.
Vince Emigh,

@VinceEmigh gangnUs, per favore :-). (gang-walking, nus - nut). E che dire dell'utilizzo di get / set solo dove ne abbiamo specificamente bisogno e del caricamento di massa in campi privati ​​riflettendo in altri casi?
Gangnus,

6

Qualcuno potrebbe spiegarmi, che senso ha nascondere tutti i campi come privati ​​e, successivamente, esporli tutti con qualche tecnologia in più? Perché allora non utilizziamo semplicemente solo i campi pubblici? Sento che abbiamo percorso una strada lunga e difficile solo per tornare al punto di partenza.

La semplice risposta qui è: hai assolutamente ragione. Getter e setter eliminano gran parte (ma non tutto) del valore dell'incapsulamento. Questo non vuol dire che ogni volta che hai un metodo get e / o set che stai rompendo l'incapsulamento, ma se aggiungi ciecamente accessor a tutti i membri privati ​​della tua classe, stai sbagliando.

Sì, ci sono altre tecnologie che funzionano attraverso getter e setter. E non possiamo usarli attraverso semplici campi pubblici. Ma queste tecnologie sono apparse solo perché possedevamo quelle numerose proprietà: campi privati ​​dietro getter / setter pubblici. Se non avessimo le proprietà, queste tecnologie si svilupperebbero in un altro modo e sosterrebbero i campi pubblici. E tutto sarebbe semplice e non avremmo bisogno di Lombok ora. Qual è il senso di tutto questo ciclo? E l'incapsulamento ha davvero senso nella programmazione della vita reale?

I getter sono setter che sono onnipresenti nella programmazione Java perché il concetto JavaBean è stato spinto come un modo per legare dinamicamente la funzionalità al codice pre-costruito. Ad esempio, potresti avere un modulo in un'applet (qualcuno ricordi quelli?) Che ispezionerebbe il tuo oggetto, troverebbe tutte le proprietà e visualizzerebbe i campi as. Quindi l'interfaccia utente può modificare tali proprietà in base all'input dell'utente. Tu come sviluppatore ti preoccupi solo di scrivere la classe e mettere lì qualsiasi convalida o logica aziendale ecc.

inserisci qui la descrizione dell'immagine

Usando i fagioli di esempio

Questa non è una terribile idea di per sé, ma non sono mai stato un grande fan dell'approccio in Java. Sta andando controcorrente. Usa Python, Groovy ecc. Qualcosa che supporta questo tipo di approccio in modo più naturale.

La cosa di JavaBean è andata fuori controllo perché ha creato JOBOL, ovvero gli sviluppatori scritti Java che non capiscono OO. Fondamentalmente, gli oggetti non erano altro che sacchi di dati e tutta la logica veniva scritta all'esterno con metodi lunghi. Perché è stato visto come normale, persone come te e me che mettono in dubbio questo sono state considerate scherzose. Ultimamente ho visto un cambiamento e questa non è una posizione tanto estranea

La cosa vincolante XML è un dado duro. Questo probabilmente non è un buon campo di battaglia per prendere una posizione contro JavaBeans. Se devi creare questi JavaBeans, prova a tenerli fuori dal codice reale. Trattali come parte del livello di serializzazione.


2
I pensieri più concreti e saggi. +1. Ma perché non scrivere un gruppo di classi, di cui ognuno caricherà / salverà in modo massiccio campi privati ​​da / verso una struttura esterna? JSON, XML, altro oggetto. Potrebbe funzionare con POJO o, forse, solo con campi, contrassegnati da un'annotazione per questo. Ora sto pensando a quella alternativa.
Gangnus,

@Gangnus A indovinare perché ciò significa un sacco di scrittura di codice extra. Se si utilizza la riflessione, deve essere scritto solo un grumo di codice di serializzazione e può serializzare qualsiasi classe che segue il modello specifico. C # supporta questo attraverso l' [Serializable]attributo e i suoi parenti. Perché scrivere 101 metodi / classi di serializzazione specifici quando puoi semplicemente scriverne uno sfruttando la riflessione?
Pharap,

@Pharap Mi dispiace moltissimo, ma il tuo commento è troppo breve per me. "sfruttando la riflessione"? E qual è il senso di nascondere cose così diverse in una classe? Potresti spiegare cosa intendi?
Gangnus,

@Gangnus Intendo la riflessione , la capacità del codice di ispezionare la propria struttura. In Java (e in altre lingue) è possibile ottenere un elenco di tutte le funzioni esposte da una classe e quindi usarle come un modo per estrarre i dati dalla classe richiesta per serializzarli ad es. XML / JSON. Usare la riflessione che sarebbe un lavoro una tantum invece di creare costantemente nuovi metodi per salvare le strutture in base alla classe. Ecco un semplice esempio Java .
Pharap,

@Pharap Questo è proprio quello di cui stavo parlando. Ma perché lo chiami "sfruttando"? E l'alternativa che ho citato nel primo commento qui rimane: i campi (non) compressi dovrebbero avere annotazioni speciali o no?
Gangnus,

5

Quanto possiamo fare senza getter? È possibile rimuoverli completamente? Quali problemi crea questo? Potremmo anche vietare la returnparola chiave?

Si scopre che puoi fare molto se sei disposto a fare il lavoro. In che modo quindi le informazioni escono da questo oggetto completamente incapsulato? Attraverso un collaboratore

Piuttosto che lasciare che il codice ti faccia domande che dici alle cose per fare le cose. Se anche queste cose non restituiscono cose, non devi fare nulla per quello che restituiscono. Quindi, quando stai pensando di returncercare invece di provare un collaboratore alla porta di output che farà tutto ciò che resta da fare.

Fare le cose in questo modo ha benefici e conseguenze. Devi pensare a qualcosa di più di quello che saresti tornato. Devi pensare a come lo invierai come messaggio a un oggetto che non lo ha richiesto. È possibile che tu distribuisca lo stesso oggetto che avresti restituito, oppure potrebbe essere sufficiente chiamare un metodo. Fare questo pensiero ha un costo.

Il vantaggio è che ora stai parlando parlando di fronte all'interfaccia. Ciò significa che ottieni il massimo beneficio dall'astrazione.

Ti dà anche una spedizione polimorfica, perché mentre sai cosa stai dicendo non devi sapere esattamente a cosa lo stai dicendo.

Potresti pensare che questo significhi che devi percorrere solo una strada attraverso una pila di strati, ma si scopre che puoi usare il polimorfismo per andare indietro senza creare folli dipendenze cicliche.

Potrebbe apparire così:

Inserisci qui la descrizione dell'immagine

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 riesci a programmare in quel modo, allora usare i getter è una scelta. Non è un male necessario.


Sì, tali getter / setter hanno un grande senso. Ma capisci che si tratta di qualcos'altro? :-) +1 comunque.
Gangnus,

1
@Gangnus Grazie. Ci sono un sacco di cose di cui potresti parlare. Se ti interessasse almeno nominarli, potrei approfondirli.
candied_orange,

5

Questo è un problema controverso (come puoi vedere) perché un mucchio di dogma e incomprensioni si confonde con le ragionevoli preoccupazioni in merito al problema dei getter e dei setter. Ma in breve, non c'è nulla di sbagliato in questo @Datae non si rompe l'incapsulamento.

Perché usare getter e setter piuttosto che campi pubblici?

Perché i getter / setter forniscono l'incapsulamento. Se si espone un valore come campo pubblico e successivamente si passa al calcolo del valore al volo, è necessario modificare tutti i client che accedono al campo. Chiaramente questo è male. È un dettaglio dell'implementazione se una proprietà di un oggetto è memorizzata in un campo, viene generata al volo o viene recuperata da qualche altra parte, quindi la differenza non deve essere esposta ai client. Il setter Getter / setter risolve questo problema, poiché nascondono l'implementazione.

Ma se il getter / setter riflette solo un campo privato sottostante non è altrettanto male?

No! Il punto è che l'incapsulamento consente di modificare l'implementazione senza influire sui client. Un campo potrebbe comunque essere un modo perfetto per archiviare valore, a condizione che i clienti non debbano conoscerlo o preoccuparsene.

Ma i getter / setter autogeneranti dai campi rompono l'incapsulamento?

No l'incapsulamento è ancora lì! @Datale annotazioni sono solo un modo conveniente per scrivere coppie getter / setter che usano un campo sottostante. Per il punto di vista di un cliente questo è proprio come una normale coppia getter / setter. Se decidi di riscrivere l'implementazione, puoi comunque farlo senza influire sul client. Quindi ottieni il meglio da entrambi i mondi: incapsulamento e sintassi concisa.

Ma alcuni dicono che getter / setter sono sempre cattivi!

C'è una controversia separata, in cui alcuni credono che il modello getter / setter sia sempre negativo, indipendentemente dall'implementazione sottostante. L'idea è che non si dovrebbero impostare o ottenere valori dagli oggetti, ma qualsiasi interazione tra oggetti dovrebbe essere modellata come messaggi in cui un oggetto chiede a un altro oggetto di fare qualcosa. Questo è principalmente un pezzo di dogma fin dai primi tempi del pensiero orientato agli oggetti. Il pensiero ora è che per determinati modelli (ad es. Oggetti valore, oggetto trasferimento dati) getter / setter possono essere perfettamente appropriati.


L'incapsulamento dovrebbe consentirci polimorfismo e sicurezza. Ottiene / imposta consentire gli abeti, ma sono fortemente contro il secondo.
Gangnus,

Se dici che uso un dogma da qualche parte, per favore, per esempio, quale sia il mio testo è dogma. E non dimenticare che alcuni pensieri che sto usando non mi piacciono o sono d'accordo. Li sto usando per la dimostrazione di una contraddizione.
Gangnus,

1
"GangNus" :-). Una settimana fa ho lavorato su un progetto letteralmente pieno di chiamanti getter e setter su più livelli. È assolutamente non sicuro e, peggio ancora, supporta le persone con codici non sicuri. Sono contento di aver cambiato lavoro abbastanza velocemente, perché le persone si abituano in quel modo. Quindi, non lo penso, lo vedo .
Gangnus,

2
@jrh: non so se esiste una spiegazione formale in quanto tale, ma C # ha un supporto integrato per le proprietà (getter / setter con una sintassi migliore) e per i tipi anonimi che sono tipi con solo proprietà e nessun comportamento. Quindi i progettisti di C # si sono deliberatamente discostati dalla metafora della messaggistica e dal principio "dillo, non chiedere". Lo sviluppo di C # dalla versione 2.0 sembra ispirarsi più ai linguaggi funzionali che alla purezza OO tradizionale.
Jacques B,

1
@jrh: Sto indovinando ora, ma penso che C # sia piuttosto ispirato da linguaggi funzionali in cui dati e operazioni sono più separati. Nelle architetture distribuite, la prospettiva si è anche spostata da orientata ai messaggi (RPC, SOAP) a orientata ai dati (REST). E i database che espongono e operano su dati (non oggetti "scatola nera") hanno prevalso. In breve, penso che l'attenzione sia passata da messaggi tra scatole nere a modelli di dati esposti
JacquesB,

3

L'incapsulamento ha uno scopo, ma può anche essere abusato o abusato.

Considera qualcosa come l'API Android che ha classi con dozzine (se non centinaia) di campi. Esporre quei campi in cui il consumatore dell'API rende più difficile la navigazione e l'uso, inoltre dà all'utente la falsa idea di poter fare tutto ciò che vuole con quei campi che potrebbero essere in conflitto con il modo in cui dovrebbero essere utilizzati. Quindi l'incapsulamento è ottimo in questo senso per manutenibilità, usabilità, leggibilità ed evitare bug pazzi.

D'altra parte, POD o semplici tipi di dati vecchi, come una struttura di C / C ++ in cui tutti i campi sono pubblici possono essere utili. Avere getter / setter inutili come quelli generati dall'annotazione @data in Lombok è solo un modo per mantenere il "modello di incapsulamento". Uno dei pochi motivi per cui facciamo getter / setter "inutili" in Java è che i metodi forniscono un contratto .

In Java, non è possibile avere campi in un'interfaccia, pertanto si utilizzano getter e setter per specificare una proprietà comune di tutti gli implementatori di tale interfaccia. In linguaggi più recenti come Kotlin o C # vediamo il concetto di proprietà come campi per i quali è possibile dichiarare setter e getter. Alla fine, i getter / setter inutili sono più un'eredità con cui Java deve convivere, a meno che Oracle non aggiunga proprietà ad esso. Kotlin, ad esempio, che è un altro linguaggio JVM sviluppato da JetBrains, ha classi di dati che sostanzialmente fanno ciò che fa l'annotazione @data in Lombok.

Anche qui ci sono alcuni esempi:

class DataClass 
{
    private int data;

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

Questo è un brutto caso di incapsulamento. Il getter e il setter sono effettivamente inutili. L'incapsulamento viene utilizzato principalmente perché questo è lo standard in linguaggi come Java. In realtà non aiuta, oltre a mantenere la coerenza attraverso la base di codice.

class DataClass implements IDataInterface
{
    private int data;

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

Questo è un buon esempio di incapsulamento. L'incapsulamento viene utilizzato per far rispettare un contratto, in questo caso IDataInterface. Lo scopo dell'incapsulamento in questo esempio è di fare in modo che il consumatore di questa classe utilizzi i metodi forniti dall'interfaccia. Anche se getter e setter non fanno nulla di speciale, ora abbiamo definito un tratto comune tra DataClass e altri implementatori di IDataInterface. Quindi posso avere un metodo come questo:

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

Ora, quando parlo di incapsulamento, penso che sia importante anche affrontare il problema della sintassi. Vedo spesso persone lamentarsi della sintassi necessaria per imporre l'incapsulamento anziché l'incapsulamento stesso. Un esempio che viene in mente è di Casey Muratori (puoi vedere il suo sfogo qui ).

Supponi di avere una classe di giocatori che usa l'incapsulamento e desideri spostare la sua posizione di 1 unità. Il codice sarebbe simile al seguente:

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

Senza incapsulamento sembrerebbe così:

player.posX++;

Qui sostiene che l'incapsulamento porta a scrivere molto di più senza vantaggi aggiuntivi e questo può in molti casi essere vero, ma si nota qualcosa. L'argomento è contrario alla sintassi, non all'incapsulamento stesso. Anche in linguaggi come C che non hanno il concetto di incapsulamento, vedrai spesso variabili in strutture pre-fissate o suffissate con '_' o 'my' o qualsiasi altra cosa per indicare che non dovrebbero essere usate dal consumatore dell'API, come se fossero privato.

Il fatto è che l'incapsulamento può aiutare a rendere il codice molto più gestibile e facile da usare. Considera questa 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 le variabili fossero pubbliche in questo esempio, un consumatore di questa API sarebbe confuso su quando usare posX e posY e quando usare setPosition (). Nascondendo questi dettagli aiuti il ​​consumatore a utilizzare meglio la tua API in modo intuitivo.

La sintassi è tuttavia una limitazione in molte lingue. Tuttavia i linguaggi più recenti offrono proprietà che ci danno la bella sintassi dei membri del publice e i vantaggi dell'incapsulamento. Troverai le proprietà in C #, Kotlin, anche in C ++ se usi MSVC. ecco un esempio in Kotlin.

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

Qui abbiamo realizzato le stesse cose dell'esempio Java, ma possiamo usare posX e posY come se fossero variabili pubbliche. Quando provo a cambiare il loro valore, però, verrà eseguito il corpo del setter set ().

In Kotlin, ad esempio, questo sarebbe l'equivalente di un bean Java con getter, setter, hashcode, uguale e toString implementati:

data class DataClass(var data: Int)

Notare come questa sintassi ci consente di eseguire un bean Java in una riga. Hai notato correttamente il problema che un linguaggio come Java ha nell'implementazione dell'incapsulamento, ma è colpa di Java non dell'incapsulamento stesso.

Hai detto che usi @Data di Lombok per generare getter e setter. Nota il nome, @Data. È principalmente pensato per essere utilizzato su classi di dati che archiviano solo dati e devono essere serializzati e deserializzati. Pensa a qualcosa come un file di salvataggio da un gioco. Ma in altri scenari, come con un elemento dell'interfaccia utente, si vogliono sicuramente setter perché semplicemente cambiare il valore di una variabile potrebbe non essere sufficiente per ottenere il comportamento previsto.


Potresti mettere qui un esempio sull'uso improprio dell'incapsulamento? Questo potrebbe essere interessante. Il tuo esempio è piuttosto contro la mancanza di incapsulamento.
Gangnus,

1
@Gangnus ho aggiunto alcuni esempi. L'incapsulamento inutile è generalmente un uso improprio e anche dalla mia esperienza gli sviluppatori di API si sforzano troppo di costringerti a utilizzare un'API in un certo modo. Non ne ho fatto un esempio perché non ne avevo uno facile da presentare. Se ne trovo uno, lo aggiungerò sicuramente. Penso che la maggior parte dei commenti contro l'incapsulamento siano in realtà contro la sintassi dell'incapsulamento piuttosto che dell'incapsulamento stesso.
BananyaDev,

Grazie per la modifica. Ho trovato un piccolo errore di battitura però: "publice" anziché "pubblico".
jrh

2

L'incapsulamento ti offre flessibilità . Separando struttura e interfaccia, consente di modificare la struttura senza modificare l'interfaccia.

Ad esempio, se scopri che devi calcolare una proprietà in base ad altri campi invece di inizializzare il campo sottostante durante la costruzione, puoi semplicemente cambiare il getter. Se avessi esposto direttamente il campo, dovresti invece modificare l'interfaccia e apportare modifiche in ogni sito di utilizzo.


Sebbene sia una buona idea in teoria, ricorda che causare la eccezione della versione 2 di un'API che la versione 1 non avrebbe infranto terribilmente gli utenti della tua libreria o classe (e possibilmente in silenzio!); Sono un po 'scettico sul fatto che qualsiasi cambiamento importante possa essere apportato "dietro le quinte" sulla maggior parte di queste classi di dati.
jrh

Siamo spiacenti, non è una spiegazione. Il fatto che l'uso dei campi sia vietato nelle interfacce deriva dall'idea di incapsulamento. E ora ne stiamo parlando. Ti stai basando sui fatti in discussione. (nota, non sto parlando di pro o contro i tuoi pensieri, solo che non possono essere usati come argomenti qui)
Gangnus,

@Gangnus La parola "interfaccia" ha un significato al di fuori della sola parola chiave Java: dizionario.com/browse/interface
glennsl

@jrh Questo è un problema completamente diverso. Puoi usarlo per argomentare contro l'incapsulamento in determinati contesti , ma non invalida in alcun modo gli argomenti per esso.
glennsl,

1
Il vecchio incapsulamento, senza acquisizione / messa in serie, ci ha dato non solo il polimorfismo, ma anche la sicurezza. E i set di massa / ci insegnano verso la cattiva pratica.
Gangnus,

2

Cercherò di illustrare lo spazio problematico dell'incapsulamento e del design di classe e di rispondere alla tua domanda alla fine.

Come menzionato in altre risposte, lo scopo dell'incapsulamento è nascondere i dettagli interni di un oggetto dietro un'API pubblica, che funge da contratto. L'oggetto è sicuro di cambiare i suoi interni perché sa che è chiamato solo attraverso l'API pubblica.

Se ha senso disporre di campi pubblici, getter / setter o metodi di transazione di livello superiore o passaggio di messaggi dipende dalla natura del dominio che viene modellato. Nel libro Akka Concurrency (che posso consigliare anche se è in qualche modo obsoleto) trovi un esempio che illustra questo, che abbrevierò qui.

Prendi in considerazione una classe utente:

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;
  }
}

Funziona bene in un contesto a thread singolo. Il dominio che viene modellato è il nome di una persona e la meccanica di come viene memorizzato quel nome può essere perfettamente incapsulata dai setter.

Tuttavia, immagina che ciò debba essere fornito in un contesto multi-thread. Supponiamo che un thread legga periodicamente il nome:

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

E altri due fili stanno combattendo un tiro alla fune, ponendolo a sua volta a Hillary Clinton e Donald Trump . Ognuno di essi deve chiamare due metodi. Per lo più funziona bene, ma ogni tanto vedrai passare Hillary Trump o Donald Clinton .

Non è possibile risolvere questo problema aggiungendo un blocco all'interno dei setter, poiché il blocco viene mantenuto solo per la durata dell'impostazione del nome o del cognome. L'unica soluzione tramite il blocco è aggiungere un blocco attorno all'intero oggetto, ma ciò rompe l'incapsulamento poiché il codice chiamante deve gestire il blocco (e può causare deadlock).

A quanto pare, non esiste una soluzione pulita attraverso il bloccaggio. La soluzione pulita è incapsulare nuovamente gli interni rendendoli più grossolani:

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; }
}

Il nome stesso è diventato immutabile e vedi che i suoi membri possono essere pubblici, perché ora è un oggetto dati puro senza possibilità di modificarlo una volta creato. A sua volta, l'API pubblica della classe User è diventata più approssimativa, con un solo setter rimasto, in modo che il nome possa essere modificato solo nel suo insieme. Incapsula più del suo stato interno dietro l'API.

Qual è il senso di tutto questo ciclo? E l'incapsulamento ha davvero senso nella programmazione della vita reale?

Ciò che vedi in questo ciclo sono i tentativi di applicare soluzioni valide per una serie specifica di circostanze in modo troppo ampio. Un livello adeguato di incapsulamento richiede la comprensione del dominio modellato e l'applicazione del giusto livello di incapsulamento. A volte questo significa che tutti i campi sono pubblici, a volte (come nelle applicazioni Akka) significa che non hai affatto un'API pubblica ad eccezione di un singolo metodo per ricevere messaggi. Tuttavia, il concetto stesso di incapsulamento, che significa nascondere gli interni dietro un'API stabile, è la chiave per programmare software su larga scala, specialmente nei sistemi multi-thread.


Fantastico. Direi che hai sollevato la discussione a un livello superiore. Forse due. Davvero, pensavo di capire l'incapsulamento e talvolta meglio dei libri standard, e avevo davvero paura che lo stessimo praticamente perdendo a causa di abitudini e tecnologie accettate, e QUESTO era il vero argomento della mia domanda (forse, non formulato in un buon modo), ma mi hai mostrato lati davvero nuovi dell'incapsulamento. Non sono sicuramente bravo nel multitasking e mi hai dimostrato quanto possa essere dannoso per la comprensione di altri principi di base.
Gangnus,

Ma non posso segnare il tuo posto come la risposta, perché non si tratta di caricamento dei dati di massa da / per gli oggetti, il problema a causa della quale appaiono tali piattaforme come fagioli e Lombok. Forse, potresti elaborare i tuoi pensieri in quella direzione, per favore, se riesci a trovare il tempo? Io stesso non ho ancora ripensato alle conseguenze che il tuo pensiero porta a quel problema. E non sono sicuro di essere abbastanza in forma per questo (ricorda, brutto background multithreading: - [)
Gangnus,

Non ho usato lombok, ma a quanto ho capito è un modo per implementare un livello specifico di incapsulamento (getter / setter su ogni campo) con meno battiture. Non cambia la forma ideale dell'API per il tuo dominio problematico, è solo uno strumento per scriverlo più rapidamente.
Joeri Sebrechts,

1

Mi viene in mente un caso d'uso in cui questo ha senso. Potresti avere una classe a cui accedi originariamente tramite una semplice API getter / setter. Successivamente si estende o si modifica in modo che non utilizzi più gli stessi campi, ma supporti comunque la stessa API .

Un esempio in qualche modo inventato: un punto che inizia come una coppia cartesiana con p.x()e p.y(). Successivamente, esegui una nuova implementazione o sottoclasse che utilizza coordinate polari, quindi puoi anche chiamare p.r()e p.theta(), ma il tuo codice client che chiama p.x()e p.y()rimane valido. La classe stessa si converte in modo trasparente dalla forma polare interna, cioè y()sarebbe ora return r * sin(theta);. (In questo esempio, impostare solo x()o y()non ha più senso, ma è ancora possibile.)

In questo caso, potresti ritrovarti a dire: "Sono contento che mi sia preoccupato di dichiarare automaticamente getter e setter invece di rendere pubblici i campi, altrimenti avrei dovuto rompere la mia API lì".


2
Non è così bello come lo vedi. Cambiare la rappresentazione fisica interna rende davvero diverso l'oggetto. Ed è un male che abbiano lo stesso aspetto, poiché hanno qualità diverse. Il sistema Cartesio non ha punti speciali, il sistema polare ne ha uno.
Gangnus,

1
Se non ti piace quell'esempio specifico, sicuramente puoi pensare ad altri che hanno davvero più rappresentazioni equivalenti o una nuova rappresentazione che può essere convertita in e da una più vecchia.
Davislor,

Sì, puoi usarli in modo molto produttivo per la presentazione. Nell'interfaccia utente può essere ampiamente utilizzato. Ma non mi stai facendo rispondere alla mia domanda? :-)
Gangnus,

Più efficiente in questo modo, no? :)
Davislor,

0

Qualcuno potrebbe spiegarmi, che senso ha nascondere tutti i campi come privati ​​e, successivamente, esporli tutti con qualche tecnologia in più?

Non ha assolutamente senso. Tuttavia, il fatto che tu faccia questa domanda dimostra che non hai capito cosa fa Lombok e che non capisci come scrivere il codice OO con l'incapsulamento. Riavvolgiamo un po '...

Alcuni dati per un'istanza di classe saranno sempre interni e non dovrebbero mai essere esposti. Alcuni dati per un'istanza di classe dovranno essere impostati esternamente e alcuni dati potrebbero dover essere restituiti da un'istanza di classe. Potremmo voler cambiare il modo in cui la classe fa cose sotto la superficie, quindi usiamo le funzioni per permetterci di ottenere e impostare i dati.

Alcuni programmi vogliono salvare lo stato per le istanze di classe, quindi potrebbero avere un'interfaccia di serializzazione. Aggiungiamo più funzioni che consentono all'istanza della classe di archiviare il suo stato nella memoria e di recuperarne lo stato dalla memoria. Ciò mantiene l'incapsulamento perché l'istanza della classe ha ancora il controllo dei propri dati. Potremmo serializzare i dati privati, ma il resto del programma non ha accesso ad essi (o, più precisamente, manteniamo un muro cinese scegliendo di non corrompere deliberatamente quei dati privati) e l'istanza della classe può (e dovrebbe) condurre controlli di integrità sulla deserializzazione per assicurarsi che i suoi dati siano tornati OK.

A volte i dati richiedono controlli di intervallo, controlli di integrità o cose del genere. Scrivere queste funzioni da soli ci consente di fare tutto ciò. In questo caso non vogliamo o non abbiamo bisogno di Lombok, perché stiamo facendo tutto da soli.

Spesso, però, scopri che un parametro impostato esternamente è memorizzato in una singola variabile. In questo caso sono necessarie quattro funzioni per ottenere / impostare / serializzare / deserializzare i contenuti di quella variabile. Scrivere queste quattro funzioni da soli ogni volta ti rallenta ed è soggetto a errori. L'automazione del processo con Lombok accelera lo sviluppo e rimuove la possibilità di errori.

Sì, sarebbe possibile rendere pubblica tale variabile. In questa particolare versione del codice, sarebbe funzionalmente identico. Ma torniamo al motivo per cui stiamo usando le funzioni: "Potremmo voler cambiare il modo in cui la classe fa cose sotto la superficie ..." Se rendi pubblica la tua variabile, stai vincolando il tuo codice ora e per sempre per avere questa variabile pubblica come l'interfaccia. Se si utilizzano le funzioni o se si utilizza Lombok per generare automaticamente tali funzioni, si è liberi di modificare i dati sottostanti e l'implementazione sottostante in qualsiasi momento in futuro.

Questo lo rende più chiaro?


Stai parlando delle domande dello studente SW del primo anno. Siamo già altrove. Non lo direi mai e ti darei anche un vantaggio per risposte intelligenti, se non fossi così scortese sotto forma della tua risposta.
Gangnus,

0

In realtà non sono uno sviluppatore Java; ma quanto segue è praticamente indipendente dalla piattaforma.

Quasi tutto ciò che scriviamo utilizza getter e setter pubblici che accedono a variabili private. La maggior parte dei getter e setter sono banali. Ma quando decidiamo che il setter ha bisogno di ricalcolare qualcosa o il setter fa un po 'di validazione o dobbiamo inoltrare la proprietà a una proprietà di una variabile membro di questa classe, questo è completamente non-break all'intero codice ed è binario compatibile, quindi noi può scambiare quel modulo.

Quando decidiamo che questa proprietà dovrebbe davvero essere calcolata al volo, tutto il codice che la osserva non deve cambiare e solo il codice che le scrive deve cambiare e l'IDE può trovarla per noi. Quando decidiamo che si tratta di un campo calcolato scrivibile (lo abbiamo dovuto fare solo poche volte), possiamo farlo anche noi. La cosa bella è che alcuni di questi cambiamenti sono binari compatibili (il passaggio al campo calcolato di sola lettura non è in teoria ma potrebbe essere in pratica comunque).

Abbiamo finito con un sacco di banali banali con setter complicati. Abbiamo anche finito con alcuni getter di cache. Il risultato finale è che ti è permesso supporre che i getter siano ragionevolmente economici ma i setter potrebbero non esserlo. D'altra parte, siamo abbastanza saggi da decidere che i setter non persistono sul disco.

Ma ho dovuto rintracciare il ragazzo che stava cambiando ciecamente tutte le variabili membro in proprietà. Non sapeva che cosa fosse l'aggiunta atomica, quindi cambiò la cosa che doveva davvero essere una variabile pubblica in una proprietà e spezzò il codice in modo sottile.


Benvenuti nel mondo Java. (Anche C #). *sospiro*. Ma per quanto riguarda l'utilizzo di get / set per nascondere la rappresentazione interna, fai attenzione. Riguarda solo il formato esterno, è OK. Ma se si tratta di matematica o, peggio ancora, di fisica o di altre cose reali, la diversa rappresentazione interiore ha qualità diverse. vale a dire il mio commento a softwareengineering.stackexchange.com/a/358662/44104
Gangnus

@Gangnus: questo esempio di punto è molto sfortunato. Ne abbiamo avute alcune, ma il calcolo è sempre stato esatto.
Giosuè,

-1

Getter e setter sono una misura "per ogni evenienza" aggiunta al fine di evitare il refactoring in futuro se la struttura interna o i requisiti di accesso cambiano durante il processo di sviluppo.

Supponiamo che pochi mesi dopo il rilascio, il tuo client ti informi che a volte uno dei campi di una classe è impostato su valori negativi, anche se in quel caso dovrebbe bloccarsi al massimo a 0. Utilizzando i campi pubblici, dovresti cercare ogni assegnazione di questo campo nell'intera base di codice per applicare la funzione di blocco al valore che intendi impostare e tieni presente che dovrai sempre farlo quando modifichi questo campo, ma fa schifo. Invece, se ti fosse già capitato di utilizzare getter e setter, saresti stato in grado di modificare il tuo metodo setField () per assicurarti che questo bloccaggio fosse sempre applicato.

Ora, il problema con linguaggi "antiquati" come Java è che incoraggiano l'uso di metodi per questo scopo, il che rende il tuo codice infinitamente più dettagliato. È doloroso da scrivere e difficile da leggere, motivo per cui abbiamo usato IDE che mitiga questo problema in un modo o nell'altro. La maggior parte degli IDE genererà automaticamente getter e setter per te, e li nasconderà anche se non diversamente specificato. Lombok fa un passo avanti e semplicemente li genera proceduralmente durante il tempo di compilazione per mantenere il tuo codice extra lucido. Tuttavia, altre lingue più moderne hanno semplicemente risolto questo problema in un modo o nell'altro. Linguaggi Scala o .NET, ad esempio,

Ad esempio, in VB .NET o C #, potresti semplicemente rendere tutti i campi destinati ad avere pianificatori senza effetti collaterali e setter solo campi pubblici, e in seguito renderli privati, cambiare il loro nome ed esporre una proprietà con il nome precedente di il campo, in cui è possibile ottimizzare il comportamento di accesso del campo, se necessario. Con Lombok, se hai bisogno di ottimizzare il comportamento di un getter o setter, puoi semplicemente rimuovere quei tag quando necessario e codificarli con i nuovi requisiti, sapendo per certo che non dovrai refactificare nulla in altri file.

Fondamentalmente, il modo in cui il tuo metodo accede a un campo dovrebbe essere trasparente e uniforme. I linguaggi moderni ti consentono di definire "metodi" con la stessa sintassi di accesso / chiamata di un campo, quindi queste modifiche possono essere fatte su richiesta senza pensarci molto durante lo sviluppo iniziale, ma Java ti costringe a fare questo lavoro in anticipo perché lo fa non avere questa funzione. Tutto ciò che Lombok fa è farti risparmiare un po 'di tempo, perché la lingua che stai utilizzando non voleva farti risparmiare tempo per un metodo "per ogni evenienza".


In quei linguaggi "moderni", l'API sembra un accesso al campo foo.barma può essere gestita da una chiamata di metodo. Sostieni che questo è superiore al modo "antiquato" di far apparire un'API come chiamate di metodo foo.getBar(). Sembriamo concordare sul fatto che i campi pubblici siano problematici, ma sostengo che l'alternativa "antiquata" sia superiore a quella "moderna", poiché la nostra intera API è simmetrica (sono tutte chiamate al metodo). Nell'approccio "moderno", dobbiamo decidere quali cose dovrebbero essere proprietà e quali dovrebbero essere metodi, che complicano eccessivamente tutto (specialmente se usiamo la riflessione!).
Warbo,

1
1. Nascondere la fisica non è sempre così buona. guarda il mio commento a softwareengineering.stackexchange.com/a/358662/44104 . 2. Non sto parlando di quanto sia difficile o semplice la creazione di getter / setter. Il C # ha assolutamente lo stesso problema di cui stiamo parlando. La mancanza di incapsulamento a causa della cattiva soluzione del caricamento di informazioni di massa. 3. Sì, la soluzione potrebbe essere basata sulla sintassi, ma, per favore, in alcuni modi diversi da quelli che stai citando. Quelli sono cattivi, abbiamo qui una grande pagina piena di spiegazioni. 4. La soluzione non deve essere basata sulla sintassi. Potrebbe essere fatto nei limiti di Java.
Gangnus,

-1

Qualcuno potrebbe spiegarmi qual è il senso di nascondere tutti i campi come privati ​​e dopo di esporli tutti con qualche tecnologia in più? Perché semplicemente non usiamo solo i campi pubblici allora? Sento che abbiamo percorso una strada lunga e difficile solo per tornare al punto di partenza.

Sì, è contraddittorio. Ho incontrato per la prima volta le proprietà in Visual Basic. Fino ad allora, in altre lingue, la mia esperienza, non c'erano avvolgitori di proprietà attorno ai campi. Solo campi pubblici, privati ​​e protetti.

Le proprietà erano una specie di incapsulamento. Ho capito che le proprietà di Visual Basic erano un modo per controllare e manipolare l'output di uno o più campi nascondendo il campo esplicito e persino il suo tipo di dati, ad esempio emettendo una data come una stringa in un particolare formato. Ma anche allora non si sta "nascondendo lo stato ed esponendo la funzionalità" dalla prospettiva dell'oggetto più grande.

Ma le proprietà si giustificano a causa di getter e setter di proprietà separati. Esporre un campo senza una proprietà era tutto o niente - se potessi leggerlo potresti cambiarlo. Quindi ora si può giustificare il design di classe debole con setter protetti.

Quindi perché non abbiamo usato solo metodi reali? Perché era Visual Basic (e VBScript ) (oooh! Aaah!), Codifica per le masse (!), Ed era di gran moda. E così alla fine un'idiotossia ha dominato.


Sì, le proprietà dell'interfaccia utente hanno un senso speciale, sono rappresentazioni pubbliche e graziose dei campi. Buon punto.
Gangnus,

"Allora perché non abbiamo usato solo metodi reali?" Tecnicamente lo hanno fatto nella versione .Net. Le proprietà sono zucchero sintattico per ottenere e impostare funzioni.
Pharap,

"idiotocrazia" ? Non intendi " idiosincrasia "?
Peter Mortensen,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.