Write different profit sharing rules using simple factories

Posted by luv2climb on Sun, 07 Jul 2019 23:27:40 +0200

My mailbox: <kco1989@qq.com>.
Welcome to reprint. Please indicate the website for reprinting. http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
The code is fully hosted github Students in need download it by themselves

Introduction

The following problems are encountered in the work project.
The company has a product that can be sold to different agents. But different agents have different ways to distribute profits. There are several ways to distribute profits.

  1. For each fixed income of 1 yuan, the agent's income is 1.00

  2. For each return of 0.1%, fill in the agent's return of 0.1%.

  3. If the rate of return is 0.1% and the fixed income is 1 yuan, the agent's income is 0.1%+1.00.

  4. The rate of return per transaction is 0.1%, the top is 3 yuan, and the bottom is 1 yuan, then the agent's income is 1.00-0.1%-3.00.

  5. Gradient fraction such as 0.1% < 10 000 < 0.2% < 20 000 < 0.3% < 30 000 < 0.5%.

    • Less than 10,000 at 0.1% profit

    • Less than 2000 according to 0.2% profit

    • Less than 30,000 according to 0.3% profit

    • More than 30,000 according to 0.5% profit

However, it is found that the company's project code is hard-coded. Hard parsing is not conducive to maintaining that all the rules need to use parameter information in one class.

It's no use saying more. Let's feel the code in the project.

public class AgentShareRule {
    private Integer profitType; // Distribution type
    private BigDecimal perFixIncome;    //First, the parameters used in four cases
    private BigDecimal perFixInrate;    //Second, third, the parameters used in four cases
    private BigDecimal safeLine;        //The parameters used in the third case
    private BigDecimal capping;            //The parameters used in the third case
    private BigDecimal ladder1Rate;    //The parameters used in the fifth case
    private BigDecimal ladder1Max;//The parameters used in the fifth case
    private BigDecimal ladder2Rate;//The parameters used in the fifth case
    private BigDecimal ladder2Max;//The parameters used in the fifth case
    private BigDecimal ladder3Rate;//The parameters used in the fifth case
    private BigDecimal ladder3Max;//The parameters used in the fifth case
    private BigDecimal ladder4Rate;//The parameters used in the fifth case
    private BigDecimal ladder4Max;//The parameters used in the fifth case
    // Omit part of the code and getter,setter method
}

If the distribution rules change or new ones are added, the code will not know how to maintain it.
Therefore, based on this situation, I rewrote the profit-sharing rule according to the given rule.

Goals to be achieved after rewriting

  1. Modifying the original rules will not affect other rules

  2. New profit-sharing rules that can be easily added

Rewrite examples

Because the rewriting program is only a test program, so the money involved is double. In practical application, to consider accuracy, it needs to be changed to BigDecimal.

Distribution interface

public interface ProfitRole {
    double getProfit(double money);
}

The profit-sharing interface is very simple, that is, to pass in the amount of profit needed, and then calculate the return of profit.

Distribution type

public enum ProfitType {
    /**
     * For each fixed income of 1 yuan, the agent's income is 1.00.
     */
    FIXED_INCOME("^" + ProfitType.realNumber + "$"),
    /**
     * If the rate of return is 0.1%, the agent's return is 0.1%.
     */
    FIXED_RATE("^"+ ProfitType.rateNumber +"$"),
    /**
     * If the rate of return is 0.1% and the fixed income is 1 yuan, the agent's income is 0.1%+1.00.
     */
    FIXED_RATE_AND_FIXED_INCOME("^"+ ProfitType.rateNumber  + "\\+" + ProfitType.realNumber + "$"),
    /**
     * The return of each transaction is 0.1%, the top is 3 yuan, and the bottom is 1 yuan, the agent's income is 1.00-0.1%-3.00;
     */
    FIXED_RATE_AND_UPPER_LIMIT("^"+ ProfitType.realNumber + "~" + ProfitType.rateNumber + "~" + ProfitType.realNumber  + "$"),
    /**
     * Gradient fraction such as 0.1% < 10 000 < 0.2% < 20 000 < 0.3% < 30 000 < 0.5%.
     * Less than 10,000 at 0.1% profit
     * Less than 2000 according to 0.2% profit
     * Less than 30,000 according to 0.3% profit
     * More than 30,000 according to 0.5% profit
     */
    GRADIENT_RATE("^"+ ProfitType.rateNumber+"(<"+ ProfitType.realNumber+"<"+ ProfitType.rateNumber+")+$");

