How to refactor object oriented 83 lines of code

Keywords: Alibaba Cloud CODE

Introduction: the 3rd 83 line code challenge in 2021 hosted by Alibaba cloud has ended. More than 20000 people watched, nearly 4000 people participated, and 85 teams came in groups. The competition adopts game breakthrough playing method, integrating meta universe science fiction and script killing elements, so that a group of developers have a lot of fun. Author: excellent contestant with 83 lines of code.

Topic introduction

Our system:

  • Each Item has a SellIn, which means that the Item must be sold within the number of days represented by the value.
  • All goods have a Value, which represents the Value of the goods.
  • Every day, the SellIn Value and Value value of all products are reduced by 1.
  • Once the remaining days of sales have passed, the value will decline at a double rate.
  • Aged Wine is a special commodity. The longer it is kept, the higher its value. And the value will double after the remaining days of sales.
  • The value of goods will never be less than 0, nor more than 50.
  • Sulfuras is a legendary commodity. Its remaining days of sales and quality value will not change.
  • The closer the Show Ticket is to the performance day, the value increases. 10 days before the performance, the value increased by 2 points every day; Five days before the performance, the value increased by 3 points every day. But once the show day is over, the value immediately becomes 0.
  • Recently, we purchased Cure because of disasters. The depreciation rate of Cure is twice that of ordinary goods, so we need to upgrade our system as soon as possible.

Problem solving ideas:

This problem is a very typical object-oriented problem. From the description of the problem, it is to realize the function of calculating the change of commodity value over time. First of all, you can delete the preset updateValue method of the topic, leaving only the signature... The eyeful of if else and countless indents are basically unreadable, so it's difficult to write this pile of big guys

Implementation method:

package store;

// Please don't modify the class name.
public class Store {
    Item[] items;

    // Please don't modify the signature of this method.
    public Store(Item[] items) {
        this.items = items;
    }

    // Please don't modify the signature of this method.
    public void updateValue() {
       
    }

From the non modifiable signature of the Store class, it can be seen that the items are built by the tester and the update value is called any time to trigger the modification. Therefore, there should be no need to modify the abstract of the Item class. We only need to process it according to its name, sellIn and value.

Since the Item does not need to be moved, we naturally need a business processing logic to change the daily changes of the Item, and its processing logic will be adjusted for special goods.

Therefore, the main implementation of this problem is to realize a unified abstract logic and business overloading of several special commodities.

1. Confirm Abstract Logic

First, we put aside the description of the differences of different commodities, first confirm the commonality of all commodities, and get the following characteristics

• sellIn and value represent the remaining days and value respectively. They are common attributes of all commodities and have been defined in Item (although getter s and setter s are not so OO without use)

• sellIn and value change every day

• once the sales days are exceeded (sellin < 0), value will change in another way (double decline by default)
• the value of goods will never be less than 0 and will never exceed 50.

According to the above four points, we can simply complete the change logic of an ordinary commodity. Here, we first define this abstract class, called AbstractNextDayProcessor, and define a method process(Item item), which represents the operation of the next day, corresponding to Store::updateValue

package store;

public abstract class AbstractNextDayProcessor {
    public void process(Item item) {
        //sellIn decreased by 1
        item.sellIn--;

        if (item.sellIn >= 0) {
            //Not expired
            modifyValue(item, -1);
        } else {
            //Out of date
            modifyValue(item, -2);
        }
    }

