it-swarm.it

Quando sono giustificati Getter e setter

Getter e setter sono spesso criticati come non OO corretto. D'altra parte la maggior parte OO che ho visto ha getter e setter estesi.

Quando sono giustificati getter e setter? Cerchi di evitare di usarli? Sono abusati in generale?

Se la tua lingua preferita ha proprietà (la mia), anche queste cose sono considerate getter e setter per questa domanda. Sono la stessa cosa dal punto di vista della metodologia OO. Hanno solo una sintassi migliore.

Fonti per la critica di Getter/Setter (alcune tratte dai commenti per dare loro una migliore visibilità):

Per esprimere semplicemente le critiche: Getter e setter ti consentono di manipolare lo stato interno degli oggetti dall'esterno dell'oggetto. Questo viola l'incapsulamento. Solo l'oggetto stesso dovrebbe preoccuparsi del suo stato interno.

E un esempio di versione procedurale del codice.

struct Fridge
{
    int cheese;
}

void go_shopping(Fridge fridge)
{
     fridge.cheese += 5;
}

Versione mutatore del codice:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

Getter e setter hanno reso il codice molto più complicato senza offrire l'incapsulamento corretto. Poiché lo stato interno è accessibile ad altri oggetti, non guadagniamo molto aggiungendo questi getter e setter.

La domanda è stata precedentemente discussa su Stack Overflow:

179
Winston Ewert

Avere getter e setter non rompe di per sé l'incapsulamento. Ciò che interrompe l'incapsulamento è l'aggiunta automatica di un getter e un setter per ogni membro di dati (ogni campo, in Java), senza pensarci. meglio che rendere pubblici tutti i membri dei dati, è solo un piccolo passo.

Il punto di incapsulamento non è che non si dovrebbe essere in grado di conoscere o modificare lo stato dell'oggetto dall'esterno dell'oggetto, ma che si dovrebbe avere un ragionevole politica per farlo.

  • Alcuni membri dei dati potrebbero essere interamente interni all'oggetto e non dovrebbero avere né getter né setter.

  • Alcuni membri dei dati dovrebbero essere di sola lettura, quindi potrebbero aver bisogno di getter ma non di setter.

  • Alcuni membri dei dati potrebbero dover essere coerenti tra loro. In tal caso non forniresti un setter per ognuno, ma un singolo metodo per impostarli contemporaneamente, in modo da poter verificare la coerenza dei valori.

  • Alcuni membri dei dati potrebbero dover essere modificati solo in un determinato modo, ad esempio incrementati o decrementati di un importo fisso. In questo caso, forniresti un metodo increment() e/o decrement(), piuttosto che un setter.

  • Tuttavia, altri potrebbero effettivamente aver bisogno di essere letti/scritti e avrebbero sia un getter che un setter.

Prendi in considerazione un esempio di class Person. Supponiamo che una persona abbia un nome, un numero di previdenza sociale e un'età. Diciamo che non permettiamo alle persone di cambiare mai nome o numero di previdenza sociale. Tuttavia, l'età della persona dovrebbe essere aumentata di 1 ogni anno. In questo caso, forniresti un costruttore che inizializzerebbe il nome e l'SSN ai valori indicati e che inizializzerebbe l'età a 0. Forniresti anche un metodo incrementAge(), che aumenterebbe l'età di 1. Forniresti anche getter per tutti e tre. In questo caso non sono richiesti setter.

In questo progetto si consente di ispezionare lo stato dell'oggetto dall'esterno della classe e si consente di modificarlo dall'esterno della classe. Tuttavia, non si consente di modificare arbitrariamente lo stato. Esiste una politica che afferma efficacemente che il nome e il SSN non possono essere cambiati affatto e che l'età può essere aumentata di 1 anno alla volta.

Ora diciamo che una persona ha anche uno stipendio. E le persone possono cambiare lavoro a piacimento, il che significa che anche il loro stipendio cambierà. Per modellare questa situazione non abbiamo altro modo che fornire un metodo setSalary()! Consentire che lo stipendio venga modificato a piacimento è una politica perfettamente ragionevole in questo caso.

A proposito, nel tuo esempio, darei alla classe Fridge i metodi putCheese() e takeCheese(), invece di get_cheese() e set_cheese(). Quindi avresti ancora l'incapsulamento.


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}
163
Dima

Il motivo di base per getter e setter in Java è molto semplice:

  • È possibile specificare metodi, non campi, in un'interfaccia.

