Perché i campi privati ​​sono privati ​​del tipo, non dell'istanza?


153

In C # (e in molte altre lingue) è perfettamente legittimo accedere a campi privati ​​di altre istanze dello stesso tipo. Per esempio:

public class Foo
{
    private bool aBool;

    public void DoBar(Foo anotherFoo)
    {
        if (anotherFoo.aBool) ...
    }
}

Come la specifica C # (sezioni 3.5.1, 3.5.2) afferma che l'accesso ai campi privati ​​è su un tipo, non su un'istanza. Ne ho discusso con un collega e stiamo cercando di trovare un motivo per cui funziona così (piuttosto che limitare l'accesso alla stessa istanza).

L'argomento migliore che potremmo trovare è per i controlli di uguaglianza in cui la classe potrebbe voler accedere ai campi privati ​​per determinare l'uguaglianza con un'altra istanza. Ci sono altri motivi? O qualche motivo d'oro che significa assolutamente che deve funzionare in questo modo o qualcosa sarebbe completamente impossibile?


18
Quali sono i vantaggi per l'alternativa?
Jon Skeet,

19
È vero che non modella molto bene il mondo reale. Dopotutto, solo perché la mia auto può segnalare la quantità di gas rimasta non significa che la mia auto dovrebbe essere in grado di dire la quantità di gas che OGNI macchina ha lasciato. Quindi, sì, posso teoricamente vedere un accesso a "istanza privata". Sento che non lo userei mai, però, perché sarei preoccupato che nel 99% dei casi, Vorrei davvero avere un'altra istanza per accedere a quella variabile.
aquinas

2
@Jon La posizione assunta per i cosiddetti "vantaggi" è che le classi non dovrebbero conoscere lo stato interno di un'altra istanza, indipendentemente dal fatto che sia dello stesso tipo. Non è un vantaggio a dirsi, ma probabilmente c'è una ragione abbastanza buona per sceglierne uno rispetto all'altro ed è quello che abbiamo cercato di capire
RichK,

4
Puoi sempre trasformare la variabile in una coppia di chiusure getter / setter, inizializzate nel costruttore, che falliranno se questa non è la stessa che era durante l'inizializzazione ...
Martin Sojka

8
Scala fornisce membri privati ​​di istanza , insieme a molti altri livelli di accesso non disponibili in Java, C # e molte altre lingue. Questa è una domanda perfettamente legittima.
Bruno Reis,

Risposte:


73

Penso che uno dei motivi per cui funziona in questo modo sia perché i modificatori di accesso funzionano in fase di compilazione . Pertanto, determinare se un determinato oggetto è anche l' oggetto corrente non è facile da fare. Ad esempio, considera questo codice:

public class Foo
{
    private int bar;

    public void Baz(Foo other)
    {
        other.bar = 2;
    }

    public void Boo()
    {
        Baz(this);
    }
}

Il compilatore può necessariamente capire che otherè effettivamente this? Non in tutti i casi Si potrebbe sostenere che questo non dovrebbe essere compilato, ma ciò significa che abbiamo un percorso di codice in cui un membro dell'istanza privata dell'istanza corretta non è accessibile, il che penso sia anche peggio.

Richiedono solo tipo di livello piuttosto che a livello di oggetto assicura visibilità che il problema è trattabile, oltre a rendere una situazione che sembra che dovrebbe funzionare effettivamente lavoro.

