서비스
교육 철학

LV12 객체지향 프로그래밍 연습(상속, 가상함수) - Part 2 (Inheritance, Virtual Functions 2)

기획서를 읽고 직접 설계하는 C++ 연습 · PART2

🎯
기획의 기능과 규칙을 이해하고, 이를 만족시키는 객체와 클래스 구조를 직접 설계하는 훈련입니다. 제공 main()을 사용 예시로 읽으며 구현한 동작을 확인하고, 같은 기획을 반복해서 다시 설계합니다.
main() 동작 실험실 · PART2
문제를 고르고 기획의 사용 흐름이 어떻게 진행되는지 관찰하세요. 한 단계 실행·자동 실행·초기화·추가 요청으로 기능의 결과를 확인할 수 있습니다. 실제 클래스는 아래 기획과 제공 main()을 바탕으로 직접 설계합니다.

기획서를 읽고 설계하는 연습

각 문제는 작은 서비스의 기획입니다. 먼저 사용자가 무엇을 하려는지, 어떤 정보가 필요한지, 요청 후 어떤 결과를 받아야 하는지를 읽으세요. 그 요구를 만족시키는 클래스 구성과 책임 분담은 직접 정합니다.
1.
기획 이해: 기본 기능과 규칙, 확장 요청, 사용 흐름을 읽고 서비스를 자신의 말로 설명합니다.
2.
사용 예시 확인: 제공 main()의 생성과 요청을 살펴보며 기획이 코드에서 어떻게 사용되는지 확인합니다. 첫 구현에서는 제공 main()의 이름·호출 형식·기대값을 유지합니다.
3.
직접 설계: 등장 대상, 필요한 정보, 가능한 행동과 대상 사이의 관계를 스스로 정리합니다. 내부 멤버·보조 클래스·저장 방법은 직접 선택합니다.
4.
구현과 동작 확인: 설계한 내용을 구현하고 사용 흐름의 결과와 비교합니다. 기획과 다른 동작이 나오면 원인을 찾아 고칩니다.
5.
반복과 변경: 다음 회차에는 이전 구현을 닫고 같은 기획과 main()에서 다시 설계합니다. 이후 다음 기획 변경을 적용하고 바뀐 설계 판단을 기록합니다.

문서에서 정해 주는 범위

본문은 기능, 처리 조건, 사용자에게 보이는 결과를 정합니다. 어떤 클래스에 정보를 넣을지, 어떤 객체가 요청을 전달할지, 공통 기능을 어떻게 나눌지는 학습자의 설계입니다. 각 문제 마지막의 회고는 구현을 마친 뒤 참고하며 디자인 패턴 이름을 외우거나 특정 패턴을 적용할 필요는 없습니다.
사용 흐름 예시는 기획의 의미를 확인하기 위한 사례입니다. 이어서 일어나는 행동은 앞 행동의 결과에서 진행하며, 별도 사례는 설명한 시작 조건으로 다시 진행합니다. 예시 외에도 같은 규칙을 지키는 결과가 나와야 합니다.
위 실험실에서 실제 동작을 관찰할 수 있습니다. 제공 main()은 구현해야 할 사용 방식의 예시로 남겨 두었습니다. 기획을 읽고 자신의 설계를 만든 뒤 두 자료를 결과 확인에 활용하세요.

공통 운영 규칙

•
따로 정하지 않은 거절된 요청은 기존 정보를 유지하며 처리할 수 없음을 알립니다. 조회만으로 위치·체력·잔액·주문 내용이 바뀌지 않습니다.
•
PART2의 등록 목록과 기록은 별도 지정이 없으면 최대 8개입니다. 같은 대상의 중복 등록, 없는 대상의 제거, 정원 초과는 거절합니다. 옵션 중복처럼 문제에서 허용한 경우에는 해당 규칙을 따릅니다.
•
빈 목록은 처리할 대상이 없는 결과로 마칩니다. 요청에 필요한 대상이 없으면 받아들이지 않습니다. 하나의 목록에서 대상을 제외하는 일이 그 대상의 정보 자체를 없애는 일인지는 각 기획을 따릅니다.
•
금액·시간·개수의 단위는 문제에서 정합니다. 안내 문구의 모양은 자유지만 대상·값·순서·처리 결과를 분명하게 구별할 수 있어야 합니다.
•
실제 결제·인증·배송·파일 읽기는 하지 않습니다. 모든 기능은 연습용 수치와 콘솔 안내로 표현합니다.

개발 환경과 제공 코드

C++17 콘솔 프로그램으로 문제마다 따로 작성합니다. 제공 main() 전체와 기대값을 유지하며 필요한 클래스와 헤더를 추가하세요. 결과 검사를 건너뛰지 않도록 assert가 활성화된 상태로 실행합니다. 실수 결과는 오차 0.001 이내로 비교합니다.

3단계 · 공통 행동과 실제 상태 (26~30)

문제 26. 동물원의 일과

기획 의도
동물원 직원이 등록된 동물들의 하루 일과를 시작합니다. 기본 일과는 먹이 제공과 울음소리 확인이며, 별도의 건강 점검 대상에게는 수면 점검 기록도 남깁니다.
기본 기능과 동작 규칙
•
개·고양이·새를 이름과 함께 등록합니다. 하루 일과는 등록 순서대로 진행하며 각 동물은 한 번씩 먹이를 먹고 자기 울음소리를 냅니다. 식사 횟수는 날짜가 지나도 누적됩니다.
•
같은 동물을 두 번 등록할 수 없습니다. 동물이 없는 동물원에서 일과를 시작하면 처리할 대상 없이 마칩니다.
확장 요청
•
이번 건강 점검에서는 개와 고양이의 수면 점검 횟수만 기록합니다. 새도 실제로 잠을 자지만 이번 기록 대상은 아니며 기본 식사 일과에는 정상 참여합니다.
•
새로운 동물이 들어오면 식사·울음소리와 수면 점검 대상 여부를 정할 수 있어야 합니다.
사용 흐름 예시
1.
첫 일과 후 개·고양이·새의 식사 횟수는 모두 1입니다. 수면 점검 기록은 개 1·고양이 1·새 0입니다.
2.
다음 일과 후 식사는 모두 2, 수면 점검은 개 2·고양이 2·새 0입니다. 중복 등록 요청을 거절한 동물이 하루에 두 번 먹지 않습니다.
다음 기획 변경
기본 일과는 같지만 수면 점검 대상이 추가된 동물을 등록하려고 합니다. 기존 동물의 일과도 그대로 제공하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Dog dog("D"); Cat cat("C"); Bird bird("B"); Animal* animals[] = { &dog, &cat, &bird }; Zoo zoo; for (Animal* animal : animals) { const bool added = zoo.Add(animal); assert(added); } const bool duplicate = zoo.Add(&dog); assert(!duplicate); zoo.RunDay(); assert(dog.GetMealCount() == 1 && cat.GetMealCount() == 1); assert(bird.GetMealCount() == 1); assert(zoo.GetSleepRecordCount(&dog) == 1); assert(zoo.GetSleepRecordCount(&cat) == 1); assert(zoo.GetSleepRecordCount(&bird) == 0); zoo.RunDay(); assert(dog.GetMealCount() == 2 && bird.GetMealCount() == 2); assert(zoo.GetSleepRecordCount(&dog) == 2); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 27. 시간이 지나면 다시 쓰는 스킬

기획 의도
플레이어가 공격·방어·회복 스킬 버튼을 사용합니다. 스킬마다 효과와 재사용 대기 시간이 다르며, 사용할 수 없는 버튼을 누른 경우에는 효과나 다음 사용 시점이 바뀌지 않아야 합니다.
기본 기능과 동작 규칙
•
공격은 적 체력을 20 줄이고, 방어는 자신의 방어 횟수를 1 늘리며, 회복은 자신의 체력을 15 늘립니다. 체력은 0과 최대 체력 사이를 지킵니다.
•
처음에는 바로 사용할 수 있습니다. 재사용 대기 시간은 공격 5초·방어 3초·회복 4초이며 마지막 성공 사용 시각에 대기 시간을 더한 시각부터 다시 사용 가능합니다. 현재 시각은 앞으로만 진행하는 정수 초로 주어집니다.
확장 요청
•
이미 쓰러진 적에게 공격하거나 체력이 가득 찬 상태에서 회복하려 하면 거절합니다. 대기 시간이 지나지 않은 사용도 거절합니다.
•
실패한 시도는 다음 사용 시점을 늦추지 않습니다. 각 스킬의 대기 시간은 독립적이며 다른 스킬을 사용했다고 함께 대기하지 않습니다.
사용 흐름 예시
1.
0초에 공격하면 적 체력이 50에서 30으로, 방어하면 자신의 방어 횟수가 0에서 1로, 회복하면 자신의 체력이 90에서 100으로 바뀝니다.
2.
방어는 2초에는 사용할 수 없지만 3초에는 다시 사용할 수 있습니다. 공격은 4초에 거절되며 5초에 성공해 적 체력이 10이 됩니다.
3.
4초에 회복을 눌러도 체력이 이미 가득 찼으면 거절합니다. 이 실패 때문에 다음 회복 가능 시점을 새로 늦추지 않습니다.
다음 기획 변경
효과 수치와 대기 시간을 변경하려고 합니다. 같은 종류의 스킬 두 개를 준비해도 각각의 사용 가능 시점을 관리해야 합니다.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Fighter hero("Hero", 90, 100); // 이름, 현재 HP, 최대 HP Fighter enemy("Enemy", 50, 50); AttackSkill attack("Fireball", 5, 20); // 이름, 대기 시간, 피해 DefenseSkill defense("Shield", 3); HealSkill heal("Heal", 4, 15); // 이름, 대기 시간, 회복량 Skill* skills[] = { &attack, &defense, &heal }; for (Skill* skill : skills) { const bool ok = skill->Execute(0, hero, enemy); assert(ok); } assert(enemy.GetHP() == 30 && hero.GetHP() == 100); assert(hero.GetDefenseCount() == 1); bool ok = defense.Execute(2, hero, enemy); assert(!ok && hero.GetDefenseCount() == 1); ok = defense.Execute(3, hero, enemy); assert(ok && hero.GetDefenseCount() == 2); ok = attack.Execute(4, hero, enemy); assert(!ok && enemy.GetHP() == 30); ok = heal.Execute(4, hero, enemy); assert(!ok && hero.GetHP() == 100); ok = attack.Execute(5, hero, enemy); assert(ok && enemy.GetHP() == 10); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 28. 배송 방식을 바꿔 견적 내기