    // Uncaptured Matched Positive Real Number
    private static final String number = "(?:(?:[1-9]+\\d*)|(?:\\d))(?:\\.\\d+)?";
    // Capturing Matched Positive Real Numbers
    private static final String realNumber = "(" + number + ")";
    // Capture matching percentage
    private static final String rateNumber = realNumber + "%";

    public static Pattern getNumberPattern(){
        return Pattern.compile(number);
    }
    private String pattern;
    ProfitType(String pattern) {
        this.pattern = pattern;
    }

    public Pattern getPattern() {
        return Pattern.compile(this.pattern);
    }
}
  1. The splitting type is an enumeration class, and the constructor parameter is a splitting expression pattern.

  2. ProfitType contains three regular expressions:

    • number matches real numbers, but does not capture

    • realNumber matches the real number and captures it

    • rateNumber matches percentages, capturing only real numbers, not percentages

  3. Five enumeration values correspond to five profit-sharing rules

    • FIXED_INCOME: $1 for each fixed income, then fill in the agent income of 1.00

    • FIXED_RATE: For each return of 0.1%, fill in the agent's return of 0.1%.

    • FIXED_RATE_AND_FIXED_INCOME: If the rate of return is 0.1% and the fixed income is 1 yuan, the agent's income is 0.1%+1.00.

    • FIXED_RATE_AND_UPPER_LIMIT: The rate of return per transaction is 0.1%, the top is 3 yuan, the bottom is 1 yuan, and the agent's income is 1.00~0.1%~3.00.

    • GRADIENT_RATE: Gradient fraction such as 0.1% < 10 000 < 0.2% < 20 000 < 0.3% < 30 000 < 0.5%.

Profit Sharing Realization Class