EDIT : il punto di Danilel Hilgarth secondo cui questo ragionamento è arretrato ha valore. I progettisti di lingue possono creare la lingua che desiderano e gli autori di compilatori devono conformarsi ad essa. Detto questo, i progettisti di lingue hanno qualche incentivo a rendere più semplice per gli scrittori di compilatori fare il loro lavoro. (Anche se in questo caso, è abbastanza facile sostenere che i membri privati ​​possano essere accessibili solo tramite this(implicitamente o esplicitamente).

Tuttavia, credo che ciò renda il problema più confuso di quanto non lo sia. La maggior parte degli utenti (me compreso) troverebbe inutilmente limitante se il codice sopra non funzionasse: dopo tutto, sono i miei dati a cui sto tentando di accedere! Perché dovrei passare attraverso this?

In breve, penso che potrei aver esagerato nel caso in cui fosse "difficile" per il compilatore. Quello che volevo davvero dire è che la situazione sopra sembra essere quella su cui i designer vorrebbero lavorare.


33
IMHO, questo ragionamento è il modo sbagliato. Il compilatore applica le regole della lingua. Non li rende. In altre parole, se i membri privati ​​fossero "istanza privata" e non "tipo privato", il compilatore sarebbe stato implementato in altri modi, ad esempio solo consentendo this.bar = 2ma non other.bar = 2, perché otherpotrebbe essere un'istanza diversa.
Daniel Hilgarth,

@Daniel Questo è un buon punto, ma come ho già detto, penso che avere quella restrizione sarebbe peggio che avere visibilità a livello di classe.
dlev,

3
@dlev: Non ho detto nulla riguardo al fatto se penso che i membri "di istanza privati" siano o meno una buona cosa. :-) Volevo solo sottolineare che stai sostenendo che un'implementazione tecnica impone le regole del linguaggio e che secondo me questa argomentazione non è corretta.
Daniel Hilgarth,

2
@Daniel Punto preso. Continuo a pensare che questa sia una considerazione valida, anche se ovviamente hai ragione sul fatto che alla fine i progettisti del linguaggio capiscono quello che vogliono, e gli autori di compilatori si conformano a quello.
dlev,

2
Non credo che l'argomento del compilatore valga. Esistono lingue che consentono solo l'istanza privata (come Ruby) e che consentono l'istanza privata e la classe privata per scelta (come Eiffel). La generazione del compilatore non era necessaria più difficile o più facile per queste lingue. Per maggiori informazioni, vedi anche la mia risposta nel thread.
Abele,

61

Perché lo scopo del tipo di incapsulamento utilizzato in C # e in linguaggi simili * è di ridurre la dipendenza reciproca di diversi pezzi di codice (classi in C # e Java), non di oggetti diversi nella memoria.

Ad esempio, se si scrive codice in una classe che utilizza alcuni campi in un'altra classe, queste classi sono molto strettamente accoppiate. Tuttavia, se hai a che fare con un codice in cui hai due oggetti della stessa classe, allora non c'è dipendenza aggiuntiva. Una classe dipende sempre da se stessa.

Tuttavia, tutta questa teoria sull'incapsulamento fallisce non appena qualcuno crea proprietà (o ottiene / imposta coppie in Java) ed espone direttamente tutti i campi, il che rende le classi accoppiate come se stessero accedendo comunque ai campi.

* Per chiarimenti sui tipi di incapsulamento, vedere la risposta eccellente di Abele.


3
Non credo che fallisca una volta get/setforniti i metodi. I metodi di accesso mantengono ancora un'astrazione (l'utente non ha bisogno di sapere che c'è un campo dietro di esso, o quali campi potrebbero essere dietro la proprietà). Ad esempio, se stiamo scrivendo una Complexclasse, possiamo esporre le proprietà per ottenere / impostare le coordinate polari e un'altra ottenere / impostare per le coordinate cartesiane. Quello che la classe usa sotto è sconosciuto all'utente (potrebbe anche essere qualcosa di completamente diverso).
Ken Wayne VanderLinde,

5
@Ken: fallisce se fornisci una proprietà per ogni singolo campo che hai senza nemmeno considerare se dovrebbe essere esposta.
Goran Jovic,

@Goran Anche se concordo sul fatto che non sarebbe l'ideale, continua a non fallire completamente poiché gli accessi get / set consentono di aggiungere ulteriore logica se, ad esempio, i campi sottostanti cambiano.
Forbes