Quindi, se vuoi consentire a un campo di passare attraverso l'interfaccia, avrai bisogno di un metodo di lettura e scrittura. Questi sono tradizionalmente chiamati getX e setX per il campo x.

42
user1249

Da http://www.adam-bien.com/roller/abien/entry/encapsulation_violation_with_getters_and

Stile JavaBean:

connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();

OO-style:

connection.connect("dukie","duke");

Bene, chiaramente preferisco il secondo approccio; non elimina i dettagli dell'implementazione, è più semplice e più conciso e tutte le informazioni necessarie sono incluse nella chiamata del metodo, quindi è più facile ottenerlo nel modo giusto. Preferisco anche impostare membri privati ​​usando parametri nel costruttore, quando possibile.

La tua domanda è: quando è giustificato un getter/setter? Forse quando è necessario un cambio di modalità o è necessario interrogare un oggetto per ottenere alcune informazioni.

myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;

Pensandoci, quando ho iniziato a scrivere codice in C #, ho scritto molto codice nello stile giavanese illustrato sopra. Ma quando ho acquisito esperienza nella lingua, ho iniziato a creare più impostazioni dei membri nel costruttore, e usando metodi che assomigliavano più allo stile sopra OO.

20
Robert Harvey

Quando sono giustificati getter e setter?

Quando il comportamento "get" e "set" corrispondono effettivamente al comportamento nel modello, ovvero praticamente mai.

Ogni altro uso è solo un imbroglio perché il comportamento del dominio aziendale non è chiaro.

Edit

Quella risposta potrebbe essere sembrata irriverente, quindi lasciami espandere. Le risposte di cui sopra sono per lo più corrette ma si concentrano sui paradigmi di programmazione di OO design e alcuni ciò che manca al quadro generale. Nella mia esperienza questo porta le persone a pensare che evitare gettings e setter sia una regola accademica per OO linguaggi di programmazione (ad es. La gente pensa che sostituire getter con proprietà sia grandioso)

In realtà è una conseguenza del processo di progettazione. Non devi entrare in interfacce, incapsulamenti e schemi, discutendo se rompa questi paradigmi e cosa è o non va bene OO. L'unico punto che alla fine conta che se nel tuo dominio non c'è nulla che funzioni in questo modo inserendoli, non stai modellando il tuo dominio.

La realtà è che è altamente improbabile che qualsiasi sistema nel tuo spazio di dominio abbia getter e setter. Non puoi avvicinarti all'uomo o alla donna responsabile del libro paga e dire semplicemente "Imposta questo stipendio su X" oppure "Ricevi questo stipendio". Tale comportamento semplicemente non esiste

Se lo stai inserendo nel tuo codice, non stai progettando il tuo sistema in modo che corrisponda al modello del tuo dominio. Sì, questo rompe le interfacce e l'incapsulamento, ma non è questo il punto. Il punto è che stai modellando qualcosa che non esiste.

Più di te probabilmente stai perdendo un passaggio o un processo importante, perché probabilmente c'è un motivo per cui non posso semplicemente camminare per pagare il rotolo e dire di impostare questo stipendio su X.

Quando le persone usano getter e setter tendono a spingere le regole per questo processo nel posto sbagliato. Questo si sta allontanando ancora di più dal tuo dominio. Usando l'esempio del mondo reale, è come uno stipendio supponendo che la persona a caso che è entrata abbia il permesso di ottenere questi valori, altrimenti non li chiederebbe. Non solo non è così come è il dominio, ma in realtà sta mentendo su come è il dominio.

14
Cormac Mulhall

Come regola generale, getter e setter sono una cattiva idea. Se un campo non fa logicamente parte dell'interfaccia e lo rendi privato, va bene. Se fa logicamente parte dell'interfaccia e la rendi pubblica, va bene. Ma se lo rendi privato e poi ti giri e lo rendi di nuovo effettivamente pubblico fornendo un getter e setter, sei di nuovo al punto di partenza, tranne per il fatto che il tuo codice è ora più dettagliato e offuscato.

Ovviamente ci sono delle eccezioni. In Java, potrebbe essere necessario utilizzare le interfacce. La libreria Java standard ha requisiti di compatibilità con le versioni precedenti così estremi da superare le normali misure di qualità del codice. È anche possibile che tu abbia effettivamente a che fare con il leggendario ma raro caso in cui ci sono buone possibilità che tu possa in seguito sostituire un campo memorizzato con il calcolo al volo senza interrompere in altro modo l'interfaccia, ma queste sono eccezioni. Getter e setter sono un anti-schema che richiede una giustificazione speciale.

