I recently had a back and forth over at StackOverflow about my answer to this question.
The question was simple. The author wanted to transform a number into an abbreviated version that appended a K, M, or B depending on the size of the number.
One user suggested a very straightforward and naive answer:
function round_thousands($number){
if($number < 1000){
return $number;
} else {
return number_format($number/1000, 1).'K';
}
}
Ignoring the fact that the name of the function doesn't match what it does, and that it only converts into K, I can see why this answer might be attractive at first.
However, I elected to take a more robust approach, and create an interface:
interface Quantifier {
public function quantify($value);
}
I then created a default quantifier called NumberQuantifier:
class NumberQuantifier implements Quantifier {
protected $quantifierList;
public function __construct($quantifierList) {
$this->quantifierList = $quantifierList;
arsort($this->quantifierList); //Make sure they are largest too smallest.
}
public function quantify($number) {
foreach ($this->quantifierList as $symbol => $threshold) {
if ($threshold > $number) continue;
return number_format($number / $threshold, 1) . $symbol;
}
}
}
Simple stuff, I thought.
This allows you to create quantifiers for all sorts of things: file size, mass, length, volume, etc. Without having to copy/paste a new function every time:
$numberQuantifier = new NumberQuantifier(array(
'B' => 1000000000,
'M' => 1000000,
'K' => 1000
));
Furthermore, in the event that you had a more advanced case, you could implement Quantifier and create some custom functionality for quantifying.
However, I was soon met with a critic who insisted that I shouldn't be using a class for this at all. In fact, he was adamant, despite my numerous attempts to satisfy why a class would be ideal.
Argument
His argument was that a class
- Must have some mutable state
- Must encapsulate more than a single operation
Otherwise, he contends, it should be a global function.
His alternative (I think) would be to do something like:
function number_quantify($value, $quanitfierList) {
foreach ($quantifierList as $symbol => $threshold) {
if ($threshold > $number) continue;
return number_format($number / $threshold, 1) . $symbol;
}
}
function quantify_mass($value) {
$quantifieryList = array(); //quantifiers here.
return number_quantify($value, $quantifierList);
}
function quantify_length($value) { //.. }
//etc.
Basically, for any type of quantifier, you have to create a new function that would always live in global space. Alternatively, you could manually pass the $quantifierList
to each function call of number_quantify
which seems like a maintenance nightmare.
I believe that my use of an interface and polymorphism not only makes the code cleaner and more maintainable, but also would facilitate better unit testing.
Question
Am I wrong? Is having a class that encapsulates a single operation and no mutable state some type of code smell?
Quantifier
interface? That's nice but not an advantage over the procedual version: "A function taking a numbers and returning its " is also a valid, sufficiently general, and simple interface. And you still haven't addressed whether there will definitely be a need to switch/have multiple quantifiers at the same time, or whether this is a purely hypothetical future requirement. – Feb 05 '14 at 15:36$numberQuantifier
as in the question, you still need to check every occurence of it. If you instanciate a new one everwhere a lanew NumberQuantifier(...)->quantify($x)
, you have the same situation as calling a concrete function, just more verbosely. Only if you already distinguished between various units and passed around differentQuantifier
s for each of them, you could affect a small subset of the call sites with a single edit, but if you did that you wouldn't be in the situation to begin with. – Feb 05 '14 at 15:48NumberQuantifier
instance based on a user's preference setting$_SESSION['unitType']
. By using the above class, the appropriate NumberQuantifier could be created once, at the top of the request, and the usage within the page would not change. If functions were used, the only means of emulating this behavior would be to a) use a reference to the function; or b) call the genericnumber_format($value, $quantifierList)
function, passing the$quantifierList
in each call. This doesn't insure immutability of the$quantifierList
– crush Feb 05 '14 at 15:51