Give Shipping Rules the Same Interface

JavaBeginner
Practice Now

Introduction

A small parcel desk needs comparable offers from different shipping rules. For a 500-gram parcel, the prepared rate data describes Town at 600 cents and Steps at 200 cents. You will make those normal offers real, then examine the weight boundary that changes a price.

This lab assumes Java classes, constructors, methods, lists and basic polymorphism. It introduces an interface and an explicit quotation result. The parser and launcher are supplied; you implement the actual eligibility and price decisions. Author-estimated time includes reading and ordinary corrections.

Give a Flat Rule a Public Contract

In this step, you will make the Town rule return a real eligible price and reject an overweight parcel.

Open the WebIDE tab, then choose Terminal → New Terminal from its menu to open the integrated terminal. Use that WebIDE terminal for the commands below. Each command runs in the same shell; cd changes that shell's working directory. If you reopen a terminal, repeat the cd command. The prepared JDK and source framework do not require a build-tool installation.

cd /home/labex/project/parcels

cat displays a file without changing it. Read the small rate file and interface:

cat data/rates.tsv
cat src/ShippingRule.java

The rate file is TSV: fields are separated by tab characters, with no header. A flat row is type, rule ID, maximum grams, cents. A step row adds a step size before the per-step cents. Here Town has a 2,000-gram maximum and charges 600 cents; Steps has a 5,000-gram maximum and charges 200 cents per started 500 grams.

An interface describes methods callers may use without knowing the implementation class. ShippingRule promises an ID and a quote for a parcel. A class declares implements ShippingRule and supplies both public methods. Calling rule.quote(parcel) chooses the implementation belonging to that object at runtime. We need no casts or carrier-name branches.

The public contract is ShippingRule.id() and ShippingRule.quote(Parcel), with Quote.eligible() and Quote.cents(). RuleFactory.flat(String, int, long) and RuleFactory.stepped(String, int, int, long) create rule objects; QuoteEngine.bestRules(Parcel, List<ShippingRule>) returns every winning rule ID. These signatures let the same caller use different implementations. You may organize implementation classes differently, including changing RuleFactory to construct them. Keep these public methods and the supplied command/output format working; class names inside the factory and your editor are not assessed.

Supported parcel weights are integers from 1 through 1,000,000 grams. Rule maximums and step sizes are integers from 1 through 1,000,000; each flat price or step price is an integer from 0 through 1,000,000 cents. A zero-cent eligible offer is valid. It differs from Quote.unavailable(), whose eligible() is false and whose stored cents must not participate in comparison. A long stores calculated cents, so a large valid product does not overflow a 32-bit int. All rates, people and parcel names are synthetic; these are not live carrier prices.

Parcel already keeps its ID, display name and grams private with read-only methods. Quote.priced(cents) creates an eligible result; Quote.unavailable() creates an ineligible result. The FlatRule constructor stores its parameters. Complete its quote method by checking eligibility before returning a price. return ends the method immediately; > leaves an exactly-at-limit parcel eligible.

The following command replaces one Java source file. cat copies the text between the two JAVA lines into the path after >; the quoted opening marker keeps shell substitutions out of the Java code. Paste the entire block, including the final JAVA on its own line. You can instead edit the same file in WebIDE, save it, and return to the WebIDE terminal. If you see a continuation prompt after an incomplete paste, press Ctrl+C, then paste the complete block again.

cat > src/FlatRule.java <<'JAVA'
public final class FlatRule implements ShippingRule {
    private final String id;
    private final int maxGrams;
    private final long cents;
    public FlatRule(String id, int maxGrams, long cents) {
        this.id = id; this.maxGrams = maxGrams; this.cents = cents;
    }
    public String id() { return id; }
    public Quote quote(Parcel parcel) {
        if (parcel.grams() > maxGrams) return Quote.unavailable();
        return Quote.priced(cents);
    }
}
JAVA

./quote.sh runs the supplied launcher in the current directory. It compiles src/*.java with javac -encoding UTF-8 into a temporary directory and starts Java with UTF-8 text encoding. A new run recompiles your saved sources, so no stale class files are reused. A compilation error includes a source filename and line number: correct that file, save it, and rerun the same command. An incorrect path produces a file error; repeat the documented cd and check the filename rather than resetting the environment.

./quote.sh quote 500

The quote command takes a weight in grams and shows each rule ID with cents or UNAVAILABLE. Town should now show 600. Steps still shows 0 because its starter method is intentionally incomplete; do not interpret that as a real free offer.

Try the maximum and one gram beyond it:

./quote.sh quote 2000
./quote.sh quote 2001

Town changes from 600 to UNAVAILABLE. You have preserved the inclusive maximum without changing data/rates.tsv. Check this step before continuing.

Charge for Every Started Weight Step

In this step, you will implement the second rule through the same interface, using integer arithmetic instead of floating-point money.

Continue in the WebIDE terminal, in /home/labex/project/parcels. A started 500-gram step costs 200 cents: 1–500 grams uses one step, 501–1,000 uses two, and 1,001–1,500 uses three. Thus 500 grams costs 200 cents, while 501 costs 400 cents. Ordinary integer division truncates: 501 / 500 is 1, which would undercharge.

For positive grams, (grams + stepGrams - 1L) / stepGrams rounds the step count upward. The L makes the arithmetic use long; multiplying this count by stepCents also uses long. The maximum-weight test comes first, so a parcel outside the service never receives a misleading price.

Replace the whole step-rule file with this implementation, using the same here-document technique as before:

cat > src/StepRule.java <<'JAVA'
public final class StepRule implements ShippingRule {
    private final String id;
    private final int maxGrams, stepGrams;
    private final long stepCents;
    public StepRule(String id, int maxGrams, int stepGrams, long stepCents) {
        this.id = id; this.maxGrams = maxGrams;
        this.stepGrams = stepGrams; this.stepCents = stepCents;
    }
    public String id() { return id; }
    public Quote quote(Parcel parcel) {
        if (parcel.grams() > maxGrams) return Quote.unavailable();
        long steps = (parcel.grams() + stepGrams - 1L) / stepGrams;
        return Quote.priced(steps * stepCents);
    }
}
JAVA
./quote.sh quote 500

Expected output (columns are tab-separated):

Town 600
Steps 200

Now cross one step boundary and the eligibility boundary:

./quote.sh quote 501
./quote.sh quote 5001

At 501 grams, Steps shows 400. At 5,001 grams, both rules show UNAVAILABLE; this is a valid weight with no offer, rather than an invalid parcel. Try zero grams deliberately:

./quote.sh quote 0

This command intentionally fails with INVALID_WEIGHT SAMPLE and exit status 2. It does not change your sources or rate records. Recover simply by supplying a valid weight:

./quote.sh quote 1500

Both rules now show 600. You have a real tie to handle in the next lab. The qualification and pricing decisions live inside replaceable rule objects, while the caller still uses only the interface.

Summary

You implemented two rules behind one Java interface, kept eligibility separate from integer cents, and exercised exact limits and started-step rounding. The same caller can now obtain meaningful prices without knowing which implementation supplies them.