2

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?

13
  • @gnat One difference is the introduction of the requirement of mutable state. I don't see that discussed in the duplicate, but I'm satisfied that the duplicate answers the debate.
    – crush
    Feb 5, 2014 at 15:21
  • @delnan The benefit of a class/interface over a function would be a) extend functionality without changing the interface; and b) easy to unit test. Would you disagree with either of these?
    – crush
    Feb 5, 2014 at 15:28
  • YAGNI: Is there a concrete undeniable requirement for the various kinds of flexibility this interface claims to introduce?
    – user7043
    Feb 5, 2014 at 15:28
  • @delnan How about switching between quantifiers without changing the interface?
    – crush
    Feb 5, 2014 at 15:29
  • 3
    "Must have some mutable state" - that's definitely nonsense (immutable class design is a broadly accepted technique for many things like string classes in Java or C#).
    – Doc Brown
    Feb 5, 2014 at 15:34

1 Answer 1

3

No, you aren't wrong--provided that you're working with a language where a class is required for downstream replacement.

In (older versions of) C#, you could finagle a static function somehow, but its more work to override that than just subclassing a simple class, especially if you use a factory pattern.

In JavaScript, however, its easier to replace a function than define a new prototype, so the overhead of a declared interface and class is just wasted memory without benefit.

And even in JavaScript, there are reasons to make a prototype class with only a single method. Not the least of which is if that "simple" method may be replaced with a very complicated set of functions instead of a few lines.

1
  • 1
    JavaScript is an interesting study since, even if an object inherits another, you can remove any inherited members. It doesn't provide a good mechanism for type safety, and thus interfaces do not make sense.
    – crush
    Feb 5, 2014 at 15:47

Not the answer you're looking for? Browse other questions tagged or ask your own question.