Введение
Небольшому пункту отправки посылок нужны сопоставимые предложения от разных правил доставки. Согласно подготовленным данным, для посылки весом 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, отделили применимость от целых сумм в центах и проверили точные границы и округление начатых ступеней. Один вызывающий код теперь получает осмысленные предложения, не зная конкретную реализацию.



