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?

crush
  • 131
  • 5
  • @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 05 '14 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 05 '14 at 15:28
  • YAGNI: Is there a concrete undeniable requirement for the various kinds of flexibility this interface claims to introduce? –  Feb 05 '14 at 15:28
  • @delnan How about switching between quantifiers without changing the interface? – crush Feb 05 '14 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 05 '14 at 15:34
  • @crush I assume by that you mean introducing completely different quanfification logic and not changing... what exactly? The `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
  • @crush Note that the class you propose is perfectly fine object-oriented design IMHO. However, it's a separate question whether object-oriented design is the best (most appropriate, easiest to maintain, cheapest, etc.) approach. –  Feb 05 '14 at 15:39
  • @delnan In the context of the SO question, you could make the argument that it is hypothetical future proofing because the author of the question didn't specifically ask for a structure that could support multiple types of quantifiers. However, outside of that context, I have found many instances where changing the quantifier list, without changing the interface, is desirable. With a grouping of named functions, you have to change the function call everywhere it is used. This is equivalent to the interface changing. – crush Feb 05 '14 at 15:39
  • @delnan [continued] As I detailed in my answer, one such instance is changing from units of measure. In the instance I suggested, it was changing from metric to imperial units. You could, of course, counter-argue that using a reference to a function would achieve the same purpose (not requiring the interface to change), I suppose. – crush Feb 05 '14 at 15:41
  • @crush Hold on a second. You need to distinguish between (1) altering an existing set of quantifiers, and (2) adding a new one *and* somehow replacing *some* of the existing calls - but not all - with that new quantifier. Case (1) I can consider likely (your metric->imperial example falls into it), but in that case the procedural programmer just needs to change the internals of the function. Case (2) seems much less likely. The only scenario I can imagine is if you previously used one quantifier for everything but now need to differentiate, but in that case [...] –  Feb 05 '14 at 15:45
  • @crush [...] the interface runs into the same problems. If you had a simple global `$numberQuantifier` as in the question, you still need to check every occurence of it. If you instanciate a new one everwhere a la `new 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 different `Quantifier`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:48
  • @delnan Consider a factory that creates the appropriate `NumberQuantifier` 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 generic `number_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
  • let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/12869/discussion-between-crush-and-delnan) – crush Feb 05 '14 at 15:52

1 Answers1

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.

DougM
  • 6,361
  • 1
  • 17
  • 34
  • 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 05 '14 at 15:47