This article is the 11th day article of FizzBuzz Advent Calender 2017.
Everyone loves FizzBuzz.
It is also used at our company as the first question of training before joining the company.
No matter what language you use, it's supposed to be a basic one-liner, so there's no problem, but I was wondering what would happen if I wrote it in object-oriented (OOP), so I wrote it in Java
.
Let's take a quick look at FizzBuzz
again.
Count the numbers from 1 to 100 and
--Fizz for multiples of 3 --Buzz for multiples of 5 --FizzBuzz for multiples of 3 and 5 --Other than that, that number
Is output.
It's just a normal Java
answer.
public class FizzBuzz {
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
if (i % 3 == 0 && i % 5 == 0) {
System.out.println("FizzBuzz");
} else if (i % 3 == 0) {
System.out.println("Fizz");
} else if (i % 5 == 0) {
System.out.println("Buzz");
} else {
System.out.println(i);
}
}
}
}
In order to make this object-oriented code, we will first design the class.
Follow the steps below to design a class.
First, identify the objects that appear in the problem.
There is no particular problem because it is just summarized by role.
Consider the required model from the objects given above.
First of all, "count the numbers from 1 to 100 and output the result according to the conditions". It has an input / output function of "getting a range of numerical values and conditions, and outputting a character string according to the conditions".
Next, regarding 2. ~ 4, it can be said that they have the same function except for the specific numerical values and character strings. It seems that it can be decomposed into two functions, "check if it is divisible by the specified number (check with and if there are multiple)" and "hold the character string corresponding to the condition".
In summary, it would be nice to have the following three models.
--Get a range of numbers and conditions, and output a character string according to the conditions --Check if it is divisible by the specified number (check with and if there are multiple) --Hold the string corresponding to the condition
Let's name the three models mentioned above. The name is used as it is as the class name.
name | function |
---|---|
Operator | Get a range of numbers and conditions, and output a character string according to the conditions |
Specification | Check if it is divisible by the specified number |
Operation | Holds the string corresponding to the condition |
Models like Specification
are introduced in Domain Driven Development (DDD) in a pattern called ** Specification Pattern **.
Now that we have the necessary classes, we will implement them.
Based on the designed model, we will drop it into the Java
code. (Since it is placed from the side to be used, it is different from the model order above.)
Stream
is used, an environment of Java8
or higher is required.Specification.java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Specification {
private List<Predicate<Integer>> predicateList = new ArrayList<>();
public Specification(Predicate<Integer> predicate) {
this.predicateList.add(predicate);
}
private Specification(List<Predicate<Integer>> predicateList) {
this.predicateList = predicateList;
}
public Specification and(Predicate<Integer> predicate) {
List<Predicate<Integer>> results = new ArrayList<>(this.predicateList);
results.add(predicate);
return new Specification(results);
}
public boolean isSatisfiedBy(Integer number) {
return this.predicateList.stream().allMatch(p -> p.test(number));
}
}
There are four main points in implementing Specification.java
:
--Implemented as a DDD specification pattern
--Use Java
's Predicate
for the part that expresses the condition
--Implemented to receive at least one condition in the constructor and chain it with ʻand` if there are multiple conditions
--Implemented as a first class collection (immutable)
Operation.java
public class Operation {
private Specification specification;
private String message;
Operation(Specification specification, String message) {
this.specification = specification;
this.message = message;
}
public Specification getSpecification() {
return this.specification;
}
public String getMessage() {
return message;
}
}
Operator.java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
public class Operator {
private List<Operation> operationList = new ArrayList<>();
public void addOperation(Operation operation) {
this.operationList.add(operation);
}
public void run(IntStream range) {
range.forEach(number -> {
String message = this.operationList.stream()
.filter(operation -> operation.getSpecification().isSatisfiedBy(number))
.map(Operation::getMessage)
.findFirst()
.orElse(String.valueOf(number));
System.out.println(message);
});
}
}
The part to check the condition is written in one liner using Stream
.
The part of .findFirst (). OrElse ()
needs to be filled in to see if there is a better way to write it.
I will also write the code to run FizzBuzz
using the above class.
FizzBuzz.java
import java.util.function.Predicate;
import java.util.stream.IntStream;
public class FizzBuzz {
public static void main(String[] args) {
Operator operator = new Operator();
Predicate<Integer> divisibleBy3 = DivisiblePredicateFactory.divisibleBy(3);
Predicate<Integer> divisibleBy5 = DivisiblePredicateFactory.divisibleBy(5);
Operation fizzbuzz = new Operation(new Specification(divisibleBy3).and(divisibleBy5), "FizzBuzz");
Operation fizz = new Operation(new Specification(divisibleBy3), "Fizz");
Operation buzz = new Operation(new Specification(divisibleBy5), "Buzz");
operator.addOperation(fizzbuzz);
operator.addOperation(fizz);
operator.addOperation(buzz);
operator.run(IntStream.rangeClosed(1, 100));
}
}
DivisiblePredicateFactory.java
import java.util.function.Predicate;
public class DivisiblePredicateFactory {
public static Predicate<Integer> divisibleBy(Integer divisor) {
return n -> n % divisor == 0;
}
}
If you look at FizzBuzz.java
, you can see that the role of each class is clear and that the writing style is linked to the problem statement.
Even if you have an additional spec like "Bar if it's a multiple of 7," you can easily imagine where to edit it.
This is the good thing about object-oriented programming.
This time I wrote FizzBuzz
in an object-oriented manner.
Even such a simple problem was quite interesting when seriously considering object orientation.
Next time I will write a test.
The code posted here is summarized on Github, so if you like, please try running it at hand and improve it again. (And please pull request.) https://github.com/totto357/OOP-FizzBuzz
Tomorrow's 12th day will be @ aimof's "FizzBuzz consisting only of one-line function definitions, excluding executable statements"!
It is an implementation with the builder pattern of the rejected Operation class.
It seemed good to be able to express "when ~~, display xx" in the code, but I rejected it because it didn't look like Java
.
However, I will leave it because it is a big deal.
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class OperationWithBuilder {
private Specification specification;
private String message;
private Operation(Builder builder) {
this.specification = builder.getSpecification();
this.message = builder.getMessage();
}
public static Builder when(Predicate<Integer> predicate) {
return new Builder(predicate);
}
@Getter
static class Builder {
private Specification specification;
private String message;
public Builder(Predicate<Integer> predicate) {
this.specification = new Specification(predicate);
}
public Builder and(Predicate<Integer> predicate) {
this.specification = this.specification.and(predicate);
return this;
}
public Operation print(String message) {
this.message = message;
return new Operation(this);
}
}
}
When using
Predicate<Integer> divisibleBy3 = DivisiblePredicateFactory.divisibleBy(3);
Predicate<Integer> divisibleBy5 = DivisiblePredicateFactory.divisibleBy(5);
Operation fizz = Operation.when(divisibleBy3).print("Fizz");
Operation fizzbuzz = Operation.when(divisibleBy3).and(divisibleBy5).print("FizzBuzz");
Recommended Posts