From 999c8bb62d93d95e7fd3f7988247edccd4b16d4d Mon Sep 17 00:00:00 2001 From: ooweaJ Date: Tue, 16 Jun 2026 15:48:43 +0900 Subject: [PATCH] feat : Wizard,GreatWizard --- .../2026-06-16-\354\203\201\354\206\215.md" | 196 ++++++++++++++++++ .../java/com/survivalcoding/GreatWizard.java | 29 +++ .../main/java/com/survivalcoding/Hero.java | 38 +++- .../main/java/com/survivalcoding/Wizard.java | 22 ++ .../java/com/survivalcoding/gametest.java | 57 +++++ 5 files changed, 336 insertions(+), 6 deletions(-) create mode 100644 "TIL/sample/2026-06-16-\354\203\201\354\206\215.md" create mode 100644 game/src/main/java/com/survivalcoding/GreatWizard.java create mode 100644 game/src/main/java/com/survivalcoding/Wizard.java create mode 100644 game/src/test/java/com/survivalcoding/gametest.java diff --git "a/TIL/sample/2026-06-16-\354\203\201\354\206\215.md" "b/TIL/sample/2026-06-16-\354\203\201\354\206\215.md" new file mode 100644 index 0000000..f1cffff --- /dev/null +++ "b/TIL/sample/2026-06-16-\354\203\201\354\206\215.md" @@ -0,0 +1,196 @@ +# 2026-06-16 자바 학습 정리 : 상속 + +## 오늘 배운 내용 + +- **상속(inheritance)** +- **오버라이드(override)** + +## 기억할 것 + +- java의 상속은 `extends` 키워드를 사용한다. +- JUnit의 `@DisplayName`은 테스트 결과 창에서 사람이 읽기 쉬운 이름을 표시하기 위한 어노테이션이다. +- 메서드 오버라이딩에서 java는 기본이 가상 메서드 이고 c++ `Virtual` 키워드를 붙여야한다. + +### 코드 예시 ( java ) + +```java +// 부모 클래스 +class Wizard { + public void heal() { + System.out.println("일반 힐!"); + } +} + +// 자식 클래스 +class GreatWizard extends Wizard { + @Override // 오버라이드 명시 (생략 가능하지만 가독성을 위해 필수) + public void heal() { + System.out.println("대마법사의 슈퍼 힐!"); + } +} + +public class Main { + public static void main(String[] args) { + // [핵심 상황] 부모 타입 변수에 자식 객체를 대입! + Wizard wizard = new GreatWizard(); + + wizard.heal(); + // 출력 결과: "대마법사의 슈퍼 힐!" (알아서 알맹이를 찾아감) + } +} +``` + +## C++ + +### 가상메서드 X + +```cpp +#include + +class Wizard { +public: + // ★ virtual이 없습니다! + void heal() { + std::cout << "일반 힐!\n"; + } +}; + +class GreatWizard : public Wizard { +public: + void heal() { + std::cout << "대마법사의 슈퍼 힐!\n"; + } +}; + +int main() { + // 부모 그릇에 자식 알맹이를 넣음 + Wizard* wizard = new GreatWizard(); + + // [결과] C++은 포인터가 Wizard니까 Wizard의 함수를 실행해버림 + wizard->heal(); // 출력: 일반 힐! (자식의 슈퍼 힐이 묻힘) + + return 0; +} +``` + +### 가상메서드 O + +```cpp +#include + +class Wizard { +public: + // ★ virtual을 붙여서 자식이 오버라이딩할 길을 열어줍니다! + virtual void heal() { + std::cout << "일반 힐!\n"; + } +}; + +class GreatWizard : public Wizard { +public: + // 부모가 virtual이면 자식은 자동으로 virtual이 됩니다. + void heal() override { + std::cout << "대마법사의 슈퍼 힐!\n"; + } +}; + +int main() { + // 부모 그릇에 자식 알맹이를 넣음 + Wizard* wizard = new GreatWizard(); + + // [결과] 부모에 virtual이 있으므로 진짜 알맹이인 GreatWizard의 함수를 실행함 + wizard->heal(); // 출력: 대마법사의 슈퍼 힐! (정상 작동) + + return 0; +} +``` + +## 실습 코드 + +```java +package com.survivalcoding; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GameTest { + + private Hero targetHero; + private Wizard wizard; + private GreatWizard greatWizard; + + @BeforeEach + void setUp() { + targetHero = new Hero("전사", 50, 100); + wizard = new Wizard("일반법사", 50, 50, 30, 50); + greatWizard = new GreatWizard("대법사", 60, 60, 60, 100); + } + + @Test + @DisplayName("일반 마법사가 힐을 하면 대상의 HP가 20 오르고 마나가 10 깎여야 한다") + void wizardHealTest() { + wizard.heal(targetHero); + + assertEquals(70, targetHero.getHp(), "전사의 HP는 50에서 20이 더해져 70이 되어야 합니다."); + } + + @Test + @DisplayName("대마법사가 슈퍼힐을 쓰면 대상의 HP가 최대치로 차고 마나가 50 깎여야 한다") + void greatWizardSuperHealTest() { + greatWizard.SuperHeal(targetHero); + + assertEquals(100, targetHero.getHp(), "슈퍼 힐을 받으면 최대 체력인 100이 되어야 합니다."); + } + + @Test + @DisplayName("마나가 부족하면 힐이 나가지 않고 체력도 그대로여야 한다") + void outOfManaTest() { + Wizard oomWizard = new Wizard("마나없는법사", 50, 50, 5, 50); + + oomWizard.heal(targetHero); + + assertEquals(50, targetHero.getHp(), "마나가 부족하므로 타겟의 HP는 변하면 안 됩니다."); + } + + @Test + @DisplayName("체력을 치유했을 때 최대 체력(MaxHP)을 초과할 수 없다") + void hpNotOverMaxHpTest() { + Hero highHpHero = new Hero("피통전사", 90, 100); + + wizard.heal(highHpHero); + + assertEquals(100, highHpHero.getHp(), "치유량의 총합이 MaxHP를 넘어가면 MaxHP로 고정되어야 합니다."); + } +} +``` + +## 어려웠던 점 + +- 오버라이딩 인식 차이: Java에서는 부모 타입 변수에 자식 객체를 담아도 알아서 자식 메서드가 호출되지만, C++에서는 부모 포인터 타입을 기준으로 함수를 실행해 버려 자식 메서드가 묻히는 정적 바인딩 현상을 + 직접 코드로 비교하며 개념을 정립하는 과정이 까다로웠다. +- JUnit 테스트 작성 중 `@DisplayName`이 적용되지 않는 것처럼 보여 원인을 찾는 데 시간이 걸렸다. + +## 해결 방법 + +- C++의 가상 함수 테이블(vtable) 구조 이해: C++ 부모 클래스에 virtual 키워드를 붙이면 컴퓨터가 내부적으로 '진짜 알맹이 객체'를 찾아갈 수 있도록 이정표(가상 함수 테이블)를 세워 동적 + 바인딩을 유도한다는 원리를 깨달으며 두 언어의 차이를 완벽히 이해했다. +- IntelliJ가 테스트를 Gradle Runner로 실행하고 있어 @DisplayName이 보이지 않았음을 확인했다. +- IntelliJ 설정에서 + +``` +설정 +└─ 빌드, 실행, 배포 +└─ 빌드 도구 +└─ Gradle +└─ 다음을 사용하여 테스트 실행 +Gradle → IntelliJ IDEA +``` + +로 변경하여 테스트 결과 창에서 `@DisplayName`을 확인할 수 있었다. + +## 내일 더 공부할 것 + +- 객체지향 프로그래밍(OOP)의 핵심을 이루는 4대 원칙 언어별 차이점 \ No newline at end of file diff --git a/game/src/main/java/com/survivalcoding/GreatWizard.java b/game/src/main/java/com/survivalcoding/GreatWizard.java new file mode 100644 index 0000000..4a341a1 --- /dev/null +++ b/game/src/main/java/com/survivalcoding/GreatWizard.java @@ -0,0 +1,29 @@ +package com.survivalcoding; + +public class GreatWizard extends Wizard { + + public GreatWizard(String name, int hp, int maxhp, int mp, int maxmp) { + super(name, hp, maxhp, mp, maxmp); + } + + @Override + public void heal(Hero hero) { + if (mp >= 5) { + mp -= 5; + hero.incrementHP(25); + System.out.printf("대마법사의 강력한 힐! %s HP : %d\n", hero.getName(), hero.getHp()); + } else { + System.out.println("마나가 부족합니다."); + } + } + + public void SuperHeal(Hero hero) { + if (mp >= 50) { + mp -= 50; + hero.incrementHP(hero.getMaxhp()); // 최대 체력만큼 힐 + System.out.printf("슈퍼 힐을 시전했습니다. %s HP : %d\n", hero.getName(), hero.getHp()); + } else { + System.out.println("마나가 부족합니다."); + } + } +} \ No newline at end of file diff --git a/game/src/main/java/com/survivalcoding/Hero.java b/game/src/main/java/com/survivalcoding/Hero.java index 9b266c9..d27ae7e 100644 --- a/game/src/main/java/com/survivalcoding/Hero.java +++ b/game/src/main/java/com/survivalcoding/Hero.java @@ -1,11 +1,37 @@ package com.survivalcoding; public class Hero { - String name; - int hp; + private String name; + private int hp; + private int maxhp; - void attack() { - // sout - System.out.println("공격했다"); + public Hero(String name, int hp, int maxhp) { + this.name = name; + this.hp = hp; + this.maxhp = maxhp; } -} + + public void attack() { + System.out.println(name + "이(가) 공격했다!"); + } + + public void incrementHP(int amount) { + if (amount + hp > maxhp) { + hp = maxhp; + } else { + hp += amount; + } + } + + public String getName() { + return name; + } + + public int getHp() { + return hp; + } + + public int getMaxhp() { + return maxhp; + } +} \ No newline at end of file diff --git a/game/src/main/java/com/survivalcoding/Wizard.java b/game/src/main/java/com/survivalcoding/Wizard.java new file mode 100644 index 0000000..8ed894b --- /dev/null +++ b/game/src/main/java/com/survivalcoding/Wizard.java @@ -0,0 +1,22 @@ +package com.survivalcoding; + +public class Wizard extends Hero { + protected int mp; + private int maxmp; + + public Wizard(String name, int hp, int maxhp, int mp, int maxmp) { + super(name, hp, maxhp); + this.mp = mp; + this.maxmp = maxmp; + } + + public void heal(Hero hero) { + if (mp >= 10) { + mp -= 10; + hero.incrementHP(20); + System.out.printf("힐을 시전했습니다. %s HP : %d\n", hero.getName(), hero.getHp()); + } else { + System.out.println("마나가 부족합니다."); + } + } +} \ No newline at end of file diff --git a/game/src/test/java/com/survivalcoding/gametest.java b/game/src/test/java/com/survivalcoding/gametest.java new file mode 100644 index 0000000..eb0f909 --- /dev/null +++ b/game/src/test/java/com/survivalcoding/gametest.java @@ -0,0 +1,57 @@ +package com.survivalcoding; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GameTest { + + private Hero targetHero; + private Wizard wizard; + private GreatWizard greatWizard; + + @BeforeEach + void setUp() { + targetHero = new Hero("전사", 50, 100); + wizard = new Wizard("일반법사", 50, 50, 30, 50); + greatWizard = new GreatWizard("대법사", 60, 60, 60, 100); + } + + @Test + @DisplayName("일반 마법사가 힐을 하면 대상의 HP가 20 오르고 마나가 10 깎여야 한다") + void wizardHealTest() { + wizard.heal(targetHero); + + assertEquals(70, targetHero.getHp(), "전사의 HP는 50에서 20이 더해져 70이 되어야 합니다."); + } + + @Test + @DisplayName("대마법사가 슈퍼힐을 쓰면 대상의 HP가 최대치로 차고 마나가 50 깎여야 한다") + void greatWizardSuperHealTest() { + greatWizard.SuperHeal(targetHero); + + assertEquals(100, targetHero.getHp(), "슈퍼 힐을 받으면 최대 체력인 100이 되어야 합니다."); + } + + @Test + @DisplayName("마나가 부족하면 힐이 나가지 않고 체력도 그대로여야 한다") + void outOfManaTest() { + Wizard oomWizard = new Wizard("마나없는법사", 50, 50, 5, 50); + + oomWizard.heal(targetHero); + + assertEquals(50, targetHero.getHp(), "마나가 부족하므로 타겟의 HP는 변하면 안 됩니다."); + } + + @Test + @DisplayName("체력을 치유했을 때 최대 체력(MaxHP)을 초과할 수 없다") + void hpNotOverMaxHpTest() { + Hero highHpHero = new Hero("피통전사", 90, 100); + + wizard.heal(highHpHero); + + assertEquals(100, highHpHero.getHp(), "치유량의 총합이 MaxHP를 넘어가면 MaxHP로 고정되어야 합니다."); + } +} \ No newline at end of file