11
rwallace

se il campo è accessibile direttamente o tramite il metodo non è davvero importante.

Gli invarianti di classe (quelli utili) sono importanti. E per preservarli, a volte abbiamo bisogno di non essere in grado di cambiare qualcosa dall'esterno. Per esempio. se abbiamo classe Square con larghezza e altezza separate, cambiando una di esse diventa qualcosa di diverso da piazza. Quindi abbiamo bisogno del metodo changeSide Se fosse Rectangle, potremmo avere setter/campo pubblico. Ma setter di testerebbe se il suo maggiore di zero sarebbe meglio.

E in linguaggi concreti (es. Java) sono i motivi per cui abbiamo bisogno di quei metodi (interfacce). E un altro motivo del metodo è la compatibilità (sorgente e binaria). Quindi è più facile aggiungerli e poi pensare se il campo pubblico sarebbe sufficiente.

btw. Mi piace usare semplici classi di valore immutabili con campi finali pubblici.

3
user470365

Potresti voler cambiare i tuoi interni in qualunque cosa mantenendo le stesse interfacce. Se le tue interfacce non variano, il codice non si romperà. Puoi comunque cambiare i tuoi interni come desideri.

2
George Silva

Considera una classe Size che incapsula larghezza e altezza. Posso eliminare i setter usando il costruttore ma come può aiutarmi a disegnare un rettangolo con Size? Larghezza e altezza non sono dati interni alla classe; sono dati condivisi che devono essere disponibili per gli utenti di Size.

Gli oggetti sono costituiti da comportamenti e stati - o attributi. Se non esiste uno stato esposto, solo i comportamenti sono pubblici.

Senza stato, come ordineresti una raccolta di oggetti? Come cercheresti un'istanza specifica di un oggetto? Se usi solo costruttori, cosa succede quando il tuo oggetto ha un lungo elenco di attributi?

Nessun parametro di metodo dovrebbe mai essere consumato senza convalida. Pertanto è pigrizia scrivere:

setMyField(int myField){
    this.myField = myField;
}

Se lo scrivi in ​​questo modo, almeno ti sei preparato per usare il setter per la validazione; è meglio di un campo pubblico - ma a malapena. Ma almeno hai un'interfaccia pubblica coerente che puoi tornare e inserire regole di convalida senza infrangere il codice dei tuoi clienti.

Getter, setter, proprietà, mutatori, chiamali come vuoi, ma sono necessari.

0
Dale

Il mio approccio è questo:

Quando mi aspetto di manipolare i dati in seguito, un getter/setter è giustificato. Inoltre, se si verificano cambiamenti, spesso inserisco i dati in un getter/setter.

Se è una struttura POD, lascio gli slot disponibili.

A un livello più astratto, la domanda è "chi gestisce i dati" e questo dipende dal progetto.

0
Paul Nathan

Se getter e setter violano l'incapsulamento e il vero OO, allora sono seriamente nei guai.

Ho sempre pensato che un oggetto rappresentasse qualunque cosa gli venisse in mente che ne hai bisogno per farlo.

Ho appena finito di scrivere un programma che genera Mazes in Java e ho una classe che rappresenta "Maze Squares". Ho dati in questa classe che rappresentano coordinate, muri e booleani ecc.

Devo avere un modo per cambiare/manipolare/accedere a questi dati! Cosa devo fare senza getter e setter? Java non usa le proprietà e impostare tutti i miei dati locali su questa classe come pubblici è Sicuramente una violazione di incapsulamento e OO.

0
Bryan Harrington

Se l'utilizzo di getter e setter sembra complicato, il problema potrebbe essere la lingua, non il concetto stesso.

Ecco il codice dell'esempio secondo scritto in Ruby:

class Fridge
  attr_accessor :cheese
end

def go_shopping fridge
  fridge.cheese += 5
end

Notate che assomiglia molto all'esempio primo in Java? Quando getter e setter vengono trattati come cittadini di prima classe, non sono un compito ingrato da usare e la maggiore flessibilità può talvolta essere un vero vantaggio, ad esempio, potremmo decidere di restituire un valore predefinito per il formaggio su un nuovo frigorifero:

class Fridge
  attr_accessor :cheese

  def cheese
    @cheese || 0
  end
end

Naturalmente ci saranno molte variabili che non dovrebbero essere esposte pubblicamente. Esporre inutilmente le variabili interne peggiorerà il tuo codice, ma difficilmente puoi incolparlo su getter e setter.

0
Tobias Cohen