preface

In my previous article, KONG Yiji’s four ways of writing “Hui” aroused my thinking on the implementation of the strategy mode, which left a suspense. There were many repetitive code blocks in the code implementation of the article. Such a problem is intolerable to a person who has high requirements on code quality. Why do you say that? Because such substandard code, whether you or others to maintain or update new features, will be difficult to start, will eventually make people “respect” the family code.

Let’s briefly review the previous content, which set up a business scenario of lottery, with four categories of prizes including cash, coupons, points and thank you for participating. In the future, new types of prizes may be added, such as the number of times of giving lottery, and the rewards will be sent to users in real time if users draw. Now we will change the business scene slightly. We will return to the business scene of the previous order activity, which is divided into multiple accommodation activity, continuous accommodation order activity, first check-in activity, membership order activity and so on. Rewards such as points, coupons and membership will be given if the order meets the conditions of the activity. Thus, the type of the types of activities and prizes are gradually increasing, so for this scenario raises the strategy design pattern, at the same time in the process of encoding found a lot of repetitive code block and fixed process and logic, the scene is conforms to the template design pattern, and led to our another leading role of this article: template design pattern.

What’s the problem

First let’s find out what duplicated code blocks are:

  • The IRewardSendStrategy interface’s isTypeMatch method. The end implementation of each strategy is the same. This is repetitive code.
  • The implementation of the afterPropertiesSet method is also duplicated for each policy class when the policy classes are composed in a manner that implements the InitializingBean interface.
  • Judge whether the order meets the conditions of the activity. If the conditions are met, the reward will be sent. If the conditions are not met, the processing will end. These logics are fixed, and the specific judgment process and reward distribution process are not fixed. We can control the process of judgment and reward distribution, and let the specific judgment process and reward distribution process be realized by subclasses.

The emergence of repetitive code blocks, which clearly do not conform to the principles of object-oriented OOP development, will certainly affect the health of software, so let’s look at how to use template design patterns to solve the problem.

Template design pattern

We know that we can use the template design pattern to solve the problem of duplicate code and make code more robust while improving code utilization. So what is a template design pattern? Let me introduce the template design pattern.

In Template Pattern, an abstract class exposes a method/Template that executes its methods. Subclasses can override methods as needed, but calls are made in a way defined in an abstract class. This type of design pattern is behavioral. The template pattern involves implementing the algorithm skeleton in the parent class and the concrete steps in the subclass, so it must have an abstract class (the default method of the interface in Java8 seems to work as well).

introduce
  • Intent: To define the skeleton of an algorithm in an operation, while deferring some steps to subclasses, the template method allows subclasses to redefine specific steps of an algorithm without changing the structure of that algorithm.
  • Main solution: some methods are generic, but the method is rewritten in every subclass.
  • When to Use: There are some general methods.
  • How to solve it: Abstract these generic algorithms.
  • Key code: In the abstract class implementation, other steps are deferred to the subclass implementation.
Application examples:
  • Implementation of fair and unfair lock in ReentrantLock in JDK
  • Spring’s support for Hibernate encapsulates some predefined methods, such as starting a transaction, obtaining a Session, closing a Session, etc.
Advantages:
  • Encapsulate invariant parts and extend variable parts.
  • Extract common code for easy maintenance.
  • The behavior is controlled by the parent class and implemented by the child class.
Disadvantages:
  • May make the code more difficult to read.
  • Each different implementation requires a subclass to implement, resulting in an increase in the number of classes, making the system larger.
Usage Scenarios:
  • There are methods that are common to multiple subclasses and have the same logic.
  • Important, complex methods can be considered as template methods.
Matters needing attention:

To prevent subclass overrides, general template methods add final keywords.

What should be done

Next I will use the code to show the specific approach, referring to the previous code, some changes, specific code I will be directly posted in the article, I suggest you learn, or to start to knock, do not come up to the source code. To know that the paper comes to sleep shallow, or to practice yourself, in order to get different experience. Next I will use the code to show the specific approach, referring to the previous code, some changes, specific code I will be directly posted in the article, I suggest you learn, or to start to knock, do not come up to the source code. To know that the paper comes to sleep shallow, or to practice yourself, in order to get different experience. Stop talking and get started!!

The UML diagram and organization diagram of the modified code are as follows:

  1. Define reward, activity type enumeration
@Getter
public enum RewardTypeEnum {
    /** * Cash bonus */
    CASH("1"."Cash"),
    /** * integral */
    POINT("2"."Points"),
    /** ** coupon */
    COUPON("3"."Coupon"),
    /** ** Thank you for joining */
    THANK_YOU("4"."Thank you for playing."),;private String code;