At present, because there are five profit-sharing rules, each corresponding to an implementation class, there are five profit-sharing implementation classes, which correspond to the five profit-sharing rules of ProfitType one by one.

  • FIXED_INCOME : FixedIncomeRole

  • FIXED_RATE : FixedRateRole

  • FIXED_RATE_AND_FIXED_INCOME : FixedRateAndFixedIncomeRole

  • FIXED_RATE_AND_UPPER_LIMIT : FixedRateAndUpperLimitRole

  • GRADIENT_RATE : GradientRateRole

  • The implementation class of profit sharing is relatively simple, so I don't post code here. I [github] need it.( https://github.com/kco1989/ma... download

    Distribution Factories

    Finally, a factory is created to create different profit-sharing implementation classes according to different ProfitType s and profit expressions.

    public final class ProfitRoleFactory {
    
        public static ProfitRole parseProfitRole(ProfitType type, String expression){
            Matcher matcher = type.getPattern().matcher(expression);
            if (!matcher.matches()){
                throw new RuntimeException("Incompatibility in the expression of profit sharing" + type + "Rules.");
            }
            switch (type){
                case FIXED_INCOME:
                    return new FixedIncomeRole(Double.parseDouble(matcher.group(1)));
                case FIXED_RATE:
                    return new FixedRateRole(Double.parseDouble(matcher.group(1)));
                case FIXED_RATE_AND_UPPER_LIMIT:
                    return new FixedRateAndUpperLimitRole(Double.parseDouble(matcher.group(1)),
                            Double.parseDouble(matcher.group(2)), Double.parseDouble(matcher.group(3)));
                case FIXED_RATE_AND_FIXED_INCOME:
                    return new FixedRateAndFixedIncomeRole(Double.parseDouble(matcher.group(1)),
                            Double.parseDouble(matcher.group(2)));
                case GRADIENT_RATE:
                    List<Double> rates = new ArrayList<>();
                    List<Double> limits = new ArrayList<>();
                    Pattern numberPattern = ProfitType.getNumberPattern();
                    Matcher numberMatcher = numberPattern.matcher(expression);
                    for (int i = 0;numberMatcher.find();i ++){
                        if (i % 2 == 0){
                            rates.add(Double.parseDouble(numberMatcher.group()));
                        }else{
                            limits.add(Double.parseDouble(numberMatcher.group()));
                        }
                    }
                    return new GradientRateRole(rates, limits);
                default: //never come here
                    return null;
            }
        }
    
        public static double getProfit(double money, ProfitType type, String expression){
            ProfitRole profitRole = parseProfitRole(type, expression);
            if (profitRole != null){
                return profitRole.getProfit(money);
            }
            return 0;
        }
    }

    Finally, create a test class test

    public class TestProfitRole {
    
        private static final List<Double> testDate = Arrays.asList(100.0,200.0,300.0,400.0,700.0,
                1000.0,2000.0,3000.0,7000.0,
                10000.0, 20000.0, 30000.0, 70000.0);
        @Test
        public void testFixedIncome(){
            for (double data : testDate){
                double profit = ProfitRoleFactory.getProfit(data, ProfitType.FIXED_INCOME, "1.00");
                Assert.assertEquals(1.00, profit, 0.00001);
            }
        }
    
        @Test
        public void testFixedRate(){
            for (double data : testDate){
                double profit = ProfitRoleFactory.getProfit(data, ProfitType.FIXED_RATE, "0.1%");
                Assert.assertEquals(data * 0.1 * 0.01, profit, 0.00001);
            }
        }
    
        @Test
        public void testFixedRateAndFixedIncome(){
            for (double data : testDate){
                double profit = ProfitRoleFactory.getProfit(data, ProfitType.FIXED_RATE_AND_FIXED_INCOME, "0.63%+3.00");
                Assert.assertEquals(data * 0.63 * 0.01 + 3.0, profit, 0.00001);
            }
        }
    
        @Test
        public void testFixedRateAndUpperLimit(){
            for (double data : testDate){
                double profit = ProfitRoleFactory.getProfit(data, ProfitType.FIXED_RATE_AND_UPPER_LIMIT, "1.00~0.1%~3.00");
                double actual = data * 0.1 * 0.01;
                if (actual < 1.0){
                    actual = 1.0;
                }
                if (actual > 3.0){
                    actual = 3.0;
                }
                Assert.assertEquals(actual, profit, 0.00001);
            }
        }
    
        @Test
        public void testGradientRate(){
            for (double data : testDate){
                double profit = ProfitRoleFactory.getProfit(data, ProfitType.GRADIENT_RATE, "0.1%<1000<0.2%<5000<0.3%<15000<0.5%");
                if (data < 1000){
                    Assert.assertEquals(data * 0.01 * 0.1, profit, 0.00001);
                }else if (data < 5000){
                    Assert.assertEquals(data * 0.01 * 0.2, profit, 0.00001);
                }else if(data < 15000){
                    Assert.assertEquals(data * 0.01 * 0.3, profit, 0.00001);
                }else{
                    Assert.assertEquals(data * 0.01 * 0.5, profit, 0.00001);
                }
            }
        }
    }

    Epilogue

    Now, looking back at our goals, our goals are:

    1. Modifying the original rules will not affect other rules

    2. New profit-sharing rules that can be easily added

    Goal 1 is achieved, but Goal 2 is somewhat unsatisfactory. For example, if we want to add a new profit sharing rule, we need to change the ProfitType and ProfitRoleFactory classes at the same time, which do not achieve complete decoupling. But there are some improvements compared with the previous code.

    Let's leave this question for you to practice first, and then I'll improve the program when I have time.

    Topics: Java less github