Java - Utilizzo di una variabile Function per impostare il valore di ritorno del metodo toString ()

2

Ultimamente ho iniziato ad aggiungere questo ad alcune classi in un'API interna:

public class MyClass {

  // I usually do this with classes I expect to 
  // be printed out or tested a lot (particularly
  // in the early stages of creating the class/package).
  // 
  // Having one field is not a criteria for doing this,
  // just a simple example.  And of course there is always
  // a default value for the toString field.

  private Object oneOtherFieldExample;

  private Function<MyClass, String> toString = 
      myClass -> String.valueOf(oneOtherFieldExample);

  ...

  public void setToString(Function<MyClass,String> toStringFunction) {
    toString = toStringFunction;
  }

  public String toString() {
    return toString.apply(this);
  }

Questo è diventato utile un paio di volte, ma visto che sto lavorando su una API interna mi chiedo

  • Prevedete tutti i potenziali problemi che mi mancano?
  • È ragionevolmente leggibile e intuitivo?
  • Questo o qualcosa di simile esiste già come una sorta di modello (e in tal caso, come si chiama)?
  • Poiché questo è particolarmente usato per test e creazione precoce, dovrei aggiungere un modo statico per impostare la variabile toString predefinita per evitare di doverla impostare manualmente ogni volta che istanzia un nuovo oggetto MyClass (nel caso in cui vengono create migliaia di oggetti )?
posta roundar 19.11.2015 - 18:07
fonte

2 risposte

3

Funzioni di prima classe e funzioni di ordine superiore

Un linguaggio di programmazione ha funzioni di prima classe se supporta il passaggio di funzioni come argomenti ad altre funzioni, restituendole come valori da altre funzioni e assegnandole a variabili o memorizzandole in strutture di dati.

Nelle lingue in cui le funzioni sono cittadini di prima classe, le funzioni possono essere passate come argomenti ad altre funzioni allo stesso modo di altri valori (una funzione che accetta un'altra funzione come argomento è chiamata una funzione di ordine superiore ).

È una tecnica perfettamente valida e rispettata, specialmente tra la folla della programmazione funzionale. Come hai già scoperto, ti permette di cambiare il comportamento di una classe senza cambiare la classe stessa.

L'esempio canonico delle funzioni di ordine superiore è Map. La funzione Mappa accetta una funzione come parametro e scorre su un elenco, applicando la funzione a ciascun elemento nell'elenco.

void map(int (*f)(int), int x[], size_t n) {
    for (int i = 0; i < n; i++)
        x[i] = f(x[i]);
}

Un altro esempio di funzioni di ordine superiore è un comparatore. I comparatori vengono utilizzati nelle funzioni di ordinamento per definire in che modo l'ordinamento confronta un valore nella raccolta con un altro valore a scopo di ordinazione.

var result = MergeSort(listOfImaginaryNumbers, ImaginaryNumberComparator);

È una tecnica valida per il tuo scenario?

Per essere onesti, una determinata classe dovrebbe già sapere come ToString() stessa, senza alcun aiuto dal mondo esterno. Se stavo progettando una classe del genere, e avevo bisogno di qualche variazione sull'output ToString() , probabilmente fornirei alcuni parametri nel costruttore che il metodo ToString() potrebbe usare per modificare il suo comportamento, piuttosto che ricorrere a funzioni di prima classe . Oppure vorrei esaminare lo stato dell'oggetto per decidere come dovrebbe comportarsi ToString() . Per essere veramente utile, la funzione di prima classe dovrebbe avere una conoscenza degli interni della classe, che violerebbe l'incapsulamento.

A seconda di cosa esattamente vuoi ottenere, una funzione Mappa potrebbe essere un'alternativa adatta.

Ulteriori letture
Funzioni di prima classe

    
risposta data 19.11.2015 - 19:28
fonte
2

In generale, ci si aspetterebbe che il metodo toString() fosse deterministico. Invocarlo più volte sullo stesso oggetto senza cambiare nessuno stato dell'oggetto dovrebbe produrre gli stessi risultati. Sebbene non faccia parte del contratto generale per toString() , molte implementazioni di librerie e framework con cui ho lavorato si aspettano questo.

Usando un lambda o un altro approccio funzionale per "raggiungere" e modificare l'output, si violano le aspettative non scritte.

Sarebbe meglio usare un decoratore o una variante su di esso per modificare il comportamento.

Come funziona, il tuo oggetto principale rimane lo stesso. Produce sempre lo stesso output toString() .

Se hai bisogno di un output diverso, costruisci un oggetto diverso contenente il primo come parametro. Chiamare toString() su questo oggetto esterno produce risultati diversi.

Considera il seguente esempio:

public interface Widget { ... }

public class MyWidget implements Widget { ... }

public class HtmlWidget implements Widget {
  public HtmlWidget(Widget w) { ... }
}

public class CsvWidget implements Widget {
  public CsvWidget(Widget w) { ... }
}

La classe MyWidget continuerà a viaggiare come sempre, con un'implementazione di base toString() . Le classi HtmlWidget e CsvWidget eseguono il wrapping di un altro widget. Passano attraverso tutte le chiamate di metodo all'oggetto sottostante tranne per toString() : producono un Widget as ( HTML e CSS ) o CSV , rispettivamente.

Il vantaggio chiave qui è la separazione delle preoccupazioni: ogni oggetto è responsabile di una preoccupazione . Il Widget fa cose Widget, i decoratori Widget decorano.

Supponendo che i dati sottostanti siano stabili, puoi persino avere più thread per mettere in fila lo stesso oggetto utilizzando decoratori diversi allo stesso tempo, perché l'oggetto originale è invariato.

    
risposta data 19.11.2015 - 20:14
fonte

Leggi altre domande sui tag