1
"Perché lo scopo dell'incapsulamento è di ridurre la dipendenza reciproca di diversi pezzi di codice" >> no. Incapsulamento può anche significare incapsulamento di istanze, dipende dalla lingua o dalla scuola di pensiero. Una delle voci più definitive su OO, Bertrand Meyer, considera questo anche il valore predefinito private. Puoi controllare la mia risposta per il mio punto di vista sulle cose se vuoi;).
Abele,

@Abel: ho basato la mia risposta su linguaggi con incapsulamento di classe perché OP mi ha chiesto di uno di essi, e francamente perché li conosco. Grazie per la correzione e per le nuove informazioni interessanti!
Goran Jovic,

49

Molte risposte sono già state aggiunte a questo interessante thread, tuttavia, non ho ancora trovato la vera ragione per cui questo comportamento è così. Lasciami provare:

Tempo fa

A metà tra Smalltalk negli anni '80 e Java a metà degli anni '90, il concetto di orientamento agli oggetti è maturato. Nascondere le informazioni, originariamente non considerato un concetto disponibile solo per OO (menzionato per primo nel 1978), è stato introdotto in Smalltalk poiché tutti i dati (campi) di una classe sono privati, tutti i metodi sono pubblici. Durante i numerosi nuovi sviluppi di OO negli anni '90, Bertrand Meyer ha cercato di formalizzare gran parte dei concetti di OO nel suo libro di riferimento Object Oriented Software Construction (OOSC) che da allora è stato considerato un riferimento (quasi) definitivo su concetti di OO e design del linguaggio .

In caso di visibilità privata

Secondo Meyer, un metodo dovrebbe essere reso disponibile per un insieme definito di classi (pagina 192-193). Ciò offre ovviamente una granularità molto elevata di occultamento delle informazioni, la seguente funzione è disponibile per la classe A e la classe B e tutti i loro discendenti:

feature {classA, classB}
   methodName

Nel caso di privatelui dice quanto segue: senza dichiarare esplicitamente un tipo come visibile alla propria classe, non è possibile accedere a tale funzione (metodo / campo) in una chiamata qualificata. Vale a dire se xè una variabile, x.doSomething()non è consentito. L'accesso non qualificato è consentito, ovviamente, all'interno della classe stessa.

In altre parole: per consentire l'accesso da un'istanza della stessa classe, è necessario consentire esplicitamente al metodo l'accesso da quella classe. Questo a volte viene chiamato istanza-privato contro classe-privato.

Istanza privata nei linguaggi di programmazione

Conosco almeno due lingue attualmente in uso che utilizzano il nascondimento di informazioni private dell'istanza anziché il nascondiglio di informazioni private di classe. Uno è Eiffel, un linguaggio progettato da Meyer, che porta OO al massimo. L'altro è Ruby, un linguaggio molto più comune al giorno d'oggi. In Ruby privatesignifica: "privato per questa istanza" .

Scelte per il design della lingua

È stato suggerito che consentire l'istanza-privato sarebbe difficile per il compilatore. Non credo, dato che è relativamente semplice consentire o vietare chiamate qualificate a metodi. Se per un metodo privato, doSomething()è consentito ex.doSomething() non lo è, un designer linguistico ha effettivamente definito l'accessibilità solo istanza per metodi e campi privati.

Da un punto di vista tecnico, non c'è motivo di scegliere in un modo o nell'altro (specialmente se si considera che Eiffel.NET può farlo con IL, anche con eredità multipla, non c'è motivo intrinseco per non fornire questa funzione).

Certo, è una questione di gusti e, come già menzionato da altri, alcuni metodi potrebbero essere più difficili da scrivere senza la funzionalità di visibilità a livello di classe di metodi e campi privati.

Perché C # consente solo l'incapsulamento di classe e non l'incapsulamento di istanze

Se si guardano i thread di Internet sull'incapsulamento dell'istanza (un termine talvolta usato per indicare il fatto che una lingua definisce i modificatori di accesso a livello di istanza, a differenza del livello di classe), il concetto è spesso disapprovato. Tuttavia, considerando che alcuni linguaggi moderni usano l'incapsulamento di istanze, almeno per il modificatore di accesso privato, ti fa pensare che possa essere ed è utile nel moderno mondo della programmazione.

Tuttavia, C # ha certamente guardato più attentamente a C ++ e Java per la sua progettazione del linguaggio. Mentre Eiffel e Modula-3 erano anche nella foto, considerando le molte caratteristiche di Eiffel mancanti (eredità multipla) credo che abbiano scelto la stessa rotta di Java e C ++ quando si è trattato del modificatore di accesso privato.

Se vuoi davvero sapere il motivo per cui dovresti provare a trovare Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg o chiunque abbia lavorato sullo standard di C #. Sfortunatamente, non sono riuscito a trovare una nota definitiva nel linguaggio di programmazione annotato C # .


1
Questa è una bella risposta con un sacco di retroscena, molto interessante, grazie per aver
dedicato

1
@RichK: prego, ho colto la domanda troppo tardi, immagino, ma ho trovato l'argomento abbastanza intrigante da rispondere con una certa profondità :)
Abel

Sotto COM, "privato" significa "istanza-privato". Penso che ciò fosse in qualche misura perché una definizione di classe Foorappresentava effettivamente un'interfaccia e una classe di implementazione; poiché COM non aveva un concetto di riferimento a un oggetto classe - solo un riferimento all'interfaccia - non era possibile che una classe potesse contenere un riferimento a qualcosa che era garantito essere un'altra istanza di quella classe. Questo design basato sull'interfaccia rendeva alcune cose ingombranti, ma significava che si poteva progettare una classe che era sostituibile con un'altra senza che fosse necessario condividere gli stessi interni.
supercat

2
Bella risposta. Il PO dovrebbe considerare la risposta a questa domanda.
Gavin Osborn,

18

Questa è solo la mia opinione, ma pragmaticamente, penso che se un programmatore ha accesso all'origine di una classe, puoi ragionevolmente fidarti di lui accedendo ai membri privati ​​dell'istanza della classe. Perché legare la mano destra di un programmatore quando alla sua sinistra hai già dato loro le chiavi del regno?


13
Non capisco questo argomento. Supponiamo che tu stia lavorando a un programma in cui sei l'unico sviluppatore e sei il solo a utilizzare MAI le tue librerie, in questo caso stai dicendo che rendi OGNI membro pubblico perché hai accesso al codice sorgente?
aquinas

@aquinas: è abbastanza diverso. Rendere tutto pubblico porterà a pratiche di programmazione orribili. Consentire a un tipo di vedere campi privati ​​di altre istanze di se stesso è un caso molto ristretto.
Igby Largeman,

2
No, non sto sostenendo di rendere pubblico ogni membro. Cieli no! La mia tesi è che, se sei già nella fonte, puoi vedere i membri privati, come vengono utilizzati, le convenzioni utilizzate, ecc., Allora perché non essere in grado di usare quella conoscenza?
FishBasketGordo

7
Penso che il modo di pensare sia ancora nei termini del tipo. Se il tipo non può essere considerato attendibile per gestire correttamente i membri del tipo, cosa può? ( Normalmente , sarebbe un programmatore che controlla effettivamente le interazioni, ma in questa epoca di strumenti per la generazione di codice, questo potrebbe non essere sempre il caso.)
Anthony Pegram

3
La mia domanda non è davvero di fiducia, più di necessità. La fiducia è totalmente irrilevante quando puoi usare la riflessione sulle cose atroci per i privati. Mi chiedo perché, concettualmente, puoi accedere ai privati. Ho pensato che ci fosse una ragione logica piuttosto che una situazione di buone pratiche.
RichK,

13

Il motivo è in effetti il ​​controllo dell'uguaglianza, il confronto, la clonazione, il sovraccarico dell'operatore ... Sarebbe molto complicato implementare l'operatore + su numeri complessi, ad esempio.


3
Ma tutto ciò che dici richiede un tipo PER OTTENERE il valore di un altro tipo. Sono d'accordo con il dire: vuoi ispezionare il mio stato privato? Bene, vai avanti. Vuoi impostare il mio stato privato? Niente da fare, amico, cambia il tuo maledetto stato. :)
aquinas

2
@aquinas Non funziona in questo modo. I campi sono campi. Sono di sola lettura se dichiarati come readonlye quindi vale anche per l'istanza dichiarante. Evidentemente stai affermando che dovrebbe esserci una specifica regola del compilatore per proibirlo, ma ogni regola del genere è una caratteristica che il team di C # deve specificare, documentare, localizzare, testare, mantenere e supportare. Se non ci sono benefici convincenti, allora perché dovrebbero farlo?
Aaronaught l'

Sono d'accordo con @Aaronaught: c'è un costo associato all'implementazione di ogni nuova specifica e modifica al compilatore. Per uno scenario definito, prendere in considerazione un metodo Clone. Certo, potresti volerlo realizzare con un costruttore privato, ma forse in alcuni scenari non è possibile / consigliabile.
Lorenzo Dematté,

1
Squadra C #? Sto solo discutendo di possibili cambiamenti teorici della lingua in QUALSIASI lingua. "Se non ci sono benefici convincenti ..." Penso che potrebbe esserci un vantaggio. Proprio come con qualsiasi garanzia del compilatore, qualcuno potrebbe trovare utile garantire che una classe modifichi solo il proprio stato.
aquinas,

@aquinas: le funzionalità vengono implementate perché sono utili a molti o alla maggior parte degli utenti, non perché potrebbero essere utili in alcuni casi isolati.
Aaronaught,

9

Prima di tutto, cosa accadrebbe ai membri statici privati? È possibile accedervi solo con metodi statici? Certamente non lo vorrai, perché in questo modo non sarai in grado di accedere ai tuoi messaggi const.

Per quanto riguarda la tua domanda esplicita, considera il caso di a StringBuilder, che è implementato come un elenco collegato di istanze di se stesso:

public class StringBuilder
{
    private string chunk;
    private StringBuilder nextChunk;
}

Se non riesci ad accedere ai membri privati ​​di altre istanze della tua classe, devi implementare in ToStringquesto modo:

public override string ToString()
{
    return chunk + nextChunk.ToString();
}

Funzionerà, ma è O (n ^ 2) - non molto efficiente. In realtà, ciò probabilmente vanifica l'intero scopo di avere una StringBuilderclasse in primo luogo. Se è possibile accedere ai membri privati ​​di altre istanze della propria classe, è possibile implementare ToStringcreando una stringa della lunghezza corretta e quindi eseguendo una copia non sicura di ciascun blocco nella posizione appropriata nella stringa:

public override string ToString()
{
    string ret = string.FastAllocateString(Length);
    StringBuilder next = this;

    unsafe
    {
        fixed (char *dest = ret)
            while (next != null)
            {
                fixed (char *src = next.chunk)
                    string.wstrcpy(dest, src, next.chunk.Length);
                next = next.nextChunk;
            }
    }
    return ret;
}

Questa implementazione è O (n), che la rende molto veloce ed è possibile solo se hai accesso a membri privati ​​di altre istanze della tua classe .


Sì, ma non ci sono altri modi per aggirare questo, come esporre alcuni campi come interni?
MikeKull

2
@Mike: esponendo un campo internalrendendolo meno privato! Finiresti per esporre gli interni della tua classe a tutto ciò che è nella sua assemblea.
Gabe,

3

Questo è perfettamente legittimo in molte lingue (C ++ per uno). I modificatori di accesso derivano dal principio di incapsulamento in OOP. L'idea è di limitare l'accesso all'esterno , in questo caso l'esterno è di altre classi. Qualsiasi classe nidificata in C #, ad esempio, può accedere anche ai membri privati ​​dei genitori.

Mentre questa è una scelta di design per un designer linguistico. La limitazione di questo accesso può complicare estremamente alcuni scenari molto comuni senza contribuire molto all'isolamento delle entità.

C'è una discussione simile qui


1

Non penso che ci sia un motivo per cui non siamo riusciti ad aggiungere un altro livello di privacy, in cui i dati sono privati ​​per ogni istanza. In effetti, ciò potrebbe anche fornire una bella sensazione di completezza alla lingua.

Ma nella pratica reale, dubito che sarebbe davvero utile. Come hai sottolineato, la nostra solita riservatezza è utile per cose come i controlli di uguaglianza, così come la maggior parte delle altre operazioni che coinvolgono più istanze di un Tipo. Tuttavia, mi piace anche il tuo punto sul mantenimento dell'astrazione dei dati, poiché questo è un punto importante in OOP.

Penso a tutti, fornire la possibilità di limitare l'accesso in questo modo potrebbe essere una bella funzionalità da aggiungere a OOP. È davvero così utile? Direi di no, dal momento che una classe dovrebbe essere in grado di fidarsi del proprio codice. Dal momento che quella classe è l'unica cosa che può accedere ai membri privati, non c'è alcun motivo reale per richiedere l'astrazione dei dati quando si ha a che fare con un'istanza di un'altra classe.

Ovviamente, puoi sempre scrivere il tuo codice come se fosse privato applicato alle istanze. Utilizzare i soliti get/setmetodi per accedere / modificare i dati. Ciò renderebbe probabilmente più gestibile il codice se la classe potesse essere soggetta a modifiche interne.


0

Grandi risposte date sopra. Vorrei aggiungere che parte di questo problema è il fatto che l'istanza di una classe al suo interno è persino consentita in primo luogo. Da allora in una logica ricorsiva il ciclo "for", ad esempio, usa quel tipo di trucco purché si abbia la logica per terminare la ricorsione. Ma creare un'istanza o passare la stessa classe dentro di sé senza creare tali loop logicamente crea i suoi pericoli, anche se è un paradigma di programmazione ampiamente accettato. Ad esempio, una classe C # può creare un'istanza di una copia di se stessa nel suo costruttore predefinito, ma ciò non infrange alcuna regola o crea cicli causali. Perché?

A proposito .... questo stesso problema si applica anche ai membri "protetti". :(

Non ho mai accettato completamente questo paradigma di programmazione perché presenta ancora un'intera serie di problemi e rischi che la maggior parte dei programmatori non affronta completamente fino a quando problemi come questo problema sorgono e confondono le persone e sfidano l'intera ragione per avere membri privati.

Questo aspetto "strano e bizzarro" di C # è ancora un altro motivo per cui una buona programmazione non ha nulla a che fare con l'esperienza e l'abilità, ma solo conoscendo i trucchi e le trappole ..... come lavorare su un'auto. È l'argomento secondo cui le regole dovevano essere infrante, il che è un modello molto negativo per qualsiasi linguaggio informatico.


-1

Mi sembra che se i dati fossero privati ​​di altre istanze dello stesso tipo, non sarebbero necessariamente più dello stesso tipo. Non sembrerebbe comportarsi o agire allo stesso modo di altri casi. Il comportamento potrebbe essere facilmente modificato sulla base di tali dati interni privati. Ciò diffonderebbe solo confusione secondo me.

Parlando liberamente, penso personalmente che scrivere classi derivate da una classe base offra funzionalità simili che stai descrivendo con "avere dati privati ​​per istanza". Invece, hai solo una nuova definizione di classe per tipo "unico".

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.