    private String desc;

    RewardTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc; }}@Getter
public enum ActiveTypeEnum {


    /** * Hotel order */
    HOTEL_ORDER("1"."Hotel Order"),

    /** * Membership order */
    LEVEL_ORDER("2"."Membership Order"),;private String code;

    private String desc;

    ActiveTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc; }}Copy the code
  1. Define activity and reward provisioning policy interfaces
/** * Policy interface for sending rewards **@author Sam
 * @date 2021/5/16
 * @since1.0.0 * /
public interface IRewardSendStrategy {

    /** * The type of reward to be given, which is a way to indicate different strategies *@return* /
    String type(a);

    /** * Whether to match *@paramType Reward type *@return* /
    boolean isTypeMatch(String type);


    /** ** Send rewards *@paramMemberId memberId */
    void sendReward(Long memberId);

}



/** * Policy interface for order activity logic judgment **@author Sam
 * @date 2020/11/26
 * @since1.7.3 * /
public interface IActiveHandleStrategy {
    /** * Returns the type of activity * ActiveCategoryEnum enum **@return* /
    String getCategory(a);

    /** * Returns the detailed type of the activity * ActiveCategoryDetailEnum enum **@return* /
    String getCategoryDetail(a);

    /** * whether to match **@paramCategory Activity type *@return* /
    boolean isTypeMatch(String category);


    /** ** Order check **@paramTemporaryorder order *@paramActiveDto activeDto *@return Result
     */
    boolean checkOrder(ActiveOrderDto temporaryOrderDto, ActiveDto activeDto);
}


Copy the code
  1. The key step is to identify the common immutable methods and logic and extract the common immutable methods and fixed logic into the abstract parent class.

Referring to the title: Where is the problem raised in the question, the final optimized code is as follows:


