Sometimes I made a screen to display two types of data together in one table. At that time, I was able to implement it neatly by using polymorphism using an interface instead of conditional branching. I would like to introduce what kind of merit it has with the actual code.
The whole code is uploaded on GitHub. Please forgive me though there may be a shortage because I will explain it briefly.
I will give you an extremely dirty program, but I implemented the example using conditional branching as follows.
monomorphism/PurchaseController.java
@Controller
@RequestMapping("monomorphism/purchase")
public class PurchaseController {
//The description about DI of Service is omitted.
@GetMapping("list/{memberId}")
public ModelAndView listByBadDto(@PathVariable Integer memberId) {
List<Contract> memberContracts = purchaseHistoryQueryService.getMemberContracts(memberId);
List<Purchase> memberPurchases = purchaseHistoryQueryService.getMemberPurchases(memberId);
ModelAndView modelAndView = new ModelAndView("monomorphism/complex");
// c-{id}Format key and Contract Map
Map<String, Contract> contractMap = memberContracts.stream().collect(Collectors.toMap(c -> "c-" + c.id, c -> c));
modelAndView.addObject("memberContracts", contractMap);
// p-{id}Format Keys and Purchase Map
Map<String, Purchase> purchaseMap = memberPurchases.stream().collect(Collectors.toMap(p -> "p-" + p.id, p -> p));
modelAndView.addObject("memberPurchases", purchaseMap);
//Generate a map of id and purchase date and sort by purchase date
Map<String, LocalDate> contractDateMap = contractMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().beginDate));
Map<String, LocalDate> purchaseDateMap = purchaseMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().purchasedDate));
Map<String, LocalDate> idMap = new HashMap<>();
idMap.putAll(contractDateMap);
idMap.putAll(purchaseDateMap);
List<String> ids = idMap.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getValue))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
modelAndView.addObject("ids", ids);
return modelAndView;
}
}
monomorohism/complex.html
<table class="uk-table">
<thead>
<tr>
<th>Purchase date</th>
<th>Purchased product name</th>
<th>unit price</th>
<th>Number of purchases</th>
<th>subtotal</th>
</tr>
</thead>
<tbody>
<tr th:each="id: ${ids}">
<th:block th:if="${id.startsWith('c-')}" th:with="contract = ${memberContracts.get(id)}">
<td th:text="${contract.beginDate}"></td>
<td th:text="${contract.service.name}"></td>
<td th:text="|${contract.service.priceByUnitMonth}Circle/${contract.service.unitMonths}months|"></td>
<td th:text="|${contract.months}months|"></td>
<td th:text="|${contract.service.priceByUnitMonth / contract.service.unitMonths * contract.months - contract.service.yearlyDiscount}Circle|"></td>
</th:block>
<th:block th:if="${id.startsWith('p-')}" th:with="purchase = ${memberPurchases.get(id)}">
<td th:text="${purchase.purchasedDate}"></td>
<td th:text="${purchase.commodity.name}"></td>
<td th:text="|${purchase.commodity.unitPrice}Circle|"></td>
<td th:text="|${purchase.amount}Pieces|"></td>
<td th:text="|${purchase.commodity.unitPrice * purchase.amount}Circle|"></td>
</th:block>
</tr>
</tbody>
</table>
The Controller and View are complicated, even though the contract history and purchase history are only displayed in a list.
In the example using polymorphism, it was implemented as follows.
polymorphism.PurchaseService.java
@Service
public class PurchaseService {
//The description about DI of Repository is omitted.
public List<PurchaseHistory> getMemberPurchaseHistory(Integer memberId) {
//Acquire product purchase data and convert it to a product purchase history class that implements the purchase history type
List<Purchase> purchases = purchaseRepository.findByMemberId(memberId);
Stream<CommodityPurchaseHistory> commodityPurchaseHistoryStream = purchases.stream().map(CommodityPurchaseHistory::new);
//Get service contract data and convert to service purchase history class that implements purchase history type
List<Contract> contracts = contractRepository.findByMemberId(memberId);
Stream<ServicePurchaseHistory> contractHistoryStream = contracts.stream().map(ServicePurchaseHistory::new);
//Combine purchase history type lists and sort by purchase date
return Stream.concat(commodityPurchaseHistoryStream, contractHistoryStream)
.sorted(Comparator.comparing(PurchaseHistory::purchasedDate))
.collect(Collectors.toList());
}
}
polymorphism.PolyPurchaseController.java
@Controller
@RequestMapping("polymorphism/purchase")
public class PolyPurchaseController {
//The description about DI of Service is omitted.
@GetMapping("list/{memberId}")
public ModelAndView getInterface(@PathVariable("memberId") Integer memberId) {
List<PurchaseHistory> memberPurchaseHistories = purchaseService.getMemberPurchaseHistory(memberId);
ModelAndView modelAndView = new ModelAndView("polymorphism/list");
modelAndView.addObject("memberPurchaseHistories", memberPurchaseHistories);
return modelAndView;
}
}
The description of view is very refreshing because it only calls the purchase history type method.
polymorphism/list.html
<table class="uk-table">
<thead>
<th>Purchase date</th>
<th>Purchased product name</th>
<th>unit price</th>
<th>Number of purchases</th>
<th>subtotal</th>
</thead>
<tbody>
<tr th:each="purchaseHistory : ${memberPurchaseHistories}">
<td th:text="${purchaseHistory.purchasedDate()}"></td>
<td th:text="${purchaseHistory.purchasedCommodityName()}"></td>
<td th:text="${purchaseHistory.unitPrice()}"></td>
<td th:text="${purchaseHistory.amount()}"></td>
<td th:text="${purchaseHistory.subtotal()}"></td>
</tr>
</tbody>
</table>
Whether you use polymorphism or not, it's better to keep calculations and conditionals in a server-side program.
I think most of the designers who collaborate with us have no knowledge of Java template engines, so I think it's best to keep Html files simple. If you design and program with an awareness of what kind of technology your colleagues have and what kind of division of roles is best, you may approach the success of the entire development team.
On top of that, if you use polymorphism, you can write a program with good visibility by reducing if statements from the server-side program.
Recommended Posts