Задайте общий интерфейс правилам доставки

JavaBeginner
Практиковаться сейчас

Введение

Небольшому пункту отправки посылок нужны сопоставимые предложения от разных правил доставки. Согласно подготовленным данным, для посылки весом 500 граммов Town стоит 600 центов, а Steps — 200. Вы реализуете эти обычные предложения, затем исследуете границу веса, меняющую цену.

Лабораторная предполагает знание классов, конструкторов, методов, списков и базового полиморфизма Java. Вы познакомитесь с интерфейсом и явным результатом расчёта цены. Парсер и скрипт запуска подготовлены; реальные решения о применимости и цене реализуете вы. Время оценено автором с учётом чтения и обычных исправлений.

Задайте публичный контракт правилу фиксированной цены

На этом шаге вы научите правило Town возвращать настоящую цену для подходящей посылки и отклонять превышение веса.

Откройте вкладку WebIDE, затем выберите в меню Terminal → New Terminal, чтобы открыть встроенный терминал. Выполняйте следующие команды в нём. Они работают в одной оболочке; cd меняет её рабочий каталог. Если вы заново откроете терминал, повторите команду cd. JDK и каркас исходного кода подготовлены; устанавливать инструмент сборки не нужно.

cd /home/labex/project/parcels

cat показывает файл, не изменяя его. Прочитайте небольшой файл тарифов и интерфейс:

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

Файл тарифов имеет формат TSV: поля разделяются табуляцией, заголовка нет. Строка flat содержит type, ID правила, максимальный вес в граммах и цену в центах. Строка step добавляет размер ступени перед ценой за ступень. Здесь Town допускает до 2,000 граммов и стоит 600 центов; Steps допускает до 5,000 и берёт 200 центов за каждые начатые 500 граммов.

interface описывает методы, доступные вызывающему коду без знания класса реализации. ShippingRule обещает ID и расчёт цены для посылки. Класс объявляет implements ShippingRule и предоставляет оба публичных метода. Вызов rule.quote(parcel) выбирает реализацию данного объекта во время выполнения. Не нужны приведения типов или ветвления по названию перевозчика.

Публичный контракт включает ShippingRule.id() и ShippingRule.quote(Parcel), а также Quote.eligible() и Quote.cents(). RuleFactory.flat(String, int, long) и RuleFactory.stepped(String, int, int, long) создают объекты правил; QuoteEngine.bestRules(Parcel, List<ShippingRule>) возвращает ID всех правил с минимальной ценой. Эти сигнатуры позволяют одному вызывающему коду использовать разные реализации. Вы можете иначе организовать классы, в том числе изменить RuleFactory для их создания. Сохраните публичные методы и предоставленный формат команд и вывода; внутренние имена классов фабрики и выбранный редактор не оцениваются.

Поддерживаются целые веса от 1 до 1,000,000 граммов. Ограничения веса и размеры ступеней также являются целыми от 1 до 1,000,000; фиксированная цена или цена одной ступени — целое от 0 до 1,000,000 центов. Подходящее предложение за ноль центов допустимо. Оно отличается от Quote.unavailable(), у которого eligible() равно false, а хранимая сумма не должна участвовать в сравнении. Рассчитанные суммы хранятся в long, поэтому большое допустимое произведение не переполнит 32-битный int. Все тарифы, персонажи и названия посылок синтетические; это не актуальные цены реальных перевозчиков.

Parcel уже хранит ID, отображаемое имя и граммы в закрытых полях с методами только для чтения. Quote.priced(cents) создаёт подходящее предложение; Quote.unavailable() — неподходящее. Конструктор FlatRule сохраняет параметры. Дополните его метод quote: сначала проверяйте применимость, затем возвращайте цену. return немедленно завершает метод; благодаря > посылка точно на границе остаётся допустимой.

Следующая команда заменяет файл исходного кода Java. cat копирует текст между двумя строками JAVA в путь после >; кавычки у начального маркера запрещают подстановки оболочки в Java-коде. Вставьте блок целиком, включая завершающий JAVA на отдельной строке. Можно также изменить этот файл в WebIDE, сохранить его и вернуться во встроенный терминал. Если неполная вставка оставила приглашение продолжения ввода, нажмите Ctrl+C и вставьте весь блок заново.

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 выполняет предоставленный скрипт запуска из текущего каталога. Он компилирует src/*.java командой javac -encoding UTF-8 во временный каталог и запускает Java с кодировкой текста UTF-8. Каждый запуск заново компилирует сохранённые исходники, не используя старые файлы классов. Ошибка компиляции указывает файл и строку: исправьте файл, сохраните и повторите команду. Неверный путь вызывает файловую ошибку; повторите указанный cd и проверьте имя, не сбрасывая среду.

./quote.sh quote 500

Команда quote принимает вес в граммах и показывает ID каждого правила с суммой в центах или UNAVAILABLE. Town теперь должен показывать 600. Steps пока показывает 0, потому что его начальный метод намеренно не завершён; это не настоящее бесплатное предложение.

Проверьте максимум и вес на один грамм больше:

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

Town меняется с 600 на UNAVAILABLE. Вы сохранили включённую верхнюю границу без изменения data/rates.tsv. Проверьте шаг, прежде чем продолжить.

Оплачивайте каждую начатую ступень веса

На этом шаге вы реализуете второе правило через тот же интерфейс с целочисленной арифметикой вместо денежных вычислений с плавающей точкой.

Продолжайте во встроенном терминале WebIDE в /home/labex/project/parcels. Каждая начатая ступень в 500 граммов стоит 200 центов: 1–500 граммов занимают одну ступень, 501–1,000 — две, 1,001–1,500 — три. Поэтому 500 граммов стоят 200 центов, а 501 — 400. Обычное целочисленное деление отбрасывает остаток: 501 / 500 равно 1, что занизит цену.

Для положительного веса (grams + stepGrams - 1L) / stepGrams округляет количество ступеней вверх. L заставляет вычисление использовать long; умножение количества на stepCents тоже использует long. Максимальный вес проверяется первым, поэтому посылка вне обслуживания не получит вводящую в заблуждение цену.

Замените весь файл ступенчатого правила этой реализацией, используя тот же here-document, что и раньше:

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

Ожидаемый вывод; столбцы разделены табуляцией:

Town 600
Steps 200

Теперь пересеките границу ступени и границу применимости:

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

При 501 грамме Steps показывает 400. При 5,001 обе цены становятся UNAVAILABLE: вес допустим, но предложений нет; посылка не является недопустимой. Намеренно попробуйте ноль граммов:

./quote.sh quote 0

Эта команда ожидаемо завершается ошибкой INVALID_WEIGHT SAMPLE с кодом выхода 2. Она не меняет исходники или тарифы. Для восстановления достаточно указать допустимый вес:

./quote.sh quote 1500

Оба правила теперь показывают 600. Получилось настоящее равенство минимальных цен, которое вы обработаете в следующей лабораторной. Применимость и цена определяются внутри заменяемых объектов правил, а вызывающий код по-прежнему использует только интерфейс.

Резюме

Вы реализовали два правила за одним интерфейсом Java, отделили применимость от целых сумм в центах и проверили точные границы и округление начатых ступеней. Один вызывающий код теперь получает осмысленные предложения, не зная конкретную реализацию.