소개
작은 소포 접수대에서는 서로 다른 배송 규칙이 내놓는 견적을 비교해야 합니다. 준비된 요금 데이터에서 500그램 소포의 Town 요금은 600센트, Steps 요금은 200센트입니다. 이 정상적인 견적이 실제로 동작하도록 만든 뒤 가격이 바뀌는 무게 경계를 살펴봅니다.
이 실습은 Java 클래스, 생성자, 메서드, 리스트, 기본 다형성을 알고 있다고 가정합니다. 인터페이스와 조건 충족 여부를 명시한 견적 결과를 새로 배웁니다. 파서와 실행 스크립트는 제공되며 실제 조건 판단과 요금 계산은 직접 구현합니다. 작성자가 추정한 시간에는 읽기와 일반적인 오류 수정이 포함됩니다.
고정 요금 규칙에 공개 계약 부여하기
이 단계에서는 Town 규칙이 조건에 맞는 실제 가격을 반환하고 무게를 초과한 소포는 거부하도록 만듭니다.
WebIDE 탭을 열고 메뉴에서 Terminal → New Terminal을 선택하여 통합 터미널을 엽니다. 아래 명령은 이 WebIDE 터미널에서 실행하세요. 명령은 같은 셸에서 실행되며, 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그램을 허용하며, 시작한 500그램 구간마다 200센트를 청구합니다.
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센트까지의 정수입니다. 조건에 맞는 0센트 견적도 유효합니다. 이는 Quote.unavailable()과 다릅니다. 후자는 eligible()이 false이며 내부에 저장한 금액은 비교에 사용하면 안 됩니다. 계산한 센트는 long에 저장하므로 큰 유효 곱도 32비트 int를 넘쳐 흐르게 하지 않습니다. 모든 요금, 인물, 소포 이름은 합성 연습 데이터이며 실제 운송사의 실시간 요금이 아닙니다.
Parcel은 이미 ID, 표시 이름, 그램 수를 비공개 필드에 저장하고 읽기 전용 메서드를 제공합니다. Quote.priced(cents)는 조건에 맞는 결과를, Quote.unavailable()은 조건에 맞지 않는 결과를 만듭니다. FlatRule 생성자는 매개변수를 저장합니다. 가격을 반환하기 전에 조건을 확인하도록 quote 메서드를 완성하세요. return은 메서드를 즉시 종료하며, >를 사용하면 무게가 상한과 정확히 같아도 허용됩니다.
다음 명령은 Java 소스 파일 하나를 교체합니다. cat은 두 JAVA 줄 사이의 텍스트를 > 뒤 경로에 기록합니다. 시작 구분자의 따옴표는 셸이 Java 코드의 표현식을 치환하지 않게 합니다. 마지막 JAVA도 단독 줄로 포함하여 전체 블록을 붙여 넣으세요. 같은 파일을 WebIDE에서 편집하고 저장한 뒤 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로 임시 디렉터리에 컴파일하고 UTF-8 텍스트 인코딩으로 Java를 시작합니다. 실행할 때마다 저장한 소스를 다시 컴파일하므로 오래된 클래스 파일을 재사용하지 않습니다. 컴파일 오류에는 소스 파일명과 줄 번호가 나옵니다. 해당 파일을 수정하고 저장한 뒤 같은 명령을 다시 실행하세요. 잘못된 경로는 파일 오류를 일으킵니다. 환경을 초기화하지 말고 문서의 cd를 다시 실행하고 파일명을 확인하세요.
./quote.sh quote 500
quote 명령은 그램 단위 무게를 받아 각 규칙 ID와 센트 금액 또는 UNAVAILABLE을 표시합니다. 이제 Town은 600을 표시해야 합니다. Steps는 초기 메서드가 의도적으로 미완성이므로 아직 0을 표시합니다. 실제 무료 견적으로 해석하지 마세요.
상한과 그보다 1그램 무거운 경우를 시험합니다.
./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입니다. 이는 유효한 무게에 이용 가능한 견적이 없는 상태이지 잘못된 소포가 아닙니다. 의도적으로 0그램을 시험하세요.
./quote.sh quote 0
이 명령은 의도한 실패로 INVALID_WEIGHT SAMPLE을 표시하며 종료 상태는 2입니다. 소스나 요금 기록은 바뀌지 않습니다. 유효한 무게를 지정하면 바로 복구됩니다.
./quote.sh quote 1500
이제 두 규칙 모두 600을 표시합니다. 다음 실습에서 처리할 실제 최저가 동률이 생겼습니다. 조건과 가격 판단은 교체 가능한 규칙 객체 안에 있고, 호출 코드는 여전히 인터페이스만 사용합니다.
요약
하나의 Java 인터페이스 뒤에 두 규칙을 구현하고, 조건 충족 여부와 정수 센트 금액을 분리했으며, 정확한 상한과 시작한 구간의 올림을 시험했습니다. 이제 같은 호출 코드가 어떤 구현이 견적을 제공하는지 몰라도 유용한 견적을 얻습니다.