기획 의도
주문자가 같은 배송 요청에서 배송 방법을 바꾸며 견적을 비교합니다. 선택한 배송 방법만 달라지고 주문 거리는 유지됩니다. 견적을 보는 일은 실제 결제가 아닙니다.
기본 기능과 동작 규칙
•
거리는 0 이상의 정수 km입니다. 일반 배송은 거리와 관계없이 3000원, 빠른 배송은 km당 500원입니다.
•
배송 방법을 선택한 뒤 견적을 확인합니다. 이미 견적을 확인했더라도 방법을 다시 선택해 새 견적을 볼 수 있습니다.
확장 요청
•
0km도 유효한 요청입니다. 음수 거리는 받아들이지 않으며 견적을 낼 수 없음을 알려 줍니다. 잘못된 거리에는 새 견적을 제공하지 않습니다.
사용 흐름 예시
1.
10km에서 일반 배송은 3000원, 빠른 배송은 5000원입니다. 다시 일반 배송을 고르면 3000원이 됩니다. 두 견적을 더한 요금을 안내하지 않습니다.
2.
0km는 일반 배송 3000원·빠른 배송 0원입니다. 음수 거리는 새 견적을 제공하지 않습니다.
다음 기획 변경
기본 2000원에 km당 300원을 더하는 당일 배송을 추가하려고 합니다. 같은 선택·견적 화면에서 비교할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { StandardDelivery standard; ExpressDelivery express; Delivery* choices[] = { &standard, &express, &standard }; DeliveryCalculator calculator(&standard); for (Delivery* choice : choices) { calculator.Use(choice); int fee = -1; const bool ok = calculator.Quote(10, fee); assert(ok); std::cout << fee << '\n'; // 3000, 5000, 3000 } calculator.Use(&express); int fee = -1; bool ok = calculator.Quote(0, fee); assert(ok && fee == 0); fee = -1; ok = calculator.Quote(-1, fee); assert(!ok && fee == -1); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 29. 서로 다른 입력으로 인증하기

기획 의도
로그인 화면에서 비밀번호 또는 모의 지문 확인을 선택합니다. 두 방식은 입력하는 정보가 다르지만 사용자는 입력을 준비한 후 같은 로그인 버튼으로 성공 여부를 확인합니다.
기본 기능과 동작 규칙
•
연습용 등록 사용자는 user123입니다. 비밀번호는 pass456, 지문 센서가 전달한 등록 번호는 42입니다. 실제 계정이나 생체 정보는 사용하지 않습니다.
•
비밀번호 방식은 문자열, 지문 방식은 정수 번호를 사용합니다. 입력을 준비했다는 사실만으로 로그인 성공이 되지는 않습니다.
확장 요청
•
등록 사용자와 해당 방식의 입력이 모두 일치해야 로그인됩니다. 없는 사용자나 잘못된 입력은 거절합니다.
•
새 입력으로 시도할 때는 새 입력을 확인해야 합니다. 앞선 로그인 성공을 이후 잘못된 입력의 성공으로 재사용하면 안 됩니다.
사용 흐름 예시
1.
user123과 pass456을 입력하거나 user123과 지문 번호 42를 입력하면 로그인됩니다.
2.
같은 사용자에게 wrong 또는 지문 번호 41을 입력하면 로그인되지 않습니다. 등록되지 않은 사용자는 올바른 비밀번호나 번호를 입력해도 거절됩니다.
다음 기획 변경
등록된 6자리 코드 123456으로 확인하는 방식을 추가하려고 합니다. 기존 로그인 화면에서 방식 선택 후 사용할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { PasswordAuth password("user123", "pass456"); FingerprintAuth fingerprint("user123", 42); Authenticator* methods[] = { &password, &fingerprint }; password.Prepare("user123", "pass456"); fingerprint.Prepare("user123", 42); for (Authenticator* method : methods) { const bool ok = method->Authenticate(); assert(ok); } password.Prepare("user123", "wrong"); fingerprint.Prepare("user123", 41); for (Authenticator* method : methods) { const bool ok = method->Authenticate(); assert(!ok); } password.Prepare("unknown", "pass456"); fingerprint.Prepare("unknown", 42); for (Authenticator* method : methods) { const bool ok = method->Authenticate(); assert(!ok); } // Prepare의 두 번째 인자: 비밀번호는 문자열, 지문 ID는 정수 }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 30. 문자열 필터 조합

기획 의도
문자열 편집 화면에서 원하는 변환 기능을 선택합니다. 한 기능만 적용하거나 여러 기능을 선택 순서대로 적용한 결과를 볼 수 있습니다. 원문은 별도로 보존합니다.
기본 기능과 동작 규칙
•
영문 ASCII 문자열의 대문자 변환·소문자 변환·글자 순서 뒤집기를 제공합니다. 대소문자 변환에서 공백·숫자·기호는 그대로 둡니다.
•
단독 변환은 원문을 기준으로 결과를 보여 줍니다. 원문 자체를 바꾸지 않으며 빈 원문에는 빈 결과를 보여 줍니다.
확장 요청
•
여러 변환을 선택하면 앞 변환의 결과에 다음 변환을 적용합니다. 선택한 변환이 없으면 원문과 같은 결과입니다.
•
선택 목록을 비우고 새로운 순서를 선택할 수 있습니다. 목록을 비워도 원문은 남아 있습니다.
사용 흐름 예시
1.
Hello!의 단독 결과는 대문자 HELLO!, 소문자 hello!, 뒤집기 !olleH입니다. 원문은 Hello!로 유지됩니다.
2.
Ab에 대문자 다음 소문자를 적용하면 ab, 소문자 다음 대문자를 적용하면 AB입니다. 선택 순서가 결과에 반영됩니다.
다음 기획 변경
일반 공백 문자만 제거하는 변환을 추가하려고 합니다. 다른 변환들과 함께 선택하고 적용할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { UpperCaseFilter upper; LowerCaseFilter lower; ReverseFilter reverse; const Filter* filters[] = { &upper, &lower, &reverse }; const std::string original = "Hello!"; for (const Filter* filter : filters) { std::cout << filter->Apply(original) << '\n'; assert(filter->Apply("").empty()); } assert(original == "Hello!"); assert(reverse.Apply(reverse.Apply(original)) == original); TextPipeline pipeline; assert(pipeline.Apply("Ab") == "Ab"); bool ok = pipeline.Add(&upper); assert(ok); ok = pipeline.Add(&lower); assert(ok); assert(pipeline.Apply("Ab") == "ab"); pipeline.Clear(); ok = pipeline.Add(&lower); assert(ok); ok = pipeline.Add(&upper); assert(ok); assert(pipeline.Apply("Ab") == "AB"); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

4단계 · 여러 객체의 협력과 변경 (31~40)

문제 31. 실제로 체력이 줄어드는 전투

기획 의도
게임에서 한 전투원이 다른 전투원을 공격하고, 대상은 실제로 피해를 받습니다. 방어 준비는 다음 유효한 공격 한 번에만 효과가 있으며 잘못된 공격 시도로 소모되지 않습니다.
기본 기능과 동작 규칙
•
전사는 체력 100·공격력 30, 마법사는 80·50, 궁수는 90·25입니다. 공격받은 대상의 체력이 피해만큼 줄며 0 아래로 내려가지 않습니다.
•
쓰러진 공격자, 이미 쓰러진 대상, 자기 자신을 대상으로 한 공격은 거절합니다. 공격자는 상대 체력을 줄이며 자신의 체력을 깎지 않습니다.
확장 요청
•
방어를 준비하면 다음 유효 피격의 피해를 전사 10·마법사 5·궁수 8 줄입니다. 피해는 최소 0입니다. 방어를 다시 준비해도 겹쳐 쌓이지 않습니다.
•
방어는 정상 피격 후 해제됩니다. 거절된 공격은 체력과 방어 준비를 그대로 유지합니다.
사용 흐름 예시
1.
전사가 방어를 두 번 준비하고 자기 자신을 공격하려 하면 거절되며 체력 100과 방어 준비가 남습니다.
2.
이어 공격력 50인 마법사에게 공격받으면 피해 40으로 체력 60이 됩니다. 두 번째 공격은 방어가 끝났으므로 체력 10, 세 번째는 0입니다.
3.
쓰러진 전사는 마법사를 공격할 수 없습니다. 이미 쓰러진 전사에 대한 추가 공격도 거절됩니다.
다음 기획 변경
새 직업의 체력·공격력·방어 감소량을 정하려고 합니다. 기존 전투원들과 같은 공격·방어 기능으로 전투할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Warrior warrior(100, 30); Mage mage(80, 50); Archer archer(90, 25); Combatant* units[] = { &warrior, &mage, &archer }; for (const Combatant* unit : units) { std::cout << unit->GetHP() << '\n'; // 100, 80, 90 } warrior.Defend(); warrior.Defend(); // 중첩하지 않음 bool ok = warrior.Attack(warrior); assert(!ok); // 실패한 공격으로 방어를 소모하지 않음 ok = mage.Attack(warrior); assert(ok && warrior.GetHP() == 60); ok = mage.Attack(warrior); assert(ok && warrior.GetHP() == 10); ok = mage.Attack(warrior); assert(ok && warrior.GetHP() == 0); ok = warrior.Attack(mage); assert(!ok && mage.GetHP() == 80); ok = mage.Attack(warrior); assert(!ok && warrior.GetHP() == 0); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 32. 접었다 펼 수 있는 메뉴

기획 의도
설정 화면의 메뉴 기획입니다. 메뉴에는 바로 선택하는 항목과 여러 항목을 묶는 그룹이 있으며, 그룹 안에 다른 그룹이 들어갈 수 있습니다.
기본 기능과 동작 규칙
•
메뉴는 등록된 순서대로 이름을 보여 줍니다. 안쪽에 들어갈수록 공백 두 칸씩 더 들여써 상하 관계를 표현합니다. 그룹은 처음에 펼쳐진 상태입니다.
•
사용자가 그룹을 접으면 그룹 이름은 남고 그 아래 모든 항목이 숨습니다. 다시 펼치면 기존 이름과 순서로 돌아옵니다. 빈 그룹도 자기 이름은 표시합니다.
확장 요청
•
일반 메뉴 항목에는 하위 항목을 넣을 수 없습니다. 같은 항목의 중복 등록이나 자신을 자기 하위 메뉴로 넣는 요청은 받아들이지 않습니다.
•
메뉴의 접기·펼치기는 내용 삭제가 아닙니다. 다른 그룹이나 형제 메뉴의 표시 상태까지 함께 바꾸지 않습니다.
사용 흐름 예시
1.
Root 안에 Play와 Settings, Settings 안에 Sound가 있으면 Root 아래 Play·Settings, 그 아래 Sound를 들여써 보여 줍니다.
2.
Settings를 접으면 Root·Play·Settings는 남고 Sound만 숨습니다. 다시 펼치면 Sound가 이전 위치에 나타납니다.
서비스 흐름
flowchart TD
 R["Root"] --> P["Play"]
 R --> S["Settings"]
 S --> Q["Sound"]
 N["Settings를 접으면 이름은 남고 Sound만 숨김"] -.-> S
Mermaid
복사
다음 기획 변경
같은 기능을 조직도에 적용하거나 모든 그룹을 한 번에 접는 기능을 추가하려고 합니다. 새로운 화면 요구를 반영하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { MenuItem play("Play"), sound("Sound"); MenuGroup root("Root"), settings("Settings"), empty("Empty"); bool ok = root.Add(&play); assert(ok); ok = root.Add(&settings); assert(ok); ok = settings.Add(&sound); assert(ok); const MenuNode* menu = &root; menu->Display(0); // Root, Play, Settings, Sound 순서와 들여쓰기 settings.SetExpanded(false); menu->Display(0); // Sound만 숨김 settings.SetExpanded(true); menu->Display(0); // Sound가 다시 보임 empty.Display(0); // Empty만 출력 ok = root.Add(&play); assert(!ok); // 같은 객체 중복 ok = root.Add(&root); assert(!ok); // 자기 자신 }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 33. 폴더 전체 크기 계산

기획 의도
가상의 파일 목록에서 폴더별 전체 크기를 확인합니다. 폴더에 하위 폴더가 여러 층 있어도 그 안에 포함된 파일 크기를 모두 합산합니다. 실제 디스크를 읽는 기능은 아닙니다.
기본 기능과 동작 규칙
•
파일에는 이름과 0 이상의 크기가 있습니다. 단위는 byte이며 폴더 자체가 차지하는 크기는 이번 기획에서 0입니다. 빈 폴더의 크기도 0입니다.
•
폴더의 전체 크기는 안에 포함된 모든 하위 파일 크기의 합입니다. 같은 파일을 한 곳의 소속으로만 두며 폴더가 자기 상위 폴더를 다시 포함하는 구성은 사용하지 않습니다.
확장 요청
•
파일 크기를 변경하면 해당 파일이 들어 있는 모든 상위 폴더의 다음 크기 조회에 반영되어야 합니다.
•
폴더에서 파일을 제외하면 그 폴더 합계에서는 빠집니다. 제외한 파일 자체를 없애는 기능은 아니므로 파일의 정보는 계속 확인할 수 있습니다.
사용 흐름 예시
1.
Root에 a 100byte와 Sub가 있고 Sub에 b 200byte·c 300byte가 있으면 Sub는 500byte, Root는 600byte입니다.
2.
b를 250byte로 바꾸면 Sub는 550byte·Root는 650byte입니다. 이후 Sub에서 c를 제외하면 Sub는 250byte·Root는 350byte이며 c의 크기는 여전히 300byte입니다.
서비스 흐름
flowchart TD
 R["Root: 하위 파일 전체 합계"] --> A["a: 100byte"]
 R --> S["Sub: 안쪽 파일 합계"]
 S --> B["b: 200byte"]
 S --> C["c: 300byte"]
Mermaid
복사
다음 기획 변경
음수 크기 변경도 거절하도록 기획을 추가하려 합니다. 선택 확장으로 잘못된 순환 폴더 구성도 받아들이지 않게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { File a("a.txt", 100), b("b.txt", 200), c("c.txt", 300); Folder root("Root"), sub("Sub"), empty("Empty"); bool ok = sub.Add(&b); assert(ok); ok = sub.Add(&c); assert(ok); ok = root.Add(&a); assert(ok); ok = root.Add(&sub); assert(ok); const FileSystemNode* nodes[] = { &root, &sub, &empty }; for (const FileSystemNode* node : nodes) { std::cout << node->GetSize() << '\n'; // 600, 500, 0 } ok = b.SetSize(250); assert(ok && root.GetSize() == 650); ok = sub.Remove(&c); assert(ok && root.GetSize() == 350 && sub.GetSize() == 250); ok = sub.Remove(&c); assert(!ok && root.GetSize() == 350); assert(c.GetSize() == 300); // 연결 해제 후에도 객체는 살아 있음 }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 34. 정렬 방법 바꾸기

기획 의도
정수 목록을 정렬하는 도구입니다. 사용자는 정렬 방법을 고르고 정렬 결과를 확인합니다. 방법이 달라도 같은 입력에서는 같은 오름차순 결과가 나와야 합니다.
기본 기능과 동작 규칙
•
기본 선택지는 버블 정렬과 삽입 정렬입니다. 음수와 같은 값이 여러 번 등장하는 목록도 지원합니다. 작은 값부터 큰 값 순서로 실제 목록을 재배치합니다.
•
정렬 전후에 원소의 개수와 각각의 값은 보존됩니다. 아무 원소가 없는 목록과 원소가 하나인 목록도 정상 처리합니다.
확장 요청
•
실행 중 정렬 방법을 바꿀 수 있습니다. 비교 화면에서는 각 방법에 동일한 정렬 전 원본을 사용해 결과를 비교합니다.
•
이번 기본 기능은 오름차순 정렬입니다. 퀵 정렬은 선택 확장으로 둡니다.
사용 흐름 예시
1.
5, 1, 4, 1, -2를 어느 기본 방법으로 정렬해도 -2, 1, 1, 4, 5입니다. 값 1 두 개가 그대로 남습니다.
2.
이미 정렬된 목록은 같은 순서를 유지합니다. 빈 목록과 7 하나만 있는 목록도 오류 없이 처리되며 원소가 새로 생기지 않습니다.
다음 기획 변경
사용자가 내림차순도 선택하려고 합니다. 정렬 방법 선택과 함께 새 기능을 제공하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { BubbleSort bubble; InsertionSort insertion; SortMethod* methods[] = { &bubble, &insertion }; Sorter sorter(&bubble); const int original[] = { 5, 1, 4, 1, -2 }; const int expected[] = { -2, 1, 1, 4, 5 }; for (SortMethod* method : methods) { int values[5]; for (int i = 0; i < 5; ++i) values[i] = original[i]; sorter.Use(method); bool ok = sorter.Sort(values, 5); assert(ok); for (int i = 0; i < 5; ++i) assert(values[i] == expected[i]); ok = sorter.Sort(nullptr, 0); // 빈 배열은 정상 처리 assert(ok); int single[] = { 7 }; ok = sorter.Sort(single, 1); assert(ok && single[0] == 7); } }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 35. 커피에 옵션 하나 추가하기

기획 의도
카페에서 같은 기본 커피를 바탕으로 서로 다른 주문을 받습니다. 고객이 고른 옵션은 그 주문에만 적용되며 메뉴의 기본 가격이나 다른 고객의 주문을 바꾸지 않습니다.
기본 기능과 동작 규칙
•
기본 커피는 2000원입니다. 우유 옵션은 500원이며 이번 기획에서는 주문당 한 번만 추가할 수 있습니다.
•
주문 내용과 현재 총액을 언제든 조회할 수 있습니다. 총액은 기본 음료 가격과 현재 선택한 옵션 가격의 합입니다.
확장 요청
•
커피만 있는 주문과 우유를 추가한 주문을 동시에 관리합니다. 한 주문에 우유를 추가했다고 다른 주문에도 우유나 추가 요금이 생기면 안 됩니다.
•
우유를 다시 추가하려는 요청은 거절하고 기존 주문을 유지합니다. 주문 조회를 반복해도 옵션 비용이 다시 청구되지 않습니다.
사용 흐름 예시
1.
일반 주문은 Coffee·2000원, 우유 주문은 Coffee + Milk·2500원입니다. 우유 주문을 변경한 뒤에도 일반 주문은 2000원입니다.
2.
우유 주문에 다시 우유를 추가하려 하면 거절되며 2500원으로 유지됩니다. 주문 내용을 여러 번 조회해도 같습니다.
다음 기획 변경
기본 가격 1500원인 차를 추가하려고 합니다. 차 주문에도 같은 우유 옵션 규칙을 적용하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Coffee coffee(2000); DrinkOrder plain(&coffee), withMilk(&coffee); const bool ok = withMilk.AddMilk(); assert(ok); const DrinkOrder* orders[] = { &plain, &withMilk }; for (const DrinkOrder* order : orders) { std::cout << order->Description() << " / " << order->Cost() << '\n'; } assert(plain.Description() == "Coffee" && plain.Cost() == 2000); assert(withMilk.Description() == "Coffee + Milk"); assert(withMilk.Cost() == 2500 && withMilk.Cost() == 2500); const bool duplicate = withMilk.AddMilk(); assert(!duplicate && withMilk.Cost() == 2500); assert(plain.Cost() == 2000); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 36. 기록 위치를 바꿀 수 있는 로그

기획 의도
작업 중 생기는 메시지를 기록하는 프로그램입니다. 운영자는 메시지를 바로 콘솔에서 확인하거나 임시 기록장에 보관하는 방법을 선택할 수 있습니다.
기본 기능과 동작 규칙
•
콘솔 방식은 새 메시지를 바로 보여 줍니다. 기록장 방식은 메시지를 들어온 순서대로 최대 8건 보관합니다.
•
빈 메시지도 한 건의 기록으로 취급합니다. 기록장이 가득 차면 새 기록을 거절하며 기존 8건은 그대로 유지합니다.
확장 요청
•
작업이 진행되는 중에도 기록 위치를 바꿀 수 있습니다. 변경 후 메시지만 새 위치로 보내며 이전 기록을 자동으로 옮기거나 삭제하지 않습니다.
•
작업 화면에서는 기록 요청이 성공했는지 알 수 있어야 합니다. 이번에는 파일 저장이나 네트워크 전송을 하지 않습니다.
사용 흐름 예시
1.
콘솔에 A를 기록한 뒤 기록장으로 바꾸어 B·C를 남기고 다시 콘솔로 바꾸어 D를 기록하면 콘솔에는 A·D, 기록장에는 B·C가 있습니다.
2.
기록장에 8건이 들어간 뒤 추가 기록은 거절됩니다. 앞선 기록의 순서와 내용이 보존됩니다.
다음 기획 변경
항상 기록을 거절하는 모의 기록 위치를 추가하려고 합니다. 작업 화면에서 기록 실패를 확인할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { ConsoleLogger console; MemoryLogger memory; LogWriter writer(&console); bool ok = writer.Log("A"); // 콘솔에 A assert(ok); writer.Use(&memory); ok = writer.Log("B"); assert(ok); ok = writer.Log("C"); assert(ok && memory.Count() == 2); assert(memory.At(0) == "B" && memory.At(1) == "C"); writer.Use(&console); ok = writer.Log("D"); assert(ok && memory.Count() == 2); writer.Use(&memory); for (int i = 0; i < 6; ++i) { ok = writer.Log(""); // 빈 문자열도 한 건 assert(ok); } ok = writer.Log("overflow"); assert(!ok && memory.Count() == 8 && memory.At(0) == "B"); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 37. 재생 기기의 상태 변화

기획 의도
음악 재생기의 상태와 현재 재생 위치를 관리합니다. 일시정지는 듣던 위치를 남기고 정지는 다음 재생이 처음부터 시작되도록 합니다.
기본 기능과 동작 규칙
•
상태는 대기·재생·일시정지이며 처음에는 대기·0초입니다. 재생 시작은 대기 또는 일시정지에서, 일시정지는 재생 중에만, 정지는 재생 또는 일시정지에서만 가능합니다.
•
재생 중에는 시간이 1초 진행될 때 위치가 1초 늘어납니다. 대기·일시정지에서는 시간이 진행되어도 재생 위치를 유지합니다.
확장 요청
•
일시정지는 위치를 보존하고 정지는 위치를 0으로 되돌립니다. 상태와 위치 조회만으로 음악이 진행되지는 않습니다.
•
현재 상태에서 할 수 없는 명령이나 알 수 없는 명령은 받아들이지 않고 상태와 위치를 유지합니다.
사용 흐름 예시
1.
재생을 시작해 2초 진행한 뒤 일시정지하면 2초에 머뭅니다. 일시정지 중 1초가 지나도 위치는 2초입니다.
2.
다시 재생해 1초가 진행되면 3초이며 정지하면 대기·0초가 됩니다. 대기 중 일시정지 요청은 거절됩니다.
3.
이미 재생 중에 재생 시작을 다시 요청해도 새로 시작하지 않습니다. 현재 재생 위치를 그대로 유지합니다.
서비스 흐름
stateDiagram-v2
 state "대기" as Idle
 state "재생" as Playing
 state "일시정지" as Paused
 [*] --> Idle
 Idle --> Playing: 재생 시작
 Playing --> Paused: 위치를 유지하며 일시정지
 Paused --> Playing: 이어서 재생
 Playing --> Idle: 정지하고 위치 0
 Paused --> Idle: 정지하고 위치 0
Mermaid
복사
다음 기획 변경
반복 재생이나 새로운 상태를 추가하려고 합니다. 어느 상태에서 어떤 요청을 허용할지 먼저 기획한 뒤 반영하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { MusicPlayer player; assert(player.StateName() == "idle" && player.Position() == 0); bool changed = player.Send("play"); assert(changed); player.Tick(); player.Tick(); assert(player.StateName() == "playing" && player.Position() == 2); changed = player.Send("pause"); assert(changed); player.Tick(); assert(player.StateName() == "paused" && player.Position() == 2); changed = player.Send("play"); assert(changed); player.Tick(); assert(player.Position() == 3); changed = player.Send("stop"); assert(changed && player.Position() == 0); changed = player.Send("pause"); assert(!changed && player.StateName() == "idle"); changed = player.Send("unknown"); assert(!changed && player.Position() == 0); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 38. 이동과 공격 되돌리기

기획 의도
게임에서 방금 수행한 이동이나 공격을 취소합니다. 사용자는 마지막 행동부터 한 번씩 되돌릴 수 있으며, 취소 후에는 그 행동을 하기 전의 위치와 체력이 정확히 돌아와야 합니다.
기본 기능과 동작 규칙
•
플레이어는 x좌표 9, 적은 체력 10에서 시작합니다. 플레이어 위치는 0부터 10까지이며 범위를 넘는 이동은 끝 지점에 멈춥니다.
•
공격은 적 체력을 피해량만큼 줄이되 0에서 멈춥니다. 0 이하 피해, 이미 쓰러진 적 공격, 위치 변화가 없는 이동은 받아들이지 않습니다.
확장 요청
•
완료된 행동을 최대 8건 취소할 수 있습니다. 같은 종류의 행동을 여러 번 했더라도 각각 한 번씩 취소할 수 있어야 합니다.
•
변화 없는 요청은 취소 대상이 아닙니다. 취소할 행동이 없으면 거절합니다. 8건이 가득 차면 새 행동 자체를 거절하며 이번에는 취소한 행동의 재실행은 제공하지 않습니다.
사용 흐름 예시
1.
9에서 오른쪽으로 3 이동하면 경계인 10에 멈춥니다. 이 이동을 취소하면 이동 전 위치인 9로 돌아옵니다.
2.
체력 10인 적을 피해 30으로 공격하면 0이 됩니다. 이동 다음에 이 공격을 했다면 첫 취소는 적 체력 10 복원, 다음 취소는 플레이어 위치 9 복원입니다.
3.
현재 위치 10에서 더 오른쪽으로 이동하려 하면 변화가 없으므로 거절합니다. 이후 취소를 눌렀을 때는 그보다 앞선 완료 행동이 취소됩니다.
서비스 흐름
flowchart LR
 A["위치 9 / 적 체력 10"] -->|오른쪽 3 이동| B["위치 10 / 적 체력 10"]
 B -->|피해 30 공격| C["위치 10 / 적 체력 0"]
 C -->|마지막 행동 취소| B
 B -->|그전 행동 취소| A
Mermaid
복사
다음 기획 변경
같은 이동을 두 번 완료하고 각각 취소하는 사용 흐름을 추가하려고 합니다. 취소 가능 건수가 가득 찬 상황도 처리하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Player player(9); Enemy enemy(10); MoveAction move(player, 3); AttackAction attack(enemy, 30); Action* actions[] = { &move, &attack }; ActionHistory history; for (Action* action : actions) { const bool ok = history.Execute(*action); assert(ok); } assert(player.GetX() == 10 && enemy.GetHP() == 0); assert(history.Count() == 2); bool ok = history.Undo(); assert(ok && enemy.GetHP() == 10 && player.GetX() == 10); ok = history.Undo(); assert(ok && player.GetX() == 9 && history.Count() == 0); ok = history.Undo(); assert(!ok); ok = history.Execute(move); // 9 -> 10 assert(ok); ok = history.Execute(move); // 이미 끝이므로 변화 없음 assert(!ok && history.Count() == 1); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 39. 컴퓨터를 단계별로 조립하기

기획 의도
컴퓨터 주문을 받아 부품을 차례로 선택하고 완성품을 전달하는 조립 화면입니다. 현재 조립 중인 주문과 이미 완성해 전달한 컴퓨터는 구별되어야 합니다.
기본 기능과 동작 규칙
•
CPU → 메모리 → 저장장치 순서로 선택한 뒤 완성합니다. CPU 이름은 비어 있지 않아야 하고 메모리·저장 용량은 양수 GB입니다.
•
사무용 기본 구성은 OfficeCPU·16GB·512GB, 게임용은 GameCPU·32GB·1024GB입니다. 부품이 빠졌거나 순서가 틀리면 해당 요청을 거절하고 현재 조립 단계와 선택 내용을 유지합니다.
확장 요청
•
새 조립을 시작하면 이전 조립 중 선택을 초기화합니다. 완성한 컴퓨터는 이후 새 조립을 시작하거나 다른 부품을 선택해도 바뀌지 않습니다.
•
잘못된 선택을 거절한 뒤 같은 단계에서 올바른 선택을 다시 할 수 있어야 합니다.
사용 흐름 예시
1.
사무용 부품을 순서대로 선택하고 완성하면 사무용 컴퓨터를 받습니다. 이어 새 게임용 조립을 시작해도 사무용 결과는 남습니다.
2.
새 조립에서 저장장치부터 선택하거나 CPU만 선택한 뒤 완성하려 하면 거절됩니다. 이후 필요한 부품을 순서대로 선택하면 정상 완성할 수 있습니다.
3.
게임용 조립을 완료하면 사무용은 OfficeCPU·16·512, 게임용은 GameCPU·32·1024인 서로 다른 완성 결과가 남습니다.
서비스 흐름
flowchart LR
 A["새 조립 시작"] --> B["CPU 선택"]
 B --> C["메모리 선택"]
 C --> D["저장장치 선택"]
 D --> E["완성품 전달"]
 E --> F["다른 조립을 시작해도 완성품 유지"]
Mermaid
복사
다음 기획 변경
기본 구성 대신 고객이 부품을 직접 고르는 맞춤 조립을 추가하려고 합니다. 같은 선택 순서와 완성 조건을 적용하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { ComputerAssembly assembly; Computer office, gaming; assembly.Begin(); bool ok = assembly.SelectCPU("OfficeCPU"); assert(ok); ok = assembly.SelectMemory(16); assert(ok); ok = assembly.SelectStorage(512); assert(ok); ok = assembly.Finish(office); assert(ok); assembly.Begin(); ok = assembly.SelectStorage(1024); // 순서가 틀림 assert(!ok); ok = assembly.SelectCPU("GameCPU"); assert(ok); ok = assembly.Finish(gaming); // 미완성 assert(!ok); ok = assembly.SelectMemory(32); assert(ok); ok = assembly.SelectStorage(1024); assert(ok); ok = assembly.Finish(gaming); assert(ok); assert(office.CPU() == "OfficeCPU"); assert(office.MemoryGB() == 16 && office.StorageGB() == 512); assert(gaming.CPU() == "GameCPU"); assert(gaming.MemoryGB() == 32 && gaming.StorageGB() == 1024); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 40. 정해진 순서로 처리하는 작업

기획 의도
문자열을 받아 가공 결과를 저장하는 작업 화면입니다. 작업 종류가 달라도 입력 확인, 가공, 저장의 순서는 같습니다. 유효하지 않은 입력으로 이전 저장 결과를 덮어쓰면 안 됩니다.
기본 기능과 동작 규칙
•
처리 순서는 입력 검사 → 변환 → 저장입니다. 두 기본 작업은 영문 ASCII 대문자 변환과 앞에 LOG:를 붙이는 작업입니다. 둘 다 빈 입력은 거절합니다.
•
각 작업은 마지막으로 성공한 결과를 보관합니다. 사용자는 한 번의 실행 요청으로 전체 작업을 진행하고 처리 순서 안내도 볼 수 있습니다.
확장 요청
•
검사에서 거절되면 변환과 저장을 진행하지 않습니다. 이전에 성공해 저장한 결과는 그대로 남습니다.
•
새 작업 종류가 추가되어도 사용자는 같은 실행 기능을 이용합니다. 실제 파일 저장 없이 메모리에 결과를 보관합니다.
사용 흐름 예시
1.
Hi를 대문자 작업에 보내면 HI가, 접두어 작업에 보내면 LOG:Hi가 저장됩니다. 처리 안내는 검사·변환·저장 순서입니다.
2.
이어서 빈 입력을 보내면 검사에서 거절됩니다. HI와 LOG:Hi라는 이전 성공 결과는 각각 그대로 유지되며 변환·저장 안내도 새로 나오지 않습니다.
다음 기획 변경
글자 순서를 뒤집는 작업을 추가하려고 합니다. 동일한 처리 순서와 실행 화면에서 이용할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { UppercaseJob upper; PrefixJob prefix("LOG:"); TextJob* jobs[] = { &upper, &prefix }; for (TextJob* job : jobs) { bool ok = job->Run("Hi"); assert(ok && job->Trace() == "check,convert,save"); const std::string saved = job->LastSaved(); ok = job->Run(""); assert(!ok && job->Trace() == "check"); assert(job->LastSaved() == saved); } assert(upper.LastSaved() == "HI"); assert(prefix.LastSaved() == "LOG:Hi"); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

5단계 · 조합·기록·연결·통합 (41~50)

문제 41. 여러 옵션을 조합하는 음료 주문

기획 의도
카페 주문 화면을 확장합니다. 고객은 여러 옵션을 고르고 같은 옵션을 여러 번 선택할 수도 있습니다. 주문 내용에는 선택 순서가 나타나야 합니다.
기본 기능과 동작 규칙
•
기본 음료는 커피 2000원·차 1500원입니다. 옵션은 우유 500원·설탕 300원·휘핑 600원이며 주문당 최대 8개를 선택합니다.
•
같은 옵션도 여러 번 추가할 수 있습니다. 총액은 기본 가격과 선택한 모든 옵션 가격의 합이며 설명은 선택 순서대로 보여 줍니다.
확장 요청
•
고객이 선택 목록의 특정 번째 옵션 하나를 제거할 수 있습니다. 번호는 1부터 시작하며 같은 종류가 여러 개여도 지정한 한 개만 제거합니다.
•
없는 번호의 제거와 8개를 넘는 추가는 거절하고 주문을 유지합니다. 한 주문의 추가·제거가 다른 주문에 영향을 주지 않습니다.
사용 흐름 예시
1.
커피에 우유 다음 휘핑을 고르면 Coffee + Milk + Whip·3100원입니다. 여기서 두 번째 옵션을 제거하면 우유만 남아 2500원입니다.
2.
우유를 두 번 선택했을 때 한 개를 제거하면 우유 한 개가 남습니다. 옵션 순서만 바꾸면 총액은 같아도 설명 순서는 달라집니다.
다음 기획 변경
새 옵션이나 새 음료를 추가하려고 합니다. 기존 주문의 선택 순서·제거·독립성 규칙을 유지하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Coffee coffee(2000); Tea tea(1500); Milk milk(500); Sugar sugar(300); Whip whip(600); DrinkOrder first(&coffee), second(&tea); const DrinkOption* options[] = { &milk, &sugar, &whip }; for (const DrinkOption* option : options) { const bool ok = first.AddOption(option); assert(ok); } assert(first.Cost() == 3400); assert(first.Description() == "Coffee + Milk + Sugar + Whip"); bool ok = first.RemoveOption(2); // 1부터 세는 선택 순서 assert(ok && first.Cost() == 3100); assert(first.Description() == "Coffee + Milk + Whip"); ok = second.AddOption(&milk); assert(ok); ok = second.AddOption(&milk); // 이 문제에서는 중복 선택 허용 assert(ok && second.Cost() == 2500); assert(first.Cost() == 3100); // 다른 주문과 독립 ok = first.RemoveOption(9); assert(!ok && first.Cost() == 3100); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 42. 제품을 빌려 주고 돌려받기

기획 의도
공구 대여점에서 드릴과 톱을 빌려 주고 반납받습니다. 같은 제품은 동시에 여러 사람에게 대여할 수 없으며, 반납 후에는 사용 이력을 유지한 채 다시 대여합니다.
기본 기능과 동작 규칙
•
드릴과 톱을 각각 두 개씩 준비합니다. 제품마다 고유 번호와 사용 횟수가 있으며 처음 사용 횟수는 0입니다. 드릴 사용은 drilling, 톱은 sawing으로 안내하고 사용 횟수를 1 늘립니다.
•
요청한 종류 중 대여 가능한 가장 작은 번호를 빌려 줍니다. 없는 종류나 품절이면 대여하지 않습니다.
확장 요청
•
대여 중인 제품은 반납 전까지 다른 요청에 다시 내주지 않습니다. 반납했다고 새 제품으로 교체하거나 사용 횟수를 초기화하지 않습니다.
•
다른 대여점의 제품과 이미 반납한 제품의 중복 반납은 거절합니다. 반납이 거절된 경우 기존 대여 현황을 유지합니다.
사용 흐름 예시
1.
드릴 1·2가 있을 때 첫 대여는 1, 다음은 2이며 세 번째 드릴 요청은 품절입니다.
2.
드릴 1을 한 번 사용하고 반납한 뒤 다시 빌리면 번호 1·사용 횟수 1인 같은 제품을 받습니다.
3.
반납한 제품을 다시 반납하거나 다른 대여점에 반납하려 하면 거절됩니다. 아직 대여 중인 다른 제품의 상태가 바뀌지 않습니다.
다음 기획 변경
새 공구 종류를 추가하려고 합니다. 같은 대여·사용·반납 서비스에서 이용할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Drill drill1(1), drill2(2); Saw saw1(3), saw2(4); Product* stock[] = { &drill1, &drill2, &saw1, &saw2 }; ToolDesk desk; for (Product* product : stock) { const bool ok = desk.Register(product); assert(ok); } Product* first = desk.Borrow("drill"); Product* second = desk.Borrow("drill"); assert(first == &drill1 && second == &drill2); Product* third = desk.Borrow("drill"); assert(third == nullptr); first->Use(); assert(first->UseCount() == 1 && second->UseCount() == 0); bool ok = desk.Return(first); assert(ok); first = nullptr; // 반납한 쪽은 이전 대여 포인터를 더 쓰지 않음 Product* again = desk.Borrow("drill"); assert(again == &drill1 && again->UseCount() == 1); ok = desk.Return(again); assert(ok); again = nullptr; ok = desk.Return(&drill1); // 이미 반납된 제품 assert(!ok); ToolDesk other; ok = other.Return(&drill2); // 다른 제공자의 제품 assert(!ok); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 43. 뉴스 구독과 해지

기획 의도
뉴스 발행자가 구독자에게 새 소식을 전달합니다. 같은 뉴스를 받더라도 구독 목적에 따라 화면에 표시하거나 수신 횟수를 집계할 수 있습니다.
기본 기능과 동작 규칙
•
새 뉴스는 등록된 순서대로 각 구독자에게 한 번씩 전달합니다. 화면 구독은 내용을 보여 주고 집계 구독은 받은 건수와 마지막 뉴스를 보관합니다.
•
빈 뉴스도 한 건입니다. 구독자는 최대 8명이며 같은 구독자의 중복 등록과 등록되지 않은 구독자의 해지는 거절합니다.
확장 요청
•
구독과 해지를 제공합니다. 해지된 구독자는 다음 뉴스부터 받지 않으며 아직 구독 중인 사람의 수신에는 영향을 주지 않습니다.
•
이번 기획에서는 뉴스를 전달하는 도중 구독을 변경하지 않습니다. 변경은 한 번의 뉴스 전달이 끝난 후 요청합니다.
사용 흐름 예시
1.
화면 구독 A와 집계 구독 B에게 news1을 발행하면 두 구독자가 각각 한 번 받으며 B의 건수는 1·마지막 뉴스는 news1입니다.
2.
A를 해지하고 news2를 발행하면 B만 받습니다. B의 건수는 2·마지막 뉴스는 news2이며 A에게는 news2가 표시되지 않습니다.
다음 기획 변경
뉴스를 받으면 새로운 반응을 하는 구독 방식을 추가하려고 합니다. 기존 뉴스 발행 기능에서도 전달할 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { ScreenSubscriber screen("A"); CountingSubscriber counter("B"); NewsPublisher news; bool ok = news.Subscribe(&screen); assert(ok); ok = news.Subscribe(&counter); assert(ok); ok = news.Subscribe(&counter); assert(!ok); news.Publish("news1"); assert(counter.Count() == 1 && counter.LastNews() == "news1"); ok = news.Unsubscribe(&screen); assert(ok); news.Publish("news2"); // 화면 A는 더 이상 출력하지 않음 assert(counter.Count() == 2 && counter.LastNews() == "news2"); ok = news.Unsubscribe(&counter); assert(ok); news.Publish("news3"); // 구독자가 없어도 정상 assert(counter.Count() == 2); ok = news.Unsubscribe(&counter); assert(!ok); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 44. 게임 이벤트와 알림 중 구독 변경

기획 의도
게임에서 동전 획득과 게임 종료를 알립니다. 각 수신자는 관심 있는 사건만 받아 화면·입력·음악에 반영합니다. 사건을 받는 도중에도 다음 사건을 위한 구독을 바꿀 수 있습니다.
기본 기능과 동작 규칙
•
처음에는 입력 가능·화면 Play·음악 Play·점수 0입니다. 동전 획득 알림은 UI에 해당 점수를 더합니다. 게임 종료 알림은 입력 불가·게임 오버 화면·게임 오버 음악으로 바꿉니다.
•
구독은 수신자와 사건 종류의 조합입니다. 한 수신자가 다른 사건 두 종류를 구독하는 것은 허용하지만 같은 조합을 두 번 등록할 수는 없습니다. 최대 8조합입니다.
확장 요청
•
한 사건을 전달하기 시작한 시점에 구독 중이던 대상은 그 사건을 한 번씩 받습니다. 전달 중 발생한 구독·해지는 다음 사건부터 적용합니다.
•
변경 요청은 접수 순서대로 처리하며 변경 후에도 중복과 정원 규칙을 지킵니다. 이번에는 사건 수신 중 새 사건을 발행하거나 수신자를 없애는 경우는 다루지 않습니다.
사용 흐름 예시
1.
동전 점수 5를 알리면 UI 점수만 5가 됩니다. 이어 게임 종료를 알리면 입력·화면·음악은 종료 상태로 바뀌고 점수 5는 유지됩니다.
2.
별도 알림 상황에서 A·B가 구독 중일 때 A가 첫 사건을 받고 B 해지·C 등록을 요청해도 첫 사건은 A와 B가 받고 C는 받지 않습니다.
3.
두 번째 사건은 A와 C가 받습니다. 두 사건을 합친 수신 횟수는 A 2·B 1·C 1입니다.
서비스 흐름
flowchart LR
 A["첫 사건 시작: A와 B 구독 중"] --> B["A가 수신하며 B 해지와 C 등록 요청"]
 B --> C["첫 사건은 A와 B가 수신"]
 C --> D["다음 사건은 A와 C가 수신"]
Mermaid
복사
다음 기획 변경
획득 점수를 별도로 보관하는 구독을 추가하려고 합니다. 다른 수신자의 기존 반응을 유지하며 새 반응을 제공하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { GameEvents events; Player player; GameUI ui; GameSound sound; bool ok = events.Subscribe(EventType::GameOver, &player); assert(ok); ok = events.Subscribe(EventType::GameOver, &ui); assert(ok); ok = events.Subscribe(EventType::GameOver, &sound); assert(ok); ok = events.Subscribe(EventType::Coin, &ui); assert(ok); events.Publish(EventType::Coin, 5); assert(ui.Score() == 5 && player.InputEnabled()); events.Publish(EventType::GameOver, 0); assert(!player.InputEnabled() && ui.Screen() == "GameOver"); assert(sound.Track() == "GameOver" && ui.Score() == 5); GameEvents test; CountingListener b("B"), c("C"); ChangeOnceListener a("A", test, EventType::Test, &b, &c); // a는 첫 수신 때만 같은 이벤트의 b 구독 해지와 c 등록을 요청 ok = test.Subscribe(EventType::Test, &a); assert(ok); ok = test.Subscribe(EventType::Test, &b); assert(ok); test.Publish(EventType::Test, 0); assert(a.Count() == 1 && b.Count() == 1 && c.Count() == 0); test.Publish(EventType::Test, 0); assert(a.Count() == 2 && b.Count() == 1 && c.Count() == 1); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 45. 처리할 수 있는 담당자 찾기

기획 의도
문의 접수 서비스에서 정해진 담당 순서대로 처리할 수 있는 사람을 찾습니다. 한 문의는 처음 처리 가능한 담당자 한 명에게만 맡기며 모든 담당자가 중복 처리하지 않습니다.
기본 기능과 동작 규칙
•
문의 난이도는 1 이상의 정수입니다. 1차 상담원은 난이도 1까지, 2차 상담원은 2까지, 관리자는 3까지 처리합니다.
•
등록된 담당 순서대로 확인합니다. 처리 가능한 담당자를 찾으면 그 담당자의 처리 결과를 안내하고 이후 담당자에게는 넘기지 않습니다.
확장 요청
•
담당 순서를 바꾸거나 일부 담당자를 제외할 수 있습니다. 끝까지 가능한 사람이 없거나 담당자가 없으면 미처리로 안내합니다.
•
0 이하 난이도는 잘못된 접수로 거절합니다. 미처리와 잘못된 요청을 정상 처리로 표시하지 않습니다.
사용 흐름 예시
1.
1차·2차·관리자 순서에서 난이도 1은 1차, 2는 2차, 3은 관리자가 처리합니다. 난이도 4는 미처리입니다.
2.
관리자를 맨 앞에 배치하면 난이도 1도 관리자가 먼저 처리합니다. 담당 순서를 바꾼 결과가 실제 처리자에 반영됩니다.
다음 기획 변경
난이도 4까지 처리하는 담당자를 추가하려고 합니다. 기존 문의 접수 기능에서 처리자를 찾을 수 있게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { SupportAgent first("L1", 1), second("L2", 2), manager("Manager", 3); SupportAgent* order[] = { &first, &second, &manager }; HelpDesk desk; for (SupportAgent* agent : order) { const bool ok = desk.Add(agent); assert(ok); } for (int level = 1; level <= 4; ++level) { std::string handledBy; const bool ok = desk.Handle(level, handledBy); std::cout << ok << " / " << handledBy << '\n'; // 1: L1, 2: L2, 3: Manager, 4: 실패와 빈 문자열 } desk.Clear(); bool ok = desk.Add(&manager); assert(ok); ok = desk.Add(&first); assert(ok); std::string handledBy; ok = desk.Handle(1, handledBy); assert(ok && handledBy == "Manager"); handledBy.clear(); ok = desk.Handle(0, handledBy); assert(!ok && handledBy.empty()); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 46. 자료를 읽기 전에 권한 확인

기획 의도
보고서 화면에서 현재 사용자 권한에 따라 자료 열람을 허용합니다. 화면에서 열람을 눌렀다는 사실과 실제 자료가 읽혔다는 사실을 구별해야 합니다.
기본 기능과 동작 규칙
•
사용자 역할은 guest와 member이며 member만 열람할 수 있습니다. 허용된 읽기는 내용을 보여 주고 실제 읽기 횟수를 1 늘립니다.
•
거절된 읽기는 보고서 내용을 보여 주지 않으며 실제 읽기 횟수도 늘리지 않습니다. 보고서는 메모리에 있고 실제 파일이나 계정 서비스는 사용하지 않습니다.
확장 요청
•
권한은 읽기를 요청할 때마다 현재 역할을 기준으로 확인합니다. 성공·거절 기록을 순서대로 남기며 이번 기획에는 이전 내용을 재사용하는 캐시가 없습니다.
•
권한이 바뀌어도 사용자에게는 같은 열람 기능을 제공합니다. 이전에 허용되었더라도 현재 guest이면 거절해야 합니다.
사용 흐름 예시
1.
guest의 첫 요청은 거절되어 읽기 횟수 0입니다. member로 바꿔 요청하면 내용이 보이고 횟수 1이 됩니다.
2.
다시 guest로 바꿔 요청하면 거절되며 내용과 실제 읽기 횟수가 새로 추가되지 않습니다. 결과 기록은 거절·허용·거절 순서입니다.
다음 기획 변경
새로운 보고서를 추가하려고 합니다. 같은 열람 화면과 권한 규칙을 적용하고 거절된 화면에는 내용이 남지 않게 하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { ReportSource source("sales report"); GuardedReport guarded(source); ReportReader* reader = &guarded; User user("guest"); std::string content; bool ok = reader->Read(user, content); assert(!ok && content.empty() && source.ReadCount() == 0); user.SetRole("member"); ok = reader->Read(user, content); assert(ok && content == "sales report" && source.ReadCount() == 1); user.SetRole("guest"); content.clear(); ok = reader->Read(user, content); assert(!ok && content.empty() && source.ReadCount() == 1); assert(guarded.LogCount() == 3); assert(guarded.LogAt(0) == "denied"); assert(guarded.LogAt(1) == "allowed"); assert(guarded.LogAt(2) == "denied"); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 47. 수정할 수 없는 오디오 코드 연결

기획 의도
새 오디오 화면을 기존 스피커에 연결합니다. 화면의 음량 눈금과 기존 장비의 눈금이 다르므로 사용자가 선택한 값을 장비 기준에 맞게 전달해야 합니다.
기본 기능과 동작 규칙
•
화면은 메시지와 정수 음량 0부터 100을 받습니다. 기존 장비는 0부터 10을 사용하며 제공된 기존 장비 코드는 수정할 수 없습니다.
•
화면 음량을 10으로 나눈 정수 몫을 장비에 전달합니다. 예를 들어 70과 79는 모두 장비 음량 7입니다.
확장 요청
•
0부터 100 사이의 유효한 요청은 장비에 정확히 한 번 전달합니다. 빈 메시지도 허용합니다.
•
범위 밖 음량은 거절하며 장비를 동작시키지 않습니다. 거절된 요청 때문에 장비의 마지막 메시지나 음량이 바뀌면 안 됩니다.
사용 흐름 예시
1.
Hi·70, Hi·79를 순서대로 요청하면 장비는 두 번 동작하고 두 요청 모두 음량 7입니다.
2.
빈 메시지·0은 정상 전달되며 Hi·100은 장비 음량 10입니다. 이후 -1과 101을 요청하면 거절되고 장비 동작 횟수와 마지막 정상 입력은 유지됩니다.
다음 기획 변경
다른 모의 오디오 장비를 연결하려고 합니다. 사용자는 같은 메시지·0부터 100 음량 화면을 계속 이용해야 합니다.
연동 대상의 기존 코드 · 이 코드는 수정하지 않습니다
#include <string> // 제공 코드: 아래 클래스는 수정하지 않습니다. class LegacySpeaker { public: void Speak(const char* text, int volume10) { lastText_ = text; lastVolume_ = volume10; ++calls_; } const std::string& LastText() const { return lastText_; } int LastVolume() const { return lastVolume_; } int CallCount() const { return calls_; } private: std::string lastText_; int lastVolume_ = 0; int calls_ = 0; };
C++
복사
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { LegacySpeaker legacy; // 위에 제공된 기존 클래스는 그대로 사용 LegacyAudioBridge bridge(legacy); AudioOutput* output = &bridge; bool ok = output->Play("Hi", 70); assert(ok && legacy.LastText() == "Hi"); assert(legacy.LastVolume() == 7 && legacy.CallCount() == 1); ok = output->Play("Hi", 79); assert(ok && legacy.LastVolume() == 7); ok = output->Play("", 0); assert(ok && legacy.LastText().empty() && legacy.LastVolume() == 0); ok = output->Play("Hi", 100); assert(ok && legacy.LastVolume() == 10); const int calls = legacy.CallCount(); ok = output->Play("Hi", -1); assert(!ok && legacy.CallCount() == calls); ok = output->Play("Hi", 101); assert(!ok && legacy.CallCount() == calls); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 48. 문서 요소에 새 작업 추가하기

기획 의도
텍스트와 이미지가 섞인 문서에서 여러 업무를 실행합니다. 기본 업무는 문서 내용 출력이고 다음 업무는 통계 확인입니다. 앞으로 같은 문서에 적용할 업무가 더 추가될 예정입니다.
기본 기능과 동작 규칙
•
문서는 등록 순서가 있는 텍스트와 이미지로 구성됩니다. 텍스트는 ASCII 문자열이며 이미지는 파일명·양수 가로·세로를 가집니다.
•
텍스트는 TEXT:내용, 이미지는 IMAGE:파일명(가로x세로)로 한 줄씩 안내합니다. 모든 요소를 문서 순서대로 보여 줍니다.
확장 요청
•
통계 화면에 텍스트 내용의 문자 수 합계, 이미지 개수, 이미지 면적 합계를 보여 줍니다. 출력용 앞말이나 이미지 파일명은 텍스트 문자 수에 포함하지 않습니다.
•
출력과 통계 조회는 문서 내용을 바꾸지 않습니다. 다른 문서를 조회하면 그 문서만의 결과를 보여 주며 빈 문서의 통계는 모두 0입니다.
사용 흐름 예시
1.
Hi, 가로 4·세로 3인 cat.png, ! 순서로 등록하면 그 순서로 세 줄을 보여 줍니다. 통계는 텍스트 문자 수 3·이미지 1개·면적 12입니다.
2.
같은 통계를 다시 조회해도 같습니다. 이어 빈 문서의 통계를 보면 세 값 모두 0이며 앞 문서의 값이 더해지지 않습니다.
다음 기획 변경
텍스트 요소의 개수만 세는 새 업무를 추가하려고 합니다. 이후 표 요소도 들어올 수 있으므로 새 업무와 새 문서 요소의 추가를 고려해 보세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { TextElement hello("Hi"), mark("!"); ImageElement image("cat.png", 4, 3); DocumentElement* elements[] = { &hello, &image, &mark }; Document document; for (DocumentElement* element : elements) { const bool ok = document.Add(element); assert(ok); } PrintJob print; StatisticsJob statistics; DocumentJob* jobs[] = { &print, &statistics }; for (DocumentJob* job : jobs) { document.Run(*job); } assert(statistics.TextCharacters() == 3); assert(statistics.ImageCount() == 1 && statistics.ImageArea() == 12); document.Run(statistics); assert(statistics.TextCharacters() == 3); // 누적하지 않음 Document empty; empty.Run(statistics); assert(statistics.TextCharacters() == 0); assert(statistics.ImageCount() == 0 && statistics.ImageArea() == 0); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 49. 문서 상태 저장과 복원

기획 의도
문서 편집기에서 문구와 커서 위치를 한 번에 바꾸고 이전 상태로 돌아갑니다. 사용자는 취소할 때 텍스트뿐 아니라 당시의 커서 위치도 되돌려 받습니다.
기본 기능과 동작 규칙
•
처음 문서는 빈 텍스트·커서 위치 0입니다. 텍스트는 ASCII이며 커서는 0부터 문자열 길이까지입니다. 위치 0은 첫 글자 앞, 길이와 같은 위치는 마지막 글자 뒤입니다.
•
새 텍스트와 커서를 하나의 편집 요청으로 적용합니다. 새 커서가 새 텍스트 범위 밖이면 전체 편집을 거절하고 이전 텍스트·커서를 유지합니다.
확장 요청
•
성공한 편집을 최대 8건, 마지막 편집부터 취소할 수 있습니다. 현재 텍스트와 커서가 모두 같은 요청은 변화가 없으므로 취소 건수를 늘리지 않습니다.
•
잘못된 편집도 취소 건수를 늘리지 않습니다. 8건이 가득 차면 새 편집을 거절합니다. 취소할 편집이 없으면 거절하며 이번에는 재실행 기능은 없습니다.
사용 흐름 예시
1.
빈 문서에서 A·커서 1, AB·2, ABC·3으로 편집하면 취소 가능한 편집은 3건입니다.
2.
이어서 A·커서 2로 바꾸려 하면 거절되고 ABC·3이 남습니다. 취소 세 번은 AB·2, A·1, 빈 텍스트·0 순으로 돌아갑니다.
3.
모두 취소한 뒤 새 편집을 하면 그 새 편집을 취소할 수 있습니다. 같은 빈 텍스트·0을 다시 적용하는 요청은 취소할 새 편집이 아닙니다.
다음 기획 변경
취소한 뒤 다른 문구를 새로 편집하는 흐름을 추가하려고 합니다. 가장 최근의 완료된 편집 전 상태로 돌아가야 합니다.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { DocumentEditor editor; bool ok = editor.Edit("A", 1); assert(ok); ok = editor.Edit("AB", 2); assert(ok); ok = editor.Edit("ABC", 3); assert(ok && editor.HistoryCount() == 3); ok = editor.Edit("A", 2); // 잘못된 커서 assert(!ok && editor.Text() == "ABC" && editor.HistoryCount() == 3); ok = editor.Undo(); assert(ok && editor.Text() == "AB" && editor.Cursor() == 2); ok = editor.Undo(); assert(ok && editor.Text() == "A" && editor.Cursor() == 1); ok = editor.Undo(); assert(ok && editor.Text().empty() && editor.Cursor() == 0); ok = editor.Undo(); assert(!ok && editor.HistoryCount() == 0); ok = editor.Edit("", 0); // 동일 상태는 변화 없음 assert(!ok && editor.HistoryCount() == 0); }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

문제 50. 작은 게임의 한 프레임 완성하기

기획 의도
플레이어가 동전을 얻고 짧은 이펙트가 사라지는 작은 게임입니다. 한 장면에서 이동과 수집 결과가 화면에 바로 반영되어야 하며, 이미 끝난 요소가 다음 장면에 다시 나타나면 안 됩니다.
기본 기능과 동작 규칙
•
플레이어는 x좌표 0·점수 0, 동전은 위치 1·점수 10, 이펙트는 남은 수명 2장면으로 시작합니다. 오른쪽 입력은 위치를 1 늘리고 입력 없음은 위치를 유지합니다.
•
장면은 입력 반영 → 참여 요소 전체 진행 → 동전 수집 판단 → 화면 표시 → 종료한 요소 정리 순서입니다. 동전은 스스로 움직이지 않고 이펙트는 장면 진행마다 수명이 1 줄어듭니다.
확장 요청
•
플레이어가 활성 동전과 같은 위치이면 점수 10을 얻고 동전은 수집 완료됩니다. 같은 동전을 다시 얻을 수 없습니다. 이펙트 수명이 0이면 종료됩니다.
•
수집된 동전과 종료된 이펙트는 그 장면 화면부터 보이지 않습니다. 장면이 끝난 후 다음 장면의 참여 목록에서도 제외합니다.
사용 흐름 예시
1.
첫 오른쪽 장면에서 플레이어는 위치 1·점수 10, 이펙트는 남은 수명 1입니다. 화면에는 플레이어와 이펙트만 보이고 동전은 이미 사라집니다.
2.
다음 입력 없음 장면에서 플레이어 위치 1·점수 10을 유지합니다. 이펙트도 종료되어 화면과 참여 목록에는 플레이어만 남습니다. 그다음 장면에서도 점수는 10입니다.
서비스 흐름
flowchart LR
 A["입력 반영"] --> B["참여 요소 전체 진행"]
 B --> C["동전 수집 판단"]
 C --> D["현재 결과를 화면에 표시"]
 D --> E["종료한 요소 정리"]
 E --> F["다음 장면 대기"]
Mermaid
복사
다음 기획 변경
같은 위치에 동전 두 개를 놓으려 합니다. 첫 수집 장면에 두 개를 모두 얻어 점수 20이 되어야 합니다. 이후 배경처럼 수집되지 않는 요소도 추가하세요.
제공 main() · 기획을 사용하는 예시
#include <iostream> #include <string> #include <cassert> // 이 위치에 필요한 클래스를 직접 구현하세요. int main() { Player player("Hero", 0); Coin coin("Coin", 1, 10); // 이름, x좌표, 점수 Effect effect("Spark", 2); // 이름, 남은 프레임 GameObject* objects[] = { &player, &coin, &effect }; GameWorld world(player); for (GameObject* object : objects) { const bool ok = world.Add(object); assert(ok); } world.Frame("right"); assert(player.GetX() == 1 && player.Score() == 10); assert(!coin.IsActive() && effect.IsActive()); assert(effect.RemainingFrames() == 1 && world.Count() == 2); // 표시: 플레이어, 이펙트. 동전은 같은 프레임부터 미표시. world.Frame("none"); assert(player.Score() == 10 && !effect.IsActive()); assert(effect.RemainingFrames() == 0 && world.Count() == 1); world.Frame("none"); assert(world.Count() == 1 && player.Score() == 10); // world에서 제거돼도 지역 변수 coin, effect의 수명은 유지됨 }
C++
복사
풀고 나서 펼치기 · 설계 회고와 관련 개념

회차 기록과 완료 기준

문제·회차
읽은 핵심 규칙
자신의 설계 선택
실제 동작과 기획의 차이
추가 기획에 따른 변경
예: 16번·2회차
출금액과 수수료를 모두 낼 수 있어야 출금
선택한 구조와 그 이유를 본인이 기록
고객에게 보인 잔액이 기획과 일치했는지 기록
수수료 변경을 어디에 반영했는지 기록
첫 회차에는 기획을 이해하고 자신의 설계를 완성합니다. 두 번째 회차에는 이전 구현을 보지 않고 같은 기획과 main()에서 다시 만듭니다. 세 번째 회차에는 추가 기획을 적용하고 자신이 바꾼 판단을 기록합니다.
서비스의 기능과 규칙을 자신의 말로 설명할 수 있다.
기획을 바탕으로 대상·정보·행동·관계를 스스로 정했다.
제공 main()과 사용 흐름의 결과가 기획에 맞는다.
이전 구현을 보지 않고 다시 설계하고 구현했다.
추가 기획을 반영하면서 바꾼 설계와 이유를 설명할 수 있다.
완료 기준은 특정 패턴 이름이나 클래스 배치가 아니라 기획에 맞는 동작, 자신의 설계 이유, 반복해서 설계하는 능력입니다.
막힐 때만 보기 · 포인터와 역할 확인