/** * The isTypeMatch method and InitializingBean implementation are promoted to the abstract class for code reuse purposes **@author Sam
 * @date 2020/11/26
 * @since1.7.3 * /
public abstract class AbstractRewardSendStrategy implements InitializingBean.RewardSendStrategy {


    @Override
    public final boolean isTypeMatch(String type) {
        return Objects.equals(type, this.type());
    }

    @Override
    public final void afterPropertiesSet(a) throws Exception {
        RewardSendStrategyFactory.registerStrategy(this.type(), this); }}/** * activity abstract class, extract the public method, * the order meets the judgment of reward after the public logic is implemented here, * order specific conditions of the judgment delay is implemented by the subclass. ** Strategy and template pattern combination use **@author Sam
 * @date 2020/11/26
 * @since1.7.3 * /
@Slf4j
@Component
public abstract class AbstractActiveHandleStrategy implements IActiveHandleStrategy {

    /** * other abstract methods */
    public abstract void otherMethod(a);


    public final void otherMethod1(a) {
        System.out.println("Other Common Methods");
    }

    @Override
    public final boolean isTypeMatch(String categoryDetail) {
        return Objects.equals(categoryDetail, this.getCategoryDetail());
    }

    /** * The actual method to call from the outside@param* / temporaryOrderDto order
    public final boolean handle(ActiveOrderDto temporaryOrderDto, ActiveDto activeDto) {

        // Call the method in the interface that needs to be implemented by subclasses
        boolean result = checkOrder(temporaryOrderDto, activeDto);
        if(! result) { log.error("Order {} does not meet the conditions of award release for activity {}", temporaryOrderDto.getOrderNo(), activeDto.getId());
            return false;
        }
        return sendReward(temporaryOrderDto, temporaryOrderDto.getMemberId(), activeDto);
    }

    /** * Unified way to send rewards **@paramTemporaryOrderDto order *@paramMemberId User ID *@param* / activeDto activities
    protected final boolean sendReward(ActiveOrderDto temporaryOrderDto, long memberId, ActiveDto activeDto) {
        AbstractIRewardSendStrategy impl = RewardSendStrategyFactory.getImpl(activeDto.getRewardType());
        impl.sendReward(memberId, activeDto);
        return true; }}Copy the code

An abstract class may not have abstract methods, but a class that has abstract methods must be defined as abstract.

  1. Create a policy factory to consolidate policies in order to better provide third party calls.
@Slf4j
@Component
public class RewardSendStrategyFactory {

    /** * Save the policy set */
    private final static Map<String, AbstractRewardSendStrategy> STRATEGY_MAP = new ConcurrentHashMap<>(16);


    /** * Add policy instance **@param type
     * @param strategy
     */
    public static void registerStrategy(String type, AbstractRewardSendStrategy strategy) {
        STRATEGY_MAP.put(type, strategy);
    }

    /** * Get the policy instance **@param type
     * @return* /
    public static AbstractRewardSendStrategy getImpl(String type) {

        returnSTRATEGY_MAP.get(type); }}/** * The entry that handles all activity processing and determines the specific activity policy ** to invoke based on the getImpl(String categoryDetail) type@author Sam
 * @date 2020/7/13
 * @since1.6.8 * /
@Slf4j
@Component
public class ActiveHandleFactory {

    @Autowired
    private List<AbstractActiveHandleStrategy> activeHandleList;


    /** ** external unified entrance **@paramCategoryDetail type *@return* /
    public AbstractActiveHandleStrategy getImpl(String categoryDetail) {
        return activeHandleList.stream().filter(strategy -> strategy.isTypeMatch(categoryDetail))
                .findAny()
                .orElseThrow(() -> new UnsupportedOperationException("No policy implementation found")); }}Copy the code
  1. Specific policy implementation

Because the strategy implementation code is relatively simple, I will give a strategy implementation of coupon issuance and membership order activities in this place, the rest of us just follow suit.


@Slf4j
@Service("couponRewardSendStrategyV1")
public class CouponRewardSendStrategy extends AbstractRewardSendStrategy {


    @Override
    public String type(a) {
        return RewardTypeEnum.COUPON.getCode();
    }


    @Override
    public void sendReward(Long memberId) {
        log.info("Send coupon prizes to [{}]", memberId); }}/** * Membership order processing **@author Sam
 * @date 2020/11/26
 * @since1.7.3 * /
@Slf4j
@Service
public class LevelOrderActiveHandleStrategy extends AbstractActiveHandleStrategy {
    @Override
    public void otherMethod(a) {
        log.info("Fulfillment of membership Orders");
    }

    @Override
    public String getCategory(a) {
        return ActiveTypeEnum.LEVEL_ORDER.getCode();
    }

    @Override
    public String getCategoryDetail(a) {
        return ActiveTypeEnum.LEVEL_ORDER.getCode();
    }

    @Override
    public boolean checkOrder(ActiveOrderDto temporaryOrderDto, ActiveDto activeDto) {

        log.info("Determine if the attributes of the order {} match the conditions of the activity {}", temporaryOrderDto, activeDto);
        Random random = new Random();
        int i = random.nextInt(4);
        if (i >= 2) {
            return false;
        }
        return true; }}Copy the code
  1. Write a unit test to see how it works

@ContextConfiguration(locations = {"classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class RewardSendStrategyFactoryTest {

    @Autowired
    ActiveHandleFactory activeHandleFactory;


    @Test
    public void test(a) {

        ActiveDto activeDto = new ActiveDto();
        activeDto.setId(101L);
        activeDto.setCategory(ActiveTypeEnum.HOTEL_ORDER.getCode());
        activeDto.setCategoryDetail(ActiveTypeEnum.HOTEL_ORDER.getCode());
        activeDto.setRewardType(RewardTypeEnum.COUPON.getCode());
        activeDto.setReward("No213215632" + RewardTypeEnum.COUPON.getDesc());

        ActiveOrderDto activeOrderDto = new ActiveOrderDto();
        activeOrderDto.setMemberId(11L);
        activeOrderDto.setOrderType("1");
        activeOrderDto.setOrderNo("202105111"); AbstractActiveHandleStrategy impl = activeHandleFactory.getImpl(activeDto.getCategoryDetail()); impl.handle(activeOrderDto, activeDto); }}Copy the code

The output of the unit test is as follows:

conclusion

So our code optimization, AbstractActiveHandleStrategy. The handle () method of logic is fixed, thus determine the logic frame of good code, subsequent new orders or new types of reward, the code is no need to change to this place, You only need to add the subclass of the corresponding interface, in accordance with the open and closed principle. I’ve written a lot of code, but the key is the Handle method. It seems that the template design pattern is not very simple, and the template and strategy two patterns can be well combined, the final effect is also good, in fact, as long as you write slightly more complex code, both have their use scenarios. In addition, we can see from the above that design patterns are not separated, and complex business code implementation may conform to multiple design patterns. As programmers, we need to abstract the business and implement it in more, better, and reasonable patterns. In addition, we must think a lot when learning, a lot of hands, do not develop the habit of high ambition and low hand, to know the paper come zhongjue shallow, must know this to practice the truth. I will continue to update the design patterns series, and the next one may be about pipeline patterns, so stay tuned! Ha ha!

This article was first published in Sam’s personal blog. It is forbidden to reprint it without authorization.