Minore di due mali per quanto riguarda i tipi di ritorno

2

Sto implementando un sistema in PHP in cui i valori sono rappresentati con oggetti che implementano un'interfaccia Amount. Sto costruendo due diverse implementazioni di questa interfaccia, una che utilizza internamente un intero semplice per rappresentare il valore che si sta memorizzando e l'altra che usa gli oggetti GMP.

Il metodo di interfaccia che sta dimostrando un punto critico è il seguente:

/**
 * Get the amount being stored
 * 
 * @return number
 */
public function getAmount ();

(Se non hai familiarità con le convenzioni della documentazione di PHP, number significa qualsiasi tipo che può essere considerato numerico, il che significherebbe un intero o un float)

L'idea è che nella maggior parte dei casi l'implementazione dei numeri interi verrà utilizzata per motivi di utilizzo delle prestazioni e della memoria e si passerà all'implementazione GMP solo quando le dimensioni dei numeri superano quelle che è possibile inserire in un numero intero. L'overflow potrebbe essere gestito in questo modo (in psuedo-code):

try
{
    $someAmount -> add ($otherAmount);
} 
catch (OverFlowException $e)
{
    // Replace the overflowed Amount object with a BigAmount
    $someAmount = new BigAmount ($someAmount -> getValue ());

    // Retry the add
    $someAmount -> add ($otherAmount);
}

La mia implementazione basata su numeri interi è molto semplice:

/**
 * @return integer
 */
public function getAmount ()
{
    return $this -> amount;
}

Tuttavia, quando ho iniziato a implementare la versione GMP, le cose si sono complicate:

/**
 * Get the amount as an integer or string
 * 
 * This method will attempt to return the current value as an integer. If 
 * the value being represented is beyond the range of a PHP integer then 
 * it will return a string instead. 
 * 
 * @return number|string
 */
public function getAmount ()
{
    // If the value is within integer range then return an int
    if ((gmp_cmp ($this -> amount, PHP_INT_MAX) < 1)
    && (gmp_cmp ($this -> amount, PHP_INT_MAX * -1 - 1) > -1))
    {
        return gmp_intval ($this -> amount);
    } else {
        return gmp_strval ($this -> amount);
    }
}

Mentre stavo implementando questo, mi sono reso conto che aveva alcuni problemi:

  • Non sempre restituisce lo stesso tipo (risultante in una violazione del principio di sorpresa minima)
  • Non è strettamente conforme alla sua interfaccia (anche violando la minima sorpresa)
  • I getter dovrebbero essere estremamente semplici e questo non è

La prima ovvia soluzione era semplicemente restituire l'oggetto GMP, ma ciò significherebbe che l'oggetto non restituirebbe mai ciò che la sua interfaccia dice che dovrebbe, e che questa classe restituirebbe gli oggetti invece dei tipi primitivi come l'implementazione dei numeri interi.

La seconda soluzione era che il getter restituisse sempre una stringa. Questo sarebbe solo leggermente più complesso del restituire l'oggetto GMP, e significherebbe restituire un tipo primitivo, ma non siamo ancora conformi all'interfaccia.

Una terza soluzione sarebbe quella di fare in modo che il getter lanci un'eccezione se il valore non può essere restituito come numero intero e fornire un metodo aggiuntivo per ottenere il valore come stringa. Ciò rispetterebbe il contratto dell'interfaccia (se avessi cambiato l'interfaccia per indicare che poteva essere generata un'eccezione se il valore non può essere restituito come un intero), ma aggiungerebbe un onere aggiuntivo ai programmatori che usano le classi per gestire l'avere per gestire casi in cui i numeri sono troppo grandi per i numeri interi.

Quindi, in sintesi, ho un numero di scelte, nessuna completamente ideale:

  1. Rimani con la soluzione corrente e disponi di un metodo che può restituire diversi tipi
  2. Il metodo restituisce sempre un GMP (che non è un tipo primitivo e non il tipo che l'interfaccia dice)
  3. Il metodo restituisce sempre una stringa (che è un tipo primitivo ma non il tipo che l'interfaccia dice)
  4. Genera un'eccezione se non è possibile restituire un intero e fornire un metodo aggiuntivo per quei casi (che soddisfa le richieste dell'interfaccia ma aggiungerebbe dei requisiti per gestirlo nel codice che consuma)

Quale approccio consideri migliore (o meno peggio) oppure esiste un altro approccio che non ho considerato?

    
posta GordonM 21.05.2015 - 00:23
fonte

2 risposte

1

Le prime 3 opzioni spezzano il principio di sostituzione di Liskov. Quarto uno che complica l'interfaccia per il client.

Personalmente ritengo che la soluzione più semplice sarebbe dichiarare il metodo getAmount () che restituisce sempre string, quindi funziona con implementazioni basate su interi e GMP.

In alternativa questo potrebbe essere un caso di ottimizzazione prematura - forse è giusto calcolare tutto in GMP e restituire sempre l'oggetto GMP (ovviamente l'interfaccia dovrebbe essere dichiarata di conseguenza).

    
risposta data 24.05.2015 - 16:51
fonte
0

Alla fine ho deciso sull'opzione 4. Avere l'interfaccia specifica che getAmount () può lanciare un'eccezione e fare in modo che l'implementazione di BigAmount ne faccia uno se non fosse possibile esprimere il suo valore come numero intero. Quindi avrò un altro metodo che può restituire l'importo in un altro formato. Richiederebbe al programmatore di usare la classe per saltare attraverso alcuni cerchi aggiuntivi, ma almeno questi cerchi sono ben definiti e documentati.

    
risposta data 21.05.2015 - 16:27
fonte

Leggi altre domande sui tag