The logic for a single Entity should define the behavior for the Entity, but I'm confused about where to write the processing for the list or set of Entity. I have summarized the contents that I investigated to solve such a hesitation.
I often write processing for lists and sets of Entity in Service and Controller.
HogeController.java
@Controller
public class HogeController {
private final HogeService service;
HogeController(HogeService service) {
this.service = service;
}
@RequestMapping("hoge")
public String xxx(Model model) {
List<ShoppingCartItem> cartItems = new ArrayList<>();
Integer total = cartItems.stream().mapToInt(ShoppingCartItem::subtotal()).sum();
model.addAttribute("total", total);
return "hoge/hoge";
}
}
I think this is fine. However, if you write the process in the Controller, when you want to perform the same process in another Controller, the code will probably be copied and maintenance will be difficult when the logic is changed. Is visible.
Therefore, if you create a class that represents a set as shown below, you can confine the logic in one place.
ShoppingCartItems.java
public class ShoppingCartItems {
private List<ShoppingCartItem> items = new ArrayList<>();
public void addItem(ShoppingCartItem item) {
items.add(item);
}
public Integer total() {
return items.stream().mapToInt(ShoppingCartItem::subtotal()).sum();
}
}
Now you've locked the method for calculating the total amount into one class. However, this is still inconvenient. This is because you cannot retrieve each ShoppingCartItem. ~~ Brain death ~~ It's okay to define Getter, but here are some useful techniques.
There is an extended for statement as the syntax that you will use to retrieve each ShoppingCartItem. It would be nice if you could call it like this!
ShoppingCartItems shoppingCartItems = new ShoppingCartItems();
for (ShoppingCardItem item : shoppingCartItems) {
// doSomething
}
To achieve this, it is better to implement the ʻIterable
ShoppingCartItems.java
public class ShoppingCartItems implements Iterable<ShoppingCartItem> {
private List<ShoppingCartItem> items = new ArrayList<>();
//abridgement
@Override
public Iterator<Asking> iterator() {
return items.iterator();
}
@Override
public void forEach(Consumer<? super Asking> action) {
items.forEach(action);
}
@Override
public Spliterator<Asking> spliterator() {
return items.spliterator();
}
}
For the three methods that should be implemented in the Iterable interface, you can call the method of the collection class ʻitems` field held in the class. Now you can write an extended for statement.
I want to write like this.
HogeController.java
@Controller
public class HogeController {
@GetMapping("hoge")
public String hoge(Model model) {
model.addAttribute("shoppingCartItems", new ShoppingCartItems());
return "hoge";
}
}
hoge.jsp
<c:forEach items="${shoppingCartItems}" var="shoppingCartItem">
<%-- do something --%>
</c:forEach>
In conclusion, without using JSTL ,. Only the following variables can be set to the items attribute in the JSTL forEach tag.
This limit is quite strict. Since I am dealing with lists this time, I think that I will implement the Collection type, but I personally wanted to avoid the Collection type because there are many unnecessary methods. So this time I implemented the Iterator type on the assumption that the loop is rotated only once.
ShoppingCartItems.java
public class ShoppingCartItems implements Iterable<ShoppingCartItem>, Iterator<ShoppingCartItem> {
private List<ShoppingCartItem> items = new ArrayList<>();
//abridgement
private int cursor = 0;
@Override
public boolean hasNext() {
if (cursor < items.size()) {
return true;
}
return false;
}
@Override
public ShoppingCartItem next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return items.get(cursor++); //If you do not increment it, it will loop infinitely ☆ 彡
}
}
You can now specify an instance of the ShoppingCartItems class in the items attribute of the JSTL forEach tag.
You can use it by defining the items field stream method as a delegation method to this ShoppingCartItems. If you have other methods you need, you can define a delegation method in this class and it will be ready to use. However, we do not recommend defining methods that perform unnecessary operations in your business logic.
ShoppingCartItems.java
public Stream<ShoppingCartItem> stream() {
return items.stream();
}
In the first place, the purpose of creating a class for handling collections is to prevent tampering with collections and to increase cohesion. If you define too many methods, it will not serve your purpose. Before you define a method, make sure you really need it.
I think it's common to write operations on sets, especially operations related to business logic, in the Service class. In the future, I would like to consider defining a class that has a set as a field as a means to confine the business logic for the set in one place.
Recommended Posts