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:
- Rimani con la soluzione corrente e disponi di un metodo che può restituire diversi tipi
- Il metodo restituisce sempre un GMP (che non è un tipo primitivo e non il tipo che l'interfaccia dice)
- Il metodo restituisce sempre una stringa (che è un tipo primitivo ma non il tipo che l'interfaccia dice)
- 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?