L'incapsulamento può essere implementato da tipi appropriati piuttosto che da accessor?

3

La domanda interessante è venuta fuori durante la progettazione di interfacce sul lavoro, ora risolta, ma voglio chiedere la teoria alla base.

È errato affermare che i membri dati digitati in modo corretto di una classe forniscano l'incapsulamento? (ad esempio un tipo di unità boost con conversioni ben definite tra altre unità simili, non un typedef'd / boxed uint64_t)

struct Ruler
{
    Length length;
    Length tick_size;   

    Ruler(Length length, Length tick_size);
    // Why not have helpers immediately related to the class, let's
    // stick in an alternative constructor
    Ruler(Length length, int number_ticks); 

    // Accessors for # tick marks, because it needs transformed 
    // to/from length and tick_size
    int GetNumberTicks(void); 
    // This specific example breaks down with this function, 
    // but I don't think it's an inherent issue with the design.
    // I need an overload so it will know which of the two member 
    // variables to calculate... problem is both are Length
    void SetNumberTicks(int nticks); 
}

vs

struct Ruler
{        
    Ruler(Length length, Length tick_size);
    Ruler(Length length, int number_ticks); 

    // General accessors
    Length GetLength(void);
    void SetLength(Length length);

    Length GetTickSize(void);
    void SetTickSize(Length length);

    // Calculated accessors, see above
    int GetNumberTicks(void); 
    void SetNumberTicks(int nticks); 

private:
    Length length;
    Length tick_size;   
}

IMO il primo incoraggia un design più pulito da parte dei consumatori e non incoraggia l'estrazione in termini leggermente correlati, ma probabilmente associati alle funzioni membro membro errate (ad es. CanMachineCreateRuler ()). Non ho davvero le parole per descriverlo correttamente e potrei fraintendere l'utilità aggiuntiva fornita dalla combinazione di dati accessors / private.

    
posta Landon 26.09.2015 - 02:48
fonte

5 risposte

2

Gli accessor per i tipi di dati di base provengono generalmente dagli operatori aggiuntivi disponibili per questi tipi. Mentre ottenere e impostare le variabili può essere consentito, mantenere spesso riferimenti a tali variabili è spesso scoraggiato.

Alcune lingue necessitano anche di accessor quando il programma incapsulato vuole apportare modifiche nel momento in cui viene utilizzato un accessor, ad es., calcolare un valore lazy o invalidare una cache.

Gran parte di questo può essere risolto creando tipi generali di accesso, come "un int, che è stato inizializzato, per il quale non è possibile ottenere alcun indirizzo, che non è un NaN, è firmato e, quando l'overflow prende il più grande valore assoluto consentito. " o "un int, che è stato inizializzato, che è un significato arbitrario, non sono consentite operazioni aritmetiche, per le quali è possibile fare riferimento". La scelta è spesso quella di scambiare il dolore dei tipi specializzati contro il dolore dell'iniezione di dipendenza.

    
risposta data 26.09.2015 - 19:52
fonte
3

Il secondo è completamente inutile: i setter non fanno altro che consentire all'utente di fare ciò che potrebbe già fare, cioè mutare l'istanza. Può semplicemente assegnargli un nuovo righello in qualsiasi momento.

Inoltre, i getter calcolati non significano veramente molto come membri, dovrebbero essere veramente funzioni libere.

Fondamentalmente, il righello non ha uno stato nascosto, quindi è solo un aiuto e il design non ha molta importanza - l'interfaccia dovrebbe essere il più semplice e pulita possibile, quindi preferirei il primo.

    
risposta data 26.09.2015 - 20:34
fonte
0

Sì, se le variabili dei membri pubblici sono indipendenti l'una dall'altra. Rompe una convenzione uniforme sull'utilizzo di accessor in altre classi, se giustificato, tuttavia, che è probabilmente una ragione sufficiente per attaccare con gli accessor per quanto riguarda lo stile del codice.

    
risposta data 26.09.2015 - 03:00
fonte
0

I don't really have the words to describe this properly and I may be misunderstanding the additional utility that the accessors/private data combination provide.

