Privato vs.Membri pubblici nella pratica (quanto è importante l'incapsulamento?) [chiuso]

StackOverflow https://stackoverflow.com/questions/99688

  •  01-07-2019
  •  | 
  •  

Domanda

Uno dei maggiori vantaggi della programmazione orientata agli oggetti è l'incapsulamento, e una delle "verità" che ci è stata insegnata (o, almeno, mi è stata insegnata) è che i membri dovrebbero sempre essere resi privati ​​e resi disponibili tramite accessor e mutator metodi, garantendo così la capacità di verificare e validare le modifiche.

Sono curioso, però, di quanto questo sia davvero importante nella pratica.In particolare, se hai un membro più complicato (come una raccolta), può essere molto forte la tentazione di renderlo semplicemente pubblico piuttosto che creare una serie di metodi per ottenere le chiavi della raccolta, aggiungere/rimuovere elementi dalla raccolta, eccetera.

Segui la regola in generale?La tua risposta cambia a seconda che si tratti di codice scritto per te o di codice scritto per te stesso.essere usato da altri?Ci sono ragioni più sottili che mi sfuggono per questo offuscamento?

È stato utile?

Soluzione

Dipende.Questa è una di quelle questioni che devono essere decise in modo pragmatico.

Supponiamo di avere una classe per rappresentare un punto.Potrei avere getter e setter per le coordinate X e Y, oppure potrei semplicemente renderli entrambi pubblici e consentire l'accesso gratuito in lettura/scrittura ai dati.Secondo me, questo va bene perché la classe si comporta come una struttura glorificata, una raccolta di dati con forse alcune funzioni utili allegate.

Tuttavia, ci sono molte circostanze in cui non vuoi fornire pieno accesso ai tuoi dati interni e fare affidamento sui metodi forniti dalla classe per interagire con l'oggetto.Un esempio potrebbe essere una richiesta e risposta HTTP.In questo caso è una cattiva idea consentire a chiunque di inviare qualcosa via cavo: deve essere elaborato e formattato dai metodi della classe.In questo caso la classe è concepita come un oggetto vero e proprio e non come un semplice archivio dati.

Dipende davvero se i verbi (metodi) guidano o meno la struttura o se i dati lo fanno.

Altri suggerimenti

Essendo qualcuno che deve mantenere un codice vecchio di diversi anni su cui hanno lavorato molte persone in passato, è molto chiaro per me che se un attributo di un membro viene reso pubblico, alla fine viene abusato.Ho anche sentito persone in disaccordo con l'idea di accessori e mutatori, poiché non è ancora all'altezza dello scopo dell'incapsulamento, che è "nascondere il funzionamento interno di una classe".È ovviamente un argomento controverso, ma la mia opinione sarebbe "rendere ogni membro variabile privata, pensare principalmente a ciò che la classe deve fare" Fare (metodi) piuttosto che Come lascerai che le persone cambino le variabili interne".

Sì, l'incapsulamento è importante.Esporre l'implementazione sottostante fa (almeno) due cose sbagliate:

  1. Confonde le responsabilità.I chiamanti non dovrebbero aver bisogno o voler comprendere l'implementazione sottostante.Dovrebbero semplicemente desiderare che la classe faccia il suo lavoro.Esponendo l'implementazione sottostante, la tua classe non sta facendo il suo lavoro.Invece, sta semplicemente scaricando la responsabilità sul chiamante.
  2. Ti lega all'implementazione sottostante.Una volta esposta l'implementazione sottostante, sei legato ad essa.Se dici ai chiamanti, ad esempio, che sotto c'è una raccolta, non puoi scambiare facilmente la raccolta con una nuova implementazione.

Questi (e altri) problemi si applicano indipendentemente dal fatto che tu dia accesso diretto all'implementazione sottostante o semplicemente duplichi tutti i metodi sottostanti.Dovresti esporre l'implementazione necessaria e niente di più.Mantenere l'implementazione privata rende il sistema complessivo più manutenibile.

Preferisco mantenere i membri privati ​​il ​​​​più a lungo possibile e accedervi solo tramite getter, anche dall'interno della stessa classe.Cerco anche di evitare i setter come prima bozza per promuovere oggetti di stile di valore finché è possibile.Lavorando molto con l'iniezione di dipendenze, spesso hai setter ma non getter, poiché i client dovrebbero essere in grado di configurare l'oggetto ma (gli altri) non riescono a sapere cosa è effettivamente configurato poiché si tratta di un dettaglio di implementazione.

Saluti, Ollie

Tendo a seguire la regola in modo piuttosto rigoroso, anche quando si tratta solo del mio codice.Per questo motivo mi piacciono molto le proprietà in C#.Rende davvero facile controllare quali valori vengono forniti, ma puoi comunque usarli come variabili.Oppure rendi il set privato e rendilo pubblico, ecc.

Fondamentalmente, l'occultamento delle informazioni riguarda la chiarezza del codice.È progettato per rendere più semplice per qualcun altro estendere il tuo codice e impedire loro di creare accidentalmente bug quando lavorano con i dati interni delle tue classi.Si basa sul principio che nessuno legge mai i commenti, specialmente quelli con le istruzioni al loro interno.

Esempio: Sto scrivendo codice che aggiorna una variabile e devo essere assolutamente sicuro che la Gui cambi per riflettere la modifica, il modo più semplice è aggiungere un metodo di accesso (noto anche come "Setter"), che viene chiamato invece di aggiornare i dati è aggiornato.

Se rendo pubblici quei dati e qualcosa cambia la variabile senza passare attraverso il metodo Setter (e questo accade ogni volta che si pronunciano parolacce), allora qualcuno dovrà dedicare un'ora al debug per scoprire perché gli aggiornamenti non vengono visualizzati.Lo stesso vale, in misura minore, per l'"acquisizione" dei dati.Potrei inserire un commento nel file di intestazione, ma è probabile che nessuno lo leggerà finché qualcosa non andrà terribilmente, terribilmente storto.Farlo rispettare con i privati ​​significa che l'errore non posso essere creato, perché verrà visualizzato come un bug in fase di compilazione facilmente individuabile, piuttosto che come bug in fase di esecuzione.

Per esperienza, le uniche volte in cui vorresti rendere pubblica una variabile membro e tralasciare i metodi Getter e Setter, è se vuoi rendere assolutamente chiaro che cambiarla non avrà effetti collaterali;soprattutto se la struttura dei dati è semplice, come una classe che contiene semplicemente due variabili in coppia.

Questo dovrebbe essere un evento abbastanza raro, come faresti normalmente Volere effetti collaterali e se la struttura dati che stai creando è così semplice da non farlo (ad esempio un abbinamento), ce ne sarà già una scritta in modo più efficiente disponibile in una libreria standard.

Detto questo, per la maggior parte dei piccoli programmi monouso e senza estensione, come quelli che ottieni all'università, è più una "buona pratica" che altro, perché te ne ricorderai nel corso della scrittura, e poi... Li consegnerò e non toccherò mai più il codice.Inoltre, se stai scrivendo una struttura dati come un modo per scoprire come memorizzano i dati piuttosto che come codice di rilascio, allora c'è una buona argomentazione secondo cui Getter e Setter non aiuteranno e ostacoleranno l'esperienza di apprendimento .

È solo quando arrivi sul posto di lavoro o in un grande progetto, dove è probabile che il tuo codice venga richiamato da oggetti e strutture scritti da persone diverse, che diventa vitale rendere forti questi "promemoria".Che si tratti o meno di un progetto da single è sorprendentemente irrilevante, per la semplice ragione che "tu tra sei settimane" è una persona diversa quanto un collega.E "io sei settimane fa" spesso risulta essere pigro.

Un ultimo punto è che alcune persone sono piuttosto zelanti nel nascondere le informazioni e si arrabbieranno se i tuoi dati saranno pubblici inutilmente.È meglio assecondarli.

Le proprietà C# "simulano" i campi pubblici.Sembra piuttosto interessante e la sintassi accelera davvero la creazione di questi metodi get/set

Tieni presente la semantica dell'invocazione di metodi su un oggetto.L'invocazione di un metodo è un'astrazione di altissimo livello che può essere implementata nel compilatore o nel sistema runtime in una varietà di modi diversi.

Se l'oggetto del metodo che stai invocando esiste nello stesso processo/mappa di memoria, un metodo potrebbe essere ottimizzato da un compilatore o da una VM per accedere direttamente al membro dati.D'altra parte, se l'oggetto risiede su un altro nodo in un sistema distribuito, non è possibile accedere direttamente ai suoi dati interni, ma è comunque possibile invocare i suoi metodi per inviargli un messaggio.

Codificando le interfacce puoi scrivere codice a cui non importa dove esiste l'oggetto di destinazione o come vengono invocati i suoi metodi o anche se è scritto nello stesso linguaggio.


Nel tuo esempio di un oggetto che implementa tutti i metodi di una raccolta, sicuramente quell'oggetto in realtà È una collezione.quindi forse questo sarebbe un caso in cui l'ereditarietà sarebbe migliore dell'incapsulamento.

Si tratta di controllare cosa le persone possono fare con ciò che dai loro.Più controlli hai, più supposizioni puoi fare.

Inoltre, in teoria puoi modificare l'implementazione sottostante o qualcosa del genere, ma poiché per la maggior parte è:

private Foo foo;
public Foo getFoo() {}
public void setFoo(Foo foo) {}

E' un po' difficile da giustificare.

L'incapsulamento è importante quando almeno uno di questi vale:

  1. Chiunque tranne te utilizzerà la tua classe (o romperà le tue invarianti perché non leggono la documentazione).
  2. Chiunque non legga la documentazione utilizzerà la tua classe (o romperà le tue invarianti attentamente documentate).Tieni presente che questa categoria include te tra due anni.
  3. Ad un certo punto in futuro qualcuno erediterà dalla tua classe (perché forse è necessario intraprendere un'azione aggiuntiva quando il valore di un campo cambia, quindi deve esserci un setter).

Se è solo per me, e utilizzato in pochi posti, e non lo erediterò, e la modifica dei campi non invaliderà alcun invariante assunto dalla classe, solo allora Occasionalmente renderò pubblico un campo.

La mia tendenza è cercare di rendere tutto privato, se possibile.Ciò mantiene i confini degli oggetti quanto più chiaramente definiti possibile e mantiene gli oggetti quanto più disaccoppiati possibile.Mi piace perché quando devo riscrivere un oggetto su cui ho sbagliato la prima (seconda, quinta?) volta, mantiene il danno contenuto in un numero minore di oggetti.

Se accoppi gli oggetti abbastanza strettamente, potrebbe essere più semplice combinarli semplicemente in un unico oggetto.Se allenti abbastanza i vincoli di accoppiamento, torni alla programmazione strutturata.

Può darsi che se scopri che molti dei tuoi oggetti sono solo funzioni accessorie, dovresti ripensare le divisioni degli oggetti.Se non stai eseguendo alcuna azione su tali dati, potrebbero appartenere a un altro oggetto.

Naturalmente, se stai scrivendo qualcosa come una libreria, vuoi un'interfaccia quanto più chiara e nitida possibile in modo che altri possano programmare contro di essa.

Adattare lo strumento al lavoro...di recente ho visto del codice come questo nella mia codebase attuale:

private static class SomeSmallDataStructure {
    public int someField;
    public String someOtherField;
}

E poi questa classe è stata utilizzata internamente per passare facilmente più valori di dati.Non sempre ha senso, ma se hai solo DATI, senza metodi, e non li esponi ai clienti, trovo che sia uno schema abbastanza utile.

L'uso più recente che ho avuto di questo è stato una pagina JSP in cui veniva visualizzata una tabella di dati, definita in alto in modo dichiarativo.Quindi, inizialmente era in più array, un array per campo dati...questo ha reso il codice piuttosto difficile da leggere con i campi non uno accanto all'altro nella definizione che sarebbero stati visualizzati insieme...quindi ho creato una classe semplice come sopra che avrebbe messo insieme il tutto...il risultato è stato un codice VERAMENTE leggibile, molto più di prima.

Morale...a volte dovresti considerare alternative "cattive accettate" se possono rendere il codice più semplice e facile da leggere, purché ci pensi bene e consideri le conseguenze...non accettare ciecamente TUTTO ciò che senti.

Detto ciò...getter e setter pubblici sono praticamente equivalenti ai campi pubblici...almeno essenzialmente (c'è un po' più di flessibilità, ma è comunque un brutto modello da applicare a OGNI campo che hai).

Anche le librerie standard Java presentano alcuni casi di campi pubblici.

Quando rendo gli oggetti significativi, lo sono Più facile A utilizzo e più facile da mantenere.

Per esempio:Person.Hand.Grab(quanto velocemente, quanto);

Il trucco non sta nel pensare ai membri come semplici valori ma come oggetti in sé.

Direi che questa domanda confonde il concetto di incapsulamento con "occultamento delle informazioni"
(questa non è una critica, poiché sembra corrispondere a un'interpretazione comune della nozione di "incapsulamento")

Tuttavia per me, "incapsulamento" è:

  • il processo di raggruppamento di diversi elementi in un contenitore
  • il contenitore stesso che raggruppa gli articoli

Supponiamo che tu stia progettando un sistema di contribuenti.Per ogni contribuente, potresti incapsulare la nozione di bambino in

  • un elenco di bambini che rappresentano i bambini
  • una mappa che tiene conto dei bambini provenienti da genitori diversi
  • un oggetto Children (not Child) che fornirebbe le informazioni necessarie (come il numero totale di bambini)

Qui hai tre diversi tipi di incapsulamenti, 2 rappresentati da un contenitore di basso livello (elenco o mappa), uno rappresentato da un oggetto.

Prendendo quelle decisioni, non lo fai

  • rendere l'incapsulamento pubblico, protetto o privato:la scelta di “nascondere le informazioni” deve ancora essere fatta
  • fare un'astrazione completa (è necessario affinare gli attributi dell'oggetto Children e si può decidere di creare un oggetto Child, che manterrebbe solo le informazioni rilevanti dal punto di vista di un sistema contribuente)
    L'astrazione è il processo di scelta di quali attributi dell'oggetto sono rilevanti per il tuo sistema e quali devono essere completamente ignorati.

Quindi il mio punto è:
Quella domanda potrebbe essere intitolata:
Privato vs.Membri pubblici nella pratica (quanto è importante occultamento delle informazioni?)

Solo i miei 2 centesimi, però.Rispetto perfettamente il fatto che si possa considerare l'incapsulamento come un processo che include la decisione di "nascondere le informazioni".

Tuttavia, cerco sempre di distinguere tra "astrazione" - "incapsulamento" - "occultamento o visibilità delle informazioni".

@VonC

Potresti trovare interessante il "Modello di riferimento dell'elaborazione distribuita aperta" dell'Organizzazione internazionale per la standardizzazione.Definisce:"Incapsulamento:la proprietà che l'informazione contenuta in un oggetto è accessibile solo attraverso interazioni sulle interfacce supportate dall'oggetto."

Ho provato a sostenere che l'occultamento delle informazioni è una parte fondamentale di questa definizione qui:http://www.edmundkirwan.com/encap/s2.html

Saluti,

Ed.

Trovo che molti getter e setter siano a odore di codice che la struttura del programma non è progettata bene.Dovresti guardare il codice che utilizza quei getter e setter e cercare funzionalità che dovrebbero davvero far parte della classe.Nella maggior parte dei casi, i campi di una classe dovrebbero essere dettagli di implementazione privati ​​e solo i metodi di quella classe possono manipolarli.

Avere sia getter che setter equivale a rendere pubblico il campo (quando getter e setter sono banali/generati automaticamente).A volte potrebbe essere meglio dichiarare semplicemente pubblici i campi, in modo che il codice sia più semplice, a meno che non sia necessario il polimorfismo o un framework richieda metodi get/set (e non sia possibile modificare il framework).

Ma ci sono anche casi in cui avere getter e setter è un buon schema.Un esempio:

Quando creo la GUI di un'applicazione, cerco di mantenere il comportamento della GUI in una classe (FooModel) in modo che possa essere facilmente testata e di avere la visualizzazione della GUI in un'altra classe (FooView) che può essere testata solo manualmente.La vista e il modello sono uniti con un semplice codice adesivo;quando l'utente modifica il valore del campo x, chiama la vista setX(String) sul modello, che a sua volta potrebbe sollevare un evento relativo alla modifica di qualche altra parte del modello e la vista otterrà i valori aggiornati dal modello con getter.

In un progetto, esiste un modello GUI che ha 15 getter e setter, di cui solo 3 metodi get sono banali (in modo tale che l'IDE possa generarli).Tutti gli altri contengono alcune funzionalità o espressioni non banali, come le seguenti:

public boolean isEmployeeStatusEnabled() {
    return pinCodeValidation.equals(PinCodeValidation.VALID);
}

public EmployeeStatus getEmployeeStatus() {
    Employee employee;
    if (isEmployeeStatusEnabled()
            && (employee = getSelectedEmployee()) != null) {
        return employee.getStatus();
    }
    return null;
}

public void setEmployeeStatus(EmployeeStatus status) {
    getSelectedEmployee().changeStatusTo(status, getPinCode());
    fireComponentStateChanged();
}

In pratica seguo sempre una sola regola, quella del “non c'è taglia adatta a tutti”.

L'incapsulamento e la sua importanza sono un prodotto del tuo progetto.Quale oggetto accederà alla tua interfaccia, come la utilizzeranno, avrà importanza se hanno diritti di accesso non necessari ai membri?quelle domande e cose simili che devi porti quando lavori sull'implementazione di ogni progetto.

Baso la mia decisione sulla profondità del Codice all'interno di un modulo.Se sto scrivendo un codice interno a un modulo e non si interfaccia con il mondo esterno, non incapsula tanto le cose con private perché influisce sulle prestazioni del mio programmatore (quanto velocemente posso scrivere e riscrivere il mio codice).

Ma per gli oggetti che fungono da interfaccia del modulo con il codice utente, aderisco a rigidi modelli di privacy.

Certamente fa la differenza se scrivi codice interno o codice che deve essere utilizzato da qualcun altro (o anche da te stesso, ma come unità contenuta). Qualsiasi codice che verrà utilizzato esternamente dovrebbe avere un'interfaccia ben definita/documentata che tu vorrò cambiare il meno possibile.

Per il codice interno, a seconda della difficoltà, potresti scoprire che è meno faticoso fare le cose in modo semplice adesso e pagare una piccola penalità in seguito.Ovviamente la legge di Murphy garantirà che il guadagno a breve termine verrà cancellato molte volte nel dover apportare modifiche ad ampio raggio in seguito laddove fosse necessario modificare i componenti interni di una classe che non si è riusciti a incapsulare.

Nello specifico del tuo esempio di utilizzo di una raccolta che vorresti restituire, sembra possibile che l'implementazione di tale raccolta possa cambiare (a differenza delle variabili membro più semplici) aumentando l'utilità dell'incapsulamento.

Detto questo, mi piace il modo in cui Python affronta la cosa.Le variabili membro sono pubbliche per impostazione predefinita.Se desideri nasconderli o aggiungere convalida, vengono fornite delle tecniche, ma questi sono considerati casi speciali.

Seguo le regole su questo quasi sempre.Per me ci sono quattro scenari: fondamentalmente, la regola stessa e diverse eccezioni (tutte influenzate da Java):

  1. Utilizzabile da qualsiasi cosa al di fuori della classe corrente, accessibile tramite getter/setter
  2. Utilizzo interno alla classe in genere preceduto da "this" per chiarire che non è un parametro del metodo
  3. Qualcosa destinato a rimanere estremamente piccolo, come un oggetto da trasporto - fondamentalmente una sequenza diretta di attributi;tutto pubblico
  4. Era necessario che non fosse privato per un'estensione di qualche tipo

C'è una preoccupazione pratica qui che non viene affrontata dalla maggior parte delle risposte esistenti.L'incapsulamento e l'esposizione di interfacce pulite e sicure al codice esterno sono sempre ottimi, ma è molto più importante quando il codice che stai scrivendo è destinato a essere utilizzato da una base di "utenti" spazialmente e/o temporalmente ampia.Ciò che intendo è che se prevedi che qualcuno (anche tu) mantenga il codice anche in futuro, o se stai scrivendo un modulo che si interfaccerà con il codice di più di una manciata di altri sviluppatori, devi pensare molto di più con attenzione rispetto a quando stai scrivendo un codice una tantum o interamente scritto da te.

Onestamente, so che miserabile pratica di ingegneria del software sia questa, ma spesso all'inizio renderò tutto pubblico, il che rende le cose leggermente più veloci da ricordare e digitare, quindi aggiungerò l'incapsulamento quando ha senso.Gli strumenti di refactoring negli IDE più popolari al giorno d'oggi determinano l'approccio da utilizzare (aggiunta di incapsulamento vs.portandolo via) molto meno rilevante di prima.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top