    //Guarantee no more than 50 and no less than 0
    private void modifyValue(Item item, int deltaValue) {
        item.value = Math.min(Math.max(item.value + deltaValue, 0), 50);
    }

So far, we have implemented a common commodity change logic.

2. Confirm abstractable business

Let's observe the characteristics of special goods to determine the part that needs to be abstracted in the above logic:
• Aged Wine sellIn is consistent with that of ordinary commodities, but the change of value is opposite;
• Sulfuras sellIn does not change and value does not change;
• Show Ticket sellIn is consistent with ordinary goods, and value has a complex and unique change mode;
• Cure sellIn is consistent with ordinary commodities, and the change of value is twice that of ordinary commodities;
It can be seen from the bold part above that for different special commodities, whether sellIn changes or value changes, there will be special situations. In view of this situation, the corresponding logic needs to be extracted into a method. In order to overload each special implementation class, except the magic hammer, sellIn follows unified logic, so sellIn changes can be in the base class The default implementation is implemented in, and the value change can be seen to be different, which can be treated as an abstract method.
The changed abstract classes are as follows:

package store;

public abstract class AbstractNextDayProcessor {
public void process(Item item) {
        item.sellIn += getSellInIncrement();

if (item.sellIn >= 0) {
//Not expired
            modifyValue(item, getValueIncrementInDate());
        } else {
//Out of date
            modifyValue(item, getValueIncrementOutOfDate());
        }
    }

//value change without expiration
protected abstract int getValueIncrementInDate();

//Change in value at expiration
protected abstract int getValueIncrementOutOfDate();

//The change of sellIn every day is - 1 for most commodities
protected int getSellInIncrement() {
return -1;
    }

//Guarantee no more than 50 and no less than 0
private void modifyValue(Item item, int deltaValue) {
        item.value = Math.min(Math.max(item.value + deltaValue, 0), 50);
    

At this time, we can start to implement the processing logic of each special commodity. It should be noted that the performance ticket is special. The calculation of its value before expiration depends on item and sellIn, and the change after expiration is not a fixed value, but cleared. The so-called clearing means that its change amount is - item.value. Therefore, the two abstract methods of value change here need to provide additional parameters The number of items is adjusted as follows:

protected abstract int getValueIncrementInDate(Item item);

protected abstract int getValueIncrementOutOfDate(Item item);

Based on the abstract class, it implements the implementation class of each special commodity, as well as the implementation class of an ordinary commodity.

//wine
public class AgedWineNextDayProcessor extends AbstractNextDayProcessor {
//Every day, value + 1
@Override
protected int getValueIncrementInDate(Item item) {
return 1;
    }

//Value + 2 if expired
@Override
protected int getValueIncrementOutOfDate(Item item) {
return 2;
    }
}

//Specific drug
public class CureNextDayProcessor extends AbstractNextDayProcessor {
//Every day, the value is - 2
@Override
protected int getValueIncrementInDate(Item item) {
return -2;
    }

//Expired, value - 4
@Override
protected int getValueIncrementOutOfDate(Item item) {
return -4;
    }
}

//Performance ticket
public class ShowTicketNextDayProcessor extends AbstractNextDayProcessor {
@Override
protected int getValueIncrementInDate(Item item) {
//Note that because the abstract class has sellIn --, the judgment ranges here are [0,4], [5-9], [10 -]. If the value is calculated first and then --, < needs to be changed to<=
if (item.sellIn < 5) {
return 3;
        }

if (item.sellIn < 10) {
return 2;
        }

return 1;
    }

@Override
protected int getValueIncrementOutOfDate(Item item) {
return -item.value;
    }
}

//Magic hammer
public class SulfurasNextDayProcessor extends AbstractNextDayProcessor {
//Constant value
@Override
protected int getValueIncrementInDate(Item item) {
return 0;
    }

//Constant value
@Override
protected int getValueIncrementOutOfDate(Item item) {
return 0;
    }

//sellIn does not change
@Override
protected int getSellInIncrement() {
return 0;
    }
}

//General merchandise
public class OtherNextDayProcessor extends AbstractNextDayProcessor {
@Override
protected int getValueIncrementInDate(Item item) {
return -1;
    }

@Override
protected int getValueIncrementOutOfDate(Item item) {
return -2;
    }
}

So far, the core business logic has been completed.

3. Build judgment process

All the tools are in place. The next step is to consider how to enable different processors to correctly handle their corresponding commodities. From the item definition and title, we can see that commodities are distinguished by different names. Here are several implementation methods. Personally, I prefer to let each business logic judge by itself. Similar to the filter chain of Spring MVC, that is to construct a The processor chain starts from the first processor. If it can be processed, it will be processed. If it cannot be processed, it will be handed over to the next one until it is handed over to the last supporting base (ordinary commodity). Naturally, the process method needs to add a step to judge whether the name of this item belongs to its own processing scope. The AbstractNextDayProcessor class is adjusted as follows:

package store;

public abstract class AbstractNextDayProcessor {
    private final String name;

    public AbstractNextDayProcessor(String name) {
        this.name = name;
    }

    public boolean process(Item item) {
        //name is not null, indicating that it is a special processor. Different from item, it means that this item should not be processed
        if (name != null && !name.equals(item.name)) {
            return false;
        }

        item.sellIn += getSellInIncrement();

        if (item.sellIn >= 0) {
            //Not expired
            modifyValue(item, getValueIncrementInDate(item));
        } else {
            //Out of date
            modifyValue(item, getValueIncrementOutOfDate(item));
        }

        return true;
    }

    protected abstract int getValueIncrementInDate(Item item);

    protected abstract int getValueIncrementOutOfDate(Item item);

    protected int getSellInIncrement() {
        return -1;
    }

    //Guarantee no more than 50 and no less than 0
    private void modifyValue(Item item, int deltaValue) {
        item.value = Math.min(Math.max(item.value + deltaValue, 0), 50);
    }
}

The private attribute name is added to identify the identifiable item name. If the name is empty, it means that everything can be processed. If it is not empty, the item name must match itself before execution. The return value of true means that the processing is successful. Naturally, other implementation classes are required to implement the corresponding unique constructor by default, such as:

package store;

public class SulfurasNextDayProcessor extends AbstractNextDayProcessor {
    public SulfurasNextDayProcessor() {
        super("Sulfuras");
    }

    //Constant value
    @Override
    protected int getValueIncrementInDate(Item item) {
        return 0;
    }

    //Constant value
    @Override
    protected int getValueIncrementOutOfDate(Item item) {
        return 0;
    }

    //sellIn does not change
    @Override
    protected int getSellInIncrement() {
        return 0;
    }
}

Finally, we need to implement the execution entry updateValue in the Store

First, build a processor chain, and ensure that OtherNextDayProcessor is placed last, because it will process all item s and return true

Then cycle each item, and then cycle the processor chain. When the first one returns true, the processing of the current item is ended. The code is as follows

package store;

import java.util.Arrays;
import java.util.List;

// Please don't modify the class name.
public class Store {
    Item[] items;

    private final List<AbstractNextDayProcessor> processors;

    // Please don't modify the signature of this method.
    public Store(Item[] items) {
        this.items = items;

        processors = Arrays.asList(
                new AgedWineNextDayProcessor(),
                new CureNextDayProcessor(),
                new ShowTicketNextDayProcessor(),
                new SulfurasNextDayProcessor(),
                new OtherNextDayProcessor());
    }

    // Please don't modify the signature of this method.
    public void updateValue() {
        for (Item item : items) {
            for (AbstractNextDayProcessor processor : processors) {
                if (processor.process(item)) {
          //If true is returned, it means that the correct processor has been found for processing and jumps out of the inner loop
                    break;
                }
            }
        }
    }
}

At this time, you can hurry up to submit. You can see that the correctness verification is 100 points, and points are deducted for other code specifications and complexity. You can use the plug-in to check and adjust.

Option 2:

According to the above methods, I think the object-oriented idea is the best implementation, but the complexity score can not reach the full score. Finally, it is judged that the amount of code is too large.

If you want to hit 100 points, instead of adding the name field in the Processor, you can use the map in the Store to maintain the Processor. The code is as follows:

package store;

import java.util.HashMap;
import java.util.Map;

// Please don't modify the class name.
public class Store {
    private final Map<String, AbstractNextDayProcessor> processors;

    private final AbstractNextDayProcessor defaultProcessor;

    Item[] items;

    // Please don't modify the signature of this method.
    public Store(Item[] items) {
        this.items = items;

        processors = new HashMap<>();
        processors.put("Age Wine", new AgedWineNextDayProcessor());
        processors.put("Cure", new CureNextDayProcessor());
        processors.put("Show Ticket", new ShowTicketNextDayProcessor());
        processors.put("Sulfuras", new SulfurasNextDayProcessor());

        defaultProcessor = new OtherNextDayProcessor();
    }

    // Please don't modify the signature of this method.
    public void updateValue() {
        for (Item item : items) {
            processors.getOrDefault(item.name, defaultProcessor).process(item);
        }
    }
}

Personally, I don't think this approach is very good because it will cause the processor judgment logic to be coupled with the Store (although scheme 1 is also coupled in the current implementation, in the actual business, the list of scheme 1 can be built by dependency injection).

last

For several test taking tips, don't hesitate to submit times. Timely submission can be checked quickly with the help of test cases. In addition, before the competition, the competition rules are understood through the preliminary competition of the previous levels. There are restrictions on the number of submissions this year. The means to increase the number of submissions should be confirmed in advance.
At present, all levels of the competition are open for experience. Domain name and address: https://code83.ide.aliyun.com/ , welcome.

Recommended reading

1,Play script with code? Official analysis of the plot of the 3rd 83 line code competition
2,No algorithm, no Java, this algorithm problem is difficult?

Welcome to the cloud effect, the new DevOps platform in the cloud native era, and greatly improve the R & D efficiency through the new cloud native technology and R & D model. At present, the cloud effect public cloud basic version is not limited to 0 yuan.

Posted by khayll on Mon, 29 Nov 2021 15:02:12 -0800