A story that could be implemented neatly by using polymorphism when you want to express two types of data in one table

A story that could be implemented neatly by using polymorphism when you want to express two types of data in one table

Overview

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.

Premise

Development environment

specification

  1. View product purchase history and subscription service contract history on Amazon
  2. I want to display in chronological order regardless of the purchase history of the product or the contract history of the subscription service.
  3. Display purchase date, product name, unit price, number of purchases, subtotal
  4. For subscription contracts, display the contract start date, service name, unit price, contract months, and subtotal.
  5. Subscription contracts get discounts when contracted annually

screen image

スクリーンショット 2019-06-24 12.38.30.png

2 types of sample code

The whole code is uploaded on GitHub. Please forgive me though there may be a shortage because I will explain it briefly.

Example using conditional branching

I will give you an extremely dirty program, but I implemented the example using conditional branching as follows.

  1. Get data from purchase history and contract history and store it in an associative array
  2. Extract the key of the associative array and the purchase date / contract date, and generate a list in which the IDs are sorted in ascending order of date.
  3. Display the purchase history and contract history on the screen in the order of the ID list

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.

Example using polymorphism

In the example using polymorphism, it was implemented as follows.

  1. Convert purchase data and contract data obtained from the database to a purchase history type class
  2. Sort the purchase history type data in order of purchase date and display it on the screen

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>

at the end

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

A story that could be implemented neatly by using polymorphism when you want to express two types of data in one table
Object-oriented design that can be used when you want to return a response in form format
I want you to put the story that the error was solved when you stabbed the charger in the corner of your head
[Swift] When you want to know if the number of characters in a String matches a certain number ...
A collection of patterns that you want to be aware of so as not to complicate the code
A memorandum when you want to see the data acquired by Jena & SPARQL for each variable.
How to make a key pair of ecdsa in a format that can be read by Java
When you want to check whether the contents of a property can be converted to a specific type
How to implement a slideshow using slick in Rails (one by one & multiple by one)
When you want to notify an error somewhere when using graphql-spring-boot in Spring Boot
How to make a unique combination of data in the rails intermediate table
[Ruby] When you want to assign the result obtained by conditional branching to a variable and put it in the argument
A story that people who did iOS solidly may be addicted to the implementation of Listener when moving to Android