Diciamo che le dimensioni delle zecche non dovrebbero mai essere zero, poiché GetNumberTicks avrebbe un problema di divisione per zero.

Ora, possiamo generalizzare questo per dire che Length non dovrebbe mai essere zero, permettendoci di eseguire il controllo all'interno di Length stesso, oppure è specifico per il contesto di Ruler ? La risposta dovrebbe essere ovvia.

Questo è uno dei motivi per cui gli accessor sono spesso necessari anche se disponi di tipi di dati ricchi che non espongono lo stato non elaborato, ad es. Hanno bisogno di informazioni contestuali, specifiche del sito ( Ruler - informazioni specifiche qui). Spesso hanno bisogno di considerare gli invarianti gestiti dalla classe esterna ( Ruler ), e forse anche l'interazione di più membri di dati (es .: "questo Length non dovrebbe mai essere più grande di questo altro Length in questo classe ").

Un singolo campo di tipo scalare spesso manca di informazioni sufficienti per fornire gli accessori / mutatori più significativi. Spesso abbiamo bisogno delle informazioni contestuali della classe che aggregano questi campi.

Un altro motivo è questo: diciamo che trovi un nuovo modo di rappresentare Length che è metà della dimensione (rendendo Ruler più cache-friendly senza perdita di informazioni necessarie) ma hai 10.000 punti nel tuo codice che dipende da quel GetLength accessor. Il nuovo tipo è più efficiente, ma può essere facilmente convertito in / da Length su richiesta. In questo caso, per fortuna puoi applicare più facilmente questo sotto l'ottimizzazione della cappa ai campi privati di Ruler senza riscrivere 10.000 righe di codice a causa di GetLength/SetLength . Se hai esposto pubblicamente i campi Length , dovresti riscrivere tutte le 10.000 righe di codice per apportare questa modifica.

Ora sono personalmente più un implementatore in stile C e un designer di interfacce in stile C ++, quindi per qualcosa di simile, mi capita spesso di farlo (attenzione: non raccomandato per la maggior parte):

struct Ruler
{
    Length length;
    Length tick_size;   
};

... è come se fosse, non mi interessa la massima sicurezza qui poiché probabilmente userò questa cosa in un contesto esterno molto più complesso che può imporre i controlli di sicurezza. Quindi eseguirò molti test sui luoghi in cui viene utilizzato Ruler . Questo a meno che Ruler fosse più di un dettaglio di implementazione localizzato e avesse un gran numero di accoppiamenti efferenti (nel qual caso potrei fare i campi privati in piena regola e il design stile getter / setter con almeno delle asserzioni di sicurezza da verificare per essere sicuri tick_size non è zero sia nel costruttore che nel setter).

Ma questo è il motivo per cui gli accessor sono importanti in un aggregato esterno come Ruler anche se i suoi singoli campi forniscono anche degli accessor.

Inoltre, solo una piccola FYI, ma le tue funzioni membro Get* potrebbero trarre vantaggio da alcune cost-correttezza.

    
risposta data 02.01.2016 - 11:22
fonte
0

Troppi accessor sono un odore di codice. Possono significare una di queste due cose:

  1. Modellazione dei dati anziché del comportamento: ci sono casi in cui questo è adeguato. Tuttavia, credo che dovresti essere onesto a riguardo quando modellerai i dati: usa solo membri pubblici in questo caso.

  2. Fallimento dell'abstract: idealmente ogni funzione membro dovrebbe effettivamente fare qualcosa di prezioso. Dovrebbe astrarre dai dettagli come viene implementata la classe (quali membri dei dati utilizza) e fornire alcune funzionalità di livello superiore che sono effettivamente richieste dall'utente.

    Fornire solo semplici accessori lascia tutta la logica che funziona sui suoi dati nel codice chiamante. Questa è una violazione del principio di incapsulamento in quanto membri di dati pubblici.

I semplici getter possono essere una buona idea, perché forniscono un accesso di sola lettura al loro membro dei dati. Ma una volta che hai sia un setter banale che un getter banale, sai che qualcosa non va.

    
risposta data 02.01.2016 - 11:46
fonte

Leggi altre domande sui tag