"Approccio predefinito" quando si crea una classe da zero: getter per tutto o accesso limitato?

1

Fino a poco tempo fa avevo sempre getter (e talvolta setter ma non sempre) per tutti i campi della mia classe. Era il mio 'default': molto automatico e non ne ho mai dubitato. Tuttavia recentemente alcune discussioni su questo sito mi hanno fatto capire che forse non è l'approccio migliore.

Quando crei una classe, spesso non sai esattamente come sarà utilizzata in futuro da altre classi.

Quindi in questo senso, è positivo avere getter e setter per tutti i campi della classe. Quindi altre classi potrebbero usarlo in futuro come vogliono. Consentire questa flessibilità non richiede di sovrascrivere nulla, ma solo di fornire getter.

Tuttavia, alcuni direbbero che è meglio limitare l'accesso a una classe e consentire solo l'accesso a determinati campi, mentre altri campi rimangono completamente privati.

Qual è il tuo approccio 'predefinito' quando crei una classe da zero? Realizzi getter per tutti i campi? Oppure scegli sempre in modo selettivo quali campi esporre tramite un getter e quali mantenere completamente privati?

    
posta Aviv Cohn 30.05.2014 - 01:29
fonte

5 risposte

8

Questo dipende molto dallo scopo della classe. Se si tratta di una classe "struct" che raggruppa solo i dati, allora dovrebbe esporre tutti i suoi campi poiché li mantiene solo, non li usa. Le classi che hanno un comportamento reale dovrebbero in genere essere più restrittive nel fornire l'accesso al loro stato interiore.

Esponendo sempre la struttura interna "Così le altre classi potrebbero usarlo in futuro in qualsiasi modo vogliano" viola il vero nucleo di OOP. La classe dovrebbe essere quella che definisce come utilizzare la sua struttura interna e fornire un'interfaccia (metodi) per farlo.

Prendiamo ad esempio un array C ++ con lunghezza (non ho scritto C ++ da molto tempo, quindi perdonare eventuali errori di sintassi):

class Array{
    private:
        int* array;
        size_t length;
    public:
        Array(size_t length):length(length){
            array=new int[length];
        }

        size_t getLength(){
            return length;
        }
        void setLength(size_t length){
            this.length=length;
        }

        int* getArray(){
            return array;
        }
        void setArray(int* array){
            this.array=array;
        }
}

Questa classe è talmente errata che questa risposta potrebbe essere contrassegnata come offensiva. Espone lo stato interno - array e length - così le altre classi saranno in grado di usare la mia classe come vogliono. Ad esempio, se un codice da qualche altra parte desidera impostare length in modo che sia diverso dalla quantità di memoria (volte sizeof(int) ) allocata a array , sarà in grado di farlo!

La mia classe non dovrebbe permettere agli altri di giocare liberamente con il suo stato interno. Invece, dovrebbe fornire un'interfaccia per modificare lo stato in un modo che lo mantenga stabile .

    
risposta data 30.05.2014 - 03:59
fonte
4

Esporre proprietà e metodi che faranno parte dell'API pubblica della classe o che soddisfano un contratto di interfaccia.

Dovresti pensare all'inizio dell'API della tua classe, non in base a ciò che intendi esporre pubblicamente, ma a quello che la tua classe fornirà.

    
risposta data 30.05.2014 - 02:00
fonte
1

Tendo a seguire la risposta di Robert Harvey (concentrati sulla funzionalità della classe, non sui bit da fare che), ma c'è ancora dell'altro.

È raro che tu abbia mai creato una classe una da zero. Spesso fornisci alcune funzionalità e una decisione chiave da prendere è dove disegnare le linee per rompere quel set di funzionalità in blocchi di dimensioni di classe. Qual è un pezzo di classe? Una singola responsabilità .

Personalmente, trovo che sia efficace disegnare quelle linee di separazione in modo che le singole classi forniscano una buona API, ma differiscono anche per quanto sono mutabili . Tanto più che i programmi diventano più simultanei e più complessi, gli oggetti immutabili consentono di rendere l'implementazione più semplice, robusta e priva di bug.

Quindi, in riferimento alla domanda, "per impostazione predefinita" mirano a rendere le mie classi immutabili quando possibile. Prendono ciò che hanno bisogno di nel costruttore ed espongono la funzionalità che è quella responsabilità della classe. Dopodiché, mirano a rendere gli oggetti mutabili con un costruttore predefinito e uno stato iniziale sensato.

Una volta che sono su oggetti mutabili dipende da cosa fa l'oggetto. Alcuni oggetti sono un insieme di funzionalità in cui l'utente può manipolare uno stato , ma non necessariamente dati . Pensa alla progressione di un gioco di poker. Il gioco del poker qui è mutevole perché il suo stato può progredire. Per questi oggetti, "per impostazione predefinita" andrò con l'incapsulamento convenzionale. Lo stato (chi ha quali carte, chi è il mazziere, qual è il piatto) viene nascosto e viene esposta la funzionalità (deal cards, bet, raise, etc.).

Alcuni oggetti sono solo dati. Pensa a un contatto nella tua app di posta elettronica. Qui, lo scopo dell'oggetto è solo raggruppare il nome, l'indirizzo e-mail, il numero di telefono, ecc. In modo che l'utente possa modificarli. Avere quei dati pubblicamente mutabili è responsabilità dell'oggetto, quindi rendi pubbliche le cose. Le lingue con proprietà lo rendono più semplice, ma ci dovrebbero essere poche funzionalità in questi tipi di classi (di nuovo, "per impostazione predefinita").

    
risposta data 30.05.2014 - 03:54
fonte
0

A meno che la tua classe sia Plain Old Data, inizia definendo l'interfaccia che i suoi clienti vedranno. Questo è un PITA in C ++, non così male in Java. Programma per l'interfaccia; nascondi la classe - se necessario - dietro un oggetto di fabbrica.

In questo modo è possibile distinguere tra le modifiche apportate alla classe che non influiscono sui client (questi modificano la classe ma non l'interfaccia) e quelli che influiscono sui client (questi modificano l'interfaccia).

Un linker intelligente (come Component Pascal / Black Box posseduto) dovrebbe consentire di evitare la ricostruzione dei client nel primo caso. Non ho idea se Java lo fa. Puoi farlo in C ++, ma devi stare attento a come sistemare e utilizzare i file di intestazione.

    
risposta data 30.05.2014 - 17:39
fonte
0

What is your 'default' approach when building a class from scratch? Do you make getters for all the fields? Or do you always choose selectively which fields to expose through a getter and which to keep completely private?

I getter e setter sono un segno di un problema più profondo, cioè che il programmatore è focalizzato sui dati che la classe detiene, non sul comportamento che incapsula.

L'approccio "predefinito" dovrebbe essere quello di non pensare affatto ai dati interni dell'oggetto in primo luogo. Se non si avvia definendo le variabili interne, non ci sono variabili che si preoccupano di esporre in attraverso getter o setter.

Pensa al posto del comportamento che l'oggetto sta tentando di rappresentare nel modello. Esporre questo comportamento attraverso metodi con nomi significativi. Solo allora pensa a come l'oggetto porterà questo comportamento e se ha bisogno di variabili private per farlo (potrebbe non farlo)

I miei passi predefiniti -

  1. Pensa a ciò che nel dominio aziendale questo oggetto rappresenta e definire una classe.

  2. Pensa al primo comportamento di cui questo oggetto è responsabile, e definisci un metodo che espone questo comportamento al mondo esterno.

  3. Scopri come eseguire questo comportamento, quali variabili interne hai bisogno (se ce ne sono).

  4. Finisci la classe (niente di sbagliato con un oggetto con uno solo metodo) o se ci sono comportamenti strettamente correlati ne definiscono un altro metodo pubblico.

  5. Refactor se necessario (potresti scoprire che la classe se cresce anche tu grande, quindi lo hai diviso).

99,9% del tempo dopo averlo fatto non avrai più variabili interne da considerare nemmeno per un getter o setter.

    
risposta data 30.05.2014 - 19:43
fonte

Leggi altre domande sui tag