There seem no end to fizzbuzz implementations. Something I rarely see is an implementation that's easy to extend with more name-divisor pairs. Another thing that usually annoys me is the lack of testability. Printing to standard output and verifying by reading is less than ideal.
This FizzBuzz
class can work with an arbitrary number of fizzers,
following these steps:
- Get a builder with
FizzBuzz.builder()
- Add the fizzers using the builder, for example for classic fizz-buzz:
- Call
.add("Fizz", 3)
- Call
.add("Buzz", 5)
- Call
- Create a
FizzBuzz
object by calling.build()
when ready - The
.getValue(number)
method returns the value for a specified number
The code:
public class FizzBuzz {
private final List<Fizzer> fizzers;
private static class Fizzer {
private final String name;
private final int divisor;
private Fizzer(String name, int divisor) {
this.name = name;
this.divisor = divisor;
}
}
public static class Builder {
private final List<Fizzer> fizzers = new ArrayList<>();
public Builder add(String name, int divisor) {
fizzers.add(new Fizzer(name, divisor));
return this;
}
public FizzBuzz build() {
return new FizzBuzz(this);
}
}
private FizzBuzz(Builder builder) {
fizzers = Collections.unmodifiableList(builder.fizzers);
}
public String getValue(int num) {
String output = "";
for (Fizzer fizzer : fizzers) {
if (num % fizzer.divisor == 0) {
output += fizzer.name;
}
}
return output.isEmpty() ? Integer.toString(num) : output;
}
public static Builder builder() {
return new Builder();
}
}
A sample demo program using classic fizz-buzz rules, and printing results from 1 to 100:
import static java.util.stream.IntStream.rangeClosed;
class FizzBuzzDemo {
public static void main(String[] args) {
FizzBuzz fizzBuzz = FizzBuzz.builder().add("Fizz", 3).add("Buzz", 5).build();
rangeClosed(1, 100).mapToObj(fizzBuzz::getValue).forEach(System.out::println);
}
}
Unit tests:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class FizzBuzzTest {
@Test
public void test_Without_Fizzer() {
FizzBuzz fizzBuzz = FizzBuzz.builder().build();
assertEquals("1", fizzBuzz.getValue(1));
assertEquals("2", fizzBuzz.getValue(2));
assertEquals("3", fizzBuzz.getValue(3));
assertEquals("4", fizzBuzz.getValue(4));
assertEquals("5", fizzBuzz.getValue(5));
assertEquals("15", fizzBuzz.getValue(15));
assertEquals("16", fizzBuzz.getValue(16));
assertEquals(Integer.toString(31 * 3 * 5), fizzBuzz.getValue(31 * 3 * 5));
assertEquals(Integer.toString(31 * 3 * 5 + 1), fizzBuzz.getValue(31 * 3 * 5 + 1));
}
@Test
public void test_With_Fizz() {
FizzBuzz fizzBuzz = FizzBuzz.builder().add("Fizz", 3).build();
assertEquals("1", fizzBuzz.getValue(1));
assertEquals("2", fizzBuzz.getValue(2));
assertEquals("Fizz", fizzBuzz.getValue(3));
assertEquals("4", fizzBuzz.getValue(4));
assertEquals("5", fizzBuzz.getValue(5));
assertEquals("Fizz", fizzBuzz.getValue(15));
assertEquals("16", fizzBuzz.getValue(16));
assertEquals("Fizz", fizzBuzz.getValue(31 * 3 * 5));
assertEquals(Integer.toString(31 * 3 * 5 + 1), fizzBuzz.getValue(31 * 3 * 5 + 1));
}
@Test
public void test_With_FizzBuzz() {
FizzBuzz fizzBuzz = FizzBuzz.builder().add("Fizz", 3).add("Buzz", 5).build();
assertEquals("1", fizzBuzz.getValue(1));
assertEquals("2", fizzBuzz.getValue(2));
assertEquals("Fizz", fizzBuzz.getValue(3));
assertEquals("4", fizzBuzz.getValue(4));
assertEquals("Buzz", fizzBuzz.getValue(5));
assertEquals("FizzBuzz", fizzBuzz.getValue(15));
assertEquals("16", fizzBuzz.getValue(16));
assertEquals("FizzBuzz", fizzBuzz.getValue(31 * 3 * 5));
assertEquals(Integer.toString(31 * 3 * 5 + 1), fizzBuzz.getValue(31 * 3 * 5 + 1));
}
@Test
public void test_With_FizzBuzzJazz() {
FizzBuzz fizzBuzz = FizzBuzz.builder().add("Fizz", 3).add("Buzz", 5).add("Jazz", 7).build();
assertEquals("1", fizzBuzz.getValue(1));
assertEquals("2", fizzBuzz.getValue(2));
assertEquals("Fizz", fizzBuzz.getValue(3));
assertEquals("4", fizzBuzz.getValue(4));
assertEquals("Buzz", fizzBuzz.getValue(5));
assertEquals("FizzBuzz", fizzBuzz.getValue(15));
assertEquals("16", fizzBuzz.getValue(16));
assertEquals("FizzBuzz", fizzBuzz.getValue(31 * 3 * 5));
assertEquals(Integer.toString(31 * 3 * 5 + 1), fizzBuzz.getValue(31 * 3 * 5 + 1));
assertEquals("FizzBuzzJazz", fizzBuzz.getValue(31 * 3 * 5 * 7));
}
}
I'm interested in any kind of improvement you could suggest about any of the above.
Something I don't like is the name Fizzer
.
Although it's an internal implementation detail not visible to users,
I'd like a better, more intuitive name, if somebody can think of one.
As you notice,
the constructor of FizzBuzz
is forbidden:
you can only create it using a builder.
The intention behind this is that we need a list of name-divisor pairs,
and this seems the most ergonomic option I see in terms of usability.
Or maybe there is something else I missed?
getValue
function that returns a string instead of printing it. I don't see what's unbearable about that. \$\endgroup\$