📘 15.3.3 나만의 방식으로 바꾸기 - 부모님 방식을 내 스타일로 개선하는 특별한 기술
창밖으로 지는 해를 바라보며, 우리가 지금까지 걸어온 길을 돌아봅니다. 특징 물려받기와 부모님께 도움 요청하기를 통해 부모 클래스의 지혜를 자식 클래스에서 조용히 활용하는 방법을 배웠습니다. 그런데 때로는 부모가 하시던 방식이 아니라 내 방식대로 하고 싶을 때가 있지요.
마치 부모님이 알려주신 인사 방법이 있지만, 내가 더 재미있고 특별하게 인사하고 싶은 그런 순간처럼요. 오늘은 이런 "내 방식으로 바꾸기"를 프로그래밍 세계에서 구현하는 나만의 방식으로 바꾸기의 따뜻한 이야기를 함께 나누어보겠습니다.
🧠 새로운 단어들과 친해지기
나만의 방식으로 바꾸기를 이해하기 위해 몇 가지 소중한 단어들을 조용히 살펴보겠습니다.
단어 | 쉬운 설명 |
---|---|
나만의 방식으로 바꾸기 | 부모 클래스의 기능을 자식 클래스에서 다시 만드는 것이에요 |
여러 형태 | 같은 이름의 기능이 물건에 따라 다르게 동작하는 특성이에요 |
확장하기 | 부모 기능의 기능을 유지하면서 새 기능 추가하기 |
기능 연결 | 부모부터 자식까지 연결된 기능들의 부르기 관계예요 |
"나만의 방식으로 바꾸기"는 "무언가를 덮어쓰다", "우선하다"라는 뜻을 가진 의미입니다. 부모 클래스의 기능보다 자식 클래스의 기능이 우선적으로 실행된다는 의미가 그 안에 조용히 담겨있어요.
✨ 나만의 방식으로 바꾸기의 핵심 개념
나만의 방식으로 바꾸기는 자바스크립트 특징 물려받기에서 개성과 특성을 표현하는 핵심 기능입니다. 부모 클래스에서 정의한 기능과 똑같은 이름의 기능을 자식 클래스에서 다시 정의하면, 자식 물건에서는 부모 기능 대신 자식 기능이 조용히 실행됩니다.
이것이 가능한 이유는 자바스크립트가 기능을 찾을 때 "가장 가까운 것부터" 찾기 때문입니다. 즉, 자식 클래스에 해당 기능이 있으면 자식 것을 사용하고, 없으면 부모 클래스로 차분히 올라가서 찾는 방식이죠.
나만의 방식으로 바꾸기에는 두 가지 주요 방식이 있습니다. 첫 번째는 완전 새로 만들기로, 부모 기능을 전혀 사용하지 않고 완전히 새로운 방식으로 구현하는 것입니다. 두 번째는 확장하기로, super.기능()
을 사용해서 부모 기능의 기능을 활용하면서 추가 기능을 조심스럽게 더하는 것입니다.
이러한 나만의 방식 바꾸기를 통해 여러 형태라는 강력한 특성을 구현할 수 있어요. 같은 이름의 기능을 부르더라도 물건의 종류에 따라 다르게 동작하게 만들 수 있습니다.
재미있는 비유: 요리법을 내 방식으로 바꾸기
나만의 방식으로 바꾸기를 더 쉽게 이해하기 위해 '요리법을 내 방식으로 바꾸기'에 비유해볼게요.
할머니께서 전통 김치찌개 레시피를 정성스럽게 알려주셨다고 생각해보세요. 기본 과정은 이렇습니다: 1) 김치 볶기, 2) 물 넣기, 3) 끓이기, 4) 간하기. 이것이 부모 클래스의 기능과 같습니다.
그런데 당신은 이 기본 레시피를 두 가지 방식으로 바꿀 수 있어요. 첫 번째 방식은 완전히 새로운 방법으로 만드는 것입니다. 할머니 레시피는 무시하고 완전히 다른 재료와 방법으로 나만의 김치찌개를 조용히 만드는 거죠. 이것이 완전 새로 만들기 방식입니다.
두 번째 방식은 할머니 레시피 + 내 특별 재료를 더하는 것입니다. 할머니가 알려주신 기본 과정은 그대로 정중히 따르면서, 내가 좋아하는 버섯이나 치즈를 추가로 조심스럽게 넣는 거예요. 이것이 확장하기 방식입니다.
어떤 방식을 선택하든, 결국 "김치찌개 만들기"라는 이름은 같지만 누가 만드느냐에 따라 다른 맛이 나게 됩니다. 이것이 바로 프로그래밍에서 말하는 여러 형태의 아름다운 개념입니다.
🎯 나만의 방식으로 바꾸기를 사용하는 이유
그렇다면 우리는 왜 나만의 방식으로 바꾸기를 사용하게 될까요? 여러 소중한 이유들이 있습니다.
첫째로 개별 특성 구현이 가능합니다. 모든 동물이 소리를 낸다는 공통점이 있지만, 강아지는 "멍멍", 고양이는 "야옹"이라고 각자 다른 소리를 내야 하죠. 나만의 방식 바꾸기를 통해 이런 개별적인 특성을 자연스럽게 구현할 수 있어요.
둘째로 코드의 일관성을 유지할 수 있습니다. 모든 물건에서 같은 이름의 기능을 부르면 되므로, 사용하는 입장에서는 각 물건의 내부 구현을 몰라도 일관된 방식으로 코드를 작성할 수 있습니다.
셋째로 유연한 확장이 가능합니다. 부모 클래스의 기본 기능은 유지하면서도 각 자식 클래스의 특수한 요구사항을 쉽게 추가할 수 있어요.
마지막으로 여러 형태 활용을 통해 더 간결하고 확장성 있는 코드를 작성할 수 있습니다. 서로 다른 타입의 물건들을 하나의 바구니에 담고 같은 기능을 부르더라도, 각각 자신에게 맞는 방식으로 조용히 동작하게 만들 수 있어요.
⚙️ 기본 사용법 살펴보기
나만의 방식으로 바꾸기의 기본 사용법은 참으로 간단합니다. 자식 클래스에서 부모 클래스와 똑같은 이름의 기능을 정의하기만 하면 됩니다.
// 부모 클래스
class 부모클래스 {
기능이름() {
// 부모 기능 구현
}
}
// 자식 클래스
class 자식클래스 extends 부모클래스 {
// 기능 나만의 방식으로 바꾸기
기능이름() {
// 새로운 구현
}
}
나만의 방식으로 바꾸기의 두 가지 방법:
완전 새로 만들기 방식:
기능이름() {
// 완전히 새로운 구현
// super 부르기 없음
}
확장하기 방식:
기능이름() {
super.기능이름(); // 부모 기능 부르기
// 추가 기능
}
부르기 우선순위:
물건에서 기능을 부르면 다음 순서로 찾습니다:
- 자식 클래스에 기능이 있으면 → 자식 기능 실행
- 자식에 없고 부모에 있으면 → 부모 기능 실행
- 둘 다 없으면 → 에러 발생
🧪 직접 해보면서 배우기
이제 실제 예시를 통해 나만의 방식으로 바꾸기가 어떻게 조용히 동작하는지 자세히 알아보겠습니다.
🔹 첫 번째 예시: 동물 소리내기 - 기본적인 나만의 방식 바꾸기
첫 번째로는 가장 기본적인 나만의 방식으로 바꾸기를 알아보겠습니다.
// 부모 클래스: 동물
class Animal {
constructor(name) {
this.name = name; // 이름 저장하기
}
// 부모 기능
makeSound() {
console.log(this.name + "이(가) 소리를 내요.");
}
introduce() {
console.log("안녕하세요, 저는 " + this.name + "이에요.");
}
}
// 자식 클래스: 강아지
class Dog extends Animal {
constructor(name, breed) {
super(name); // 부모 만들기 도우미 부르기
this.breed = breed; // 품종 저장하기
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기
makeSound() {
console.log(this.name + ": 멍멍! 🐕");
}
}
// 자식 클래스: 고양이
class Cat extends Animal {
constructor(name, color) {
super(name); // 부모 만들기 도우미 부르기
this.color = color; // 색깔 저장하기
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기
makeSound() {
console.log(this.name + ": 야옹~ 🐱");
}
// 기능 나만의 방식으로 바꾸기 - 부모 기능 + 확장
introduce() {
// 부모 기능 부르기
super.introduce();
// 추가 내용
console.log("저는 " + this.color + " 고양이예요.");
}
}
// 나만의 방식으로 바꾸기 테스트
console.log("=== 동물 소리 테스트 ===");
let animal = new Animal("동물");
let dog = new Dog("바둑이", "말티즈");
let cat = new Cat("나비", "하얀색");
animal.makeSound(); // 부모 기능
dog.makeSound(); // 나만의 방식으로 바뀐 기능
cat.makeSound(); // 나만의 방식으로 바뀐 기능
console.log("\n=== 자기소개 테스트 ===");
animal.introduce(); // 부모 기능
cat.introduce(); // 확장된 기능
이 과정을 가만히 살펴보면, 먼저 부모 클래스 Animal
에서 기본 makeSound()
기능을 정의합니다. 그다음 Dog
와 Cat
클래스에서 같은 이름의 기능을 각자의 방식으로 다시 정의하죠. 물건을 만들어서 기능을 부르면 각각 자신의 버전이 조용히 실행되는 것을 확인할 수 있습니다.
🔹 두 번째 예시: 도형 넓이 계산 - 다양한 나만의 방식 바꾸기 방식
이번에는 도형 클래스를 통해 서로 다른 나만의 방식 바꾸기 방식을 천천히 비교해보겠습니다.
// 부모 클래스: 도형
class Shape {
constructor(color) {
if (color) {
this.color = color; // 색깔이 주어지면 저장하기
} else {
this.color = "검정"; // 주어지지 않으면 검정으로 하기
}
}
// 기본 넓이 계산 기능
getArea() {
return 0; // 기본 구현
}
// 정보 출력 기능
describe() {
console.log("이것은 " + this.color + "색 도형이에요.");
console.log("넓이: " + this.getArea());
}
}
// 자식 클래스: 사각형
class Rectangle extends Shape {
constructor(width, height, color) {
super(color); // 부모 만들기 도우미 부르기
this.width = width; // 가로 저장하기
this.height = height; // 세로 저장하기
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기
getArea() {
return this.width * this.height; // 가로 × 세로
}
// 기능 나만의 방식으로 바꾸기 - 부모 기능 + 확장
describe() {
super.describe(); // 부모 기능 부르기
console.log("가로: " + this.width + ", 세로: " + this.height);
console.log("이것은 사각형이에요.");
}
}
// 자식 클래스: 원
class Circle extends Shape {
constructor(radius, color) {
super(color); // 부모 만들기 도우미 부르기
this.radius = radius; // 반지름 저장하기
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기
getArea() {
return 3.14 * this.radius * this.radius; // 3.14 × 반지름 × 반지름
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기 (부모 기능 사용 안 함)
describe() {
console.log("이것은 " + this.color + "색 원이에요.");
console.log("반지름: " + this.radius);
console.log("넓이: " + this.getArea());
}
}
// 각 도형 만들고 테스트하기
console.log("=== 사각형 테스트 ===");
let rect = new Rectangle(5, 3, "파랑");
rect.describe();
console.log("\n=== 원 테스트 ===");
let circle = new Circle(4, "빨강");
circle.describe();
이 예시에서는 Rectangle
클래스가 부모의 describe()
기능을 확장하는 방식을, Circle
클래스가 완전히 새롭게 다시 정의하는 방식을 보여줍니다. 같은 나만의 방식 바꾸기라도 상황에 따라 다른 접근 방식을 조심스럽게 선택할 수 있다는 것을 알 수 있어요.
🔹 세 번째 예시: 메시지 시스템 - 여러 형태 활용하기
마지막으로는 나만의 방식으로 바꾸기를 통한 여러 형태 활용을 조용히 알아보겠습니다.
// 부모 클래스: 메시지
class Message {
constructor(sender, content) {
this.sender = sender; // 보내는 사람 저장하기
this.content = content; // 내용 저장하기
this.year = 2024; // 보낸 연도 저장하기
}
send() {
console.log("메시지를 보내요.");
console.log("보내는 사람: " + this.sender);
console.log("내용: " + this.content);
return true;
}
format() {
return this.sender + ": " + this.content;
}
}
// 자식 클래스: 이메일 메시지
class EmailMessage extends Message {
constructor(sender, content, subject) {
super(sender, content); // 부모 만들기 도우미 부르기
this.subject = subject; // 제목 저장하기
}
// 기능 나만의 방식으로 바꾸기 - 부모 + 확장
send() {
console.log("📧 이메일을 보내요.");
console.log("제목: " + this.subject);
// 부모 기능 부르기
super.send();
console.log("이메일 전송 완료!");
return true;
}
}
// 자식 클래스: SMS 메시지
class SMSMessage extends Message {
constructor(sender, content, phoneNumber) {
super(sender, content); // 부모 만들기 도우미 부르기
this.phoneNumber = phoneNumber; // 전화번호 저장하기
}
// 기능 나만의 방식으로 바꾸기 - 부모 + 확장
send() {
if (this.content.length > 50) { // 내용이 50글자보다 길면
console.log("SMS 메시지가 너무 길어요.");
return false;
}
console.log("📱 SMS를 보내요.");
console.log("전화번호: " + this.phoneNumber);
// 부모 기능 부르기
super.send();
console.log("SMS 전송 완료!");
return true;
}
}
// 여러 형태 테스트: 같은 기능을 불러도 물건에 따라 다르게 동작
function sendAllMessages(messages) {
console.log("=== 모든 메시지 보내기 ===");
for (let i = 0; i < messages.length; i++) {
console.log("\n--- 메시지 " + (i + 1) + " ---");
messages[i].send(); // 각 물건의 나만의 방식으로 바뀐 기능 부르기
}
}
// 다양한 메시지 만들기
let basicMessage = new Message("관리자", "시스템 점검 예정");
let email = new EmailMessage("admin@site.com", "점검 안내예요.", "시스템 점검");
let sms = new SMSMessage("시스템", "점검 예정", "010-1234-5678");
// 바구니에 담아서 일괄 처리하기 (여러 형태 활용)
let allMessages = [basicMessage, email, sms];
sendAllMessages(allMessages);
이 예시에서는 서로 다른 타입의 메시지 물건들을 하나의 바구니에 담고, 같은 send()
기능을 부르지만 각각 자신에게 맞는 방식으로 조용히 동작하는 여러 형태의 강력함을 보여줍니다.
🔄 나만의 방식으로 바꾸기 과정 정리하기
지금까지 배운 나만의 방식으로 바꾸기 과정을 차근차근 정리해보겠습니다.
첫 번째 단계는 부모 클래스 기능 정의하기입니다. 기본적인 동작을 하는 기능을 부모 클래스에 조용히 만드는 것이죠. 두 번째 단계로는 자식 클래스에서 같은 이름 기능 다시 만들기입니다. 부모와 정확히 같은 이름의 기능을 자식 클래스에서 새롭게 구현합니다.
세 번째 단계는 나만의 방식 바꾸기 방식 선택하기입니다. 완전 새로 만들지, 아니면 super
를 사용해서 부모 기능을 확장할지 조심스럽게 결정하죠. 네 번째 단계로는 물건 만들기 및 부르기입니다. 자식 물건을 만들고 기능을 부르면 나만의 방식으로 바뀐 버전이 조용히 실행됩니다.
마지막으로 다섯 번째 단계는 여러 형태 활용하기에서 서로 다른 타입의 물건들을 같은 방식으로 다루면서도 각각의 특성에 맞게 동작하도록 할 수 있습니다.
🧚♀️ 이야기로 다시 배우기: 학교 발표 대회
지금까지 배운 내용을 하나의 따뜻한 이야기로 다시 정리해볼까요?
학교에서 "자기소개 발표 대회"가 열렸습니다. 선생님께서는 모든 학생에게 기본 발표 형식을 정성스럽게 알려주셨어요: "안녕하세요, 저는 ○○예요. 만나서 반가워요."
하지만 각 학생들은 자신만의 특별한 방식으로 발표를 했습니다. 철수는 완전히 새로운 방식으로 "안녕하세요! 저는 장래 축구선수가 되고 싶은 철수예요. 함께 축구해요!"라고 발표했어요. 이것은 선생님이 알려준 기본 형식을 완전히 무시하고 자신만의 방식으로 한 것입니다.
영희는 기본 형식에 자신만의 특색을 조심스럽게 더했습니다. "안녕하세요, 저는 영희예요. 만나서 반가워요. 그리고 저는 그림 그리기를 정말 좋아해요!"라고 발표했죠. 이것은 선생님이 알려준 기본 형식을 그대로 사용하면서 자신만의 내용을 추가한 것입니다.
민수는 상황에 따라 다르게 발표했습니다. 1학년 동생들 앞에서는 "안녕! 나는 민수 형이야!"라고 친근하게, 선생님들 앞에서는 "안녕하세요, 저는 민수예요"라고 정중하게 말했어요.
이처럼 같은 "자기소개하기"라는 이름의 활동이지만, 각 학생마다 자신에게 맞는 방식으로 다르게 표현한 것이 바로 프로그래밍에서 말하는 나만의 방식으로 바꾸기와 여러 형태의 아름다운 개념입니다.
🧠 자주 하는 실수와 주의할 점
나만의 방식으로 바꾸기를 사용할 때 자주 발생하는 실수들을 미리 알아두면 더 안전한 코딩을 할 수 있어요.
❌ 실수 1: 기능 이름을 다르게 작성하기
class Animal {
makeSound() {
console.log("동물 소리");
}
}
// 잘못된 예시 - 이름이 다름 (나만의 방식 바꾸기 아님)
class Dog extends Animal {
bark() { // makeSound가 아닌 다른 이름!
console.log("멍멍!");
}
}
// 올바른 예시 - 같은 이름 사용
class Cat extends Animal {
makeSound() { // 부모와 정확히 같은 이름
console.log("야옹~");
}
}
let dog = new Dog();
let cat = new Cat();
dog.makeSound(); // "동물 소리" (부모 기능)
dog.bark(); // "멍멍!" (새로운 기능, 나만의 방식 바꾸기 아님)
cat.makeSound(); // "야옹~" (제대로 나만의 방식으로 바뀜)
이런 실수가 발생하는 이유는 나만의 방식 바꾸기가 성립하려면 기능 이름이 부모 클래스와 정확히 일치해야 하기 때문입니다. 다른 이름을 사용하면 새로운 기능이 되어버려요.
❌ 실수 2: super 부르기 시 기능 이름을 틀리게 쓰기
class Vehicle {
start() {
console.log("차량 시동 걸기");
}
}
// 잘못된 예시 - super 기능 이름 틀림
class Car extends Vehicle {
start() {
super.begin(); // begin이라는 기능은 부모에 없어요!
console.log("자동차 시동 걸림");
}
}
// 올바른 예시 - 올바른 부모 기능 이름 사용
class Truck extends Vehicle {
start() {
super.start(); // 올바른 기능 이름
console.log("트럭 시동 걸림");
}
}
try {
let car = new Car();
car.start(); // 에러가 날 거예요!
} catch (error) {
console.log("에러: 부모에 없는 기능을 불렀어요.");
}
let truck = new Truck();
truck.start(); // 정상 작동
super
로 부모 기능을 부를 때는 부모 클래스에 실제로 존재하는 기능 이름을 정확히 사용해야 합니다.
❌ 실수 3: 부모 기능의 기능을 완전히 무시하고 중요한 로직 누락
class BankAccount {
constructor(balance) {
this.balance = balance; // 잔액 저장하기
this.transactions = []; // 거래 내역을 담을 바구니 만들기
}
withdraw(amount) {
if (amount > this.balance) { // 잔액보다 많이 빼려고 하면
console.log("잔액이 부족해요.");
return false;
}
this.balance = this.balance - amount; // 잔액에서 돈 빼기
this.transactions.push("돈 빼기: " + amount + "원"); // 거래 내역에 기록하기
console.log(amount + "원 빼기 완료. 잔액: " + this.balance + "원");
return true;
}
}
// 잘못된 예시 - 중요한 검증 로직 누락
class BadSavingsAccount extends BankAccount {
withdraw(amount) {
// 부모의 잔액 검증 로직을 무시하고 바로 출금!
this.balance = this.balance - amount; // 잔액 확인 없이 돈 빼기
console.log("적금 계좌에서 " + amount + "원 뺐어요");
// 거래 내역 기록도 누락!
}
}
// 올바른 예시 - 부모 기능 활용
class GoodSavingsAccount extends BankAccount {
withdraw(amount) {
// 부모 기능 부르기 (검증 및 기본 로직 수행)
const success = super.withdraw(amount);
if (success) {
// 추가 기능
console.log("적금 계좌에서 뺐어요.");
}
return success;
}
}
// 테스트
let badAccount = new BadSavingsAccount(1000);
badAccount.withdraw(1500); // 잔액보다 많이 빠지는 문제!
console.log("BadAccount 잔액:", badAccount.balance); // -500 (문제!)
let goodAccount = new GoodSavingsAccount(1000);
goodAccount.withdraw(1500); // 정상적으로 거부됨
console.log("GoodAccount 잔액:", goodAccount.balance); // 1000 (정상)
부모 기능에 중요한 검증이나 비즈니스 로직이 있다면, 그것을 무시하고 완전히 새로 구현하기보다는 super
를 활용하는 것이 안전합니다.
✏️ 연습문제로 개념 다지기
조용한 시간이 흘러간 지금, 배운 내용을 연습문제를 통해 차분히 익혀보겠습니다.
Ex1) 교통수단 클래스 계층을 만들고 나만의 방식으로 바꾸기를 사용해보자
// 부모 클래스: 교통수단
class Transportation {
constructor(name) {
this.name = name; // 이름 저장하기
this.speed = 0; // 속도를 0으로 시작하기
}
move() {
console.log(this.name + "이(가) 움직여요.");
}
getInfo() {
return this.name + " (속도: " + this.speed + "km/h)";
}
}
// 자식 클래스: 자동차
class Car extends Transportation {
constructor(name, fuel) {
super(name); // 부모 만들기 도우미 부르기
this.fuel = fuel; // 연료 저장하기
this.speed = 80; // 자동차 속도 설정하기
}
// 기능 나만의 방식으로 바꾸기 - 부모 + 확장
move() {
super.move(); // 부모 기능 부르기
console.log("🚗 연료를 사용해서 달려요. (연료: " + this.fuel + "L)");
}
}
// 자식 클래스: 자전거
class Bicycle extends Transportation {
constructor(name, type) {
super(name); // 부모 만들기 도우미 부르기
this.type = type; // 자전거 종류 저장하기
this.speed = 20; // 자전거 속도 설정하기
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기
move() {
console.log("🚲 " + this.name + " " + this.type + " 자전거가 페달을 밟으며 움직여요.");
}
}
// 테스트
let car = new Car("소나타", 50);
let bike = new Bicycle("삼천리", "산악용");
console.log("=== 움직임 테스트 ===");
car.move();
bike.move();
console.log("\n=== 정보 출력 ===");
console.log(car.getInfo());
console.log(bike.getInfo());
이 연습을 통해 부모 기능을 확장하는 방식과 완전히 다시 정의하는 방식의 차이를 익힐 수 있습니다.
Ex2) 게임 캐릭터 클래스에서 공격 기능 나만의 방식으로 바꾸기를 구현해보자
시간이 흘러 우리는 게임 캐릭터의 이야기를 통해 나만의 방식 바꾸기를 배워보겠습니calls.
// 부모 클래스: 게임 캐릭터
class GameCharacter {
constructor(name, level) {
this.name = name; // 이름 저장하기
if (level) {
this.level = level; // 레벨이 주어지면 저장하기
} else {
this.level = 1; // 주어지지 않으면 1로 하기
}
this.hp = this.level * 10; // 체력 = 레벨 × 10
this.attackPower = this.level * 2; // 공격력 = 레벨 × 2
}
attack(target) {
const damage = this.attackPower; // 데미지 = 공격력
console.log(this.name + "이(가) " + target.name + "을(를) 공격해요.");
console.log(damage + "의 데미지를 입혔어요.");
return damage;
}
getStatus() {
return this.name + " (레벨: " + this.level + ", 체력: " + this.hp + ")";
}
}
// 자식 클래스: 전사
class Warrior extends GameCharacter {
constructor(name, level) {
super(name, level); // 부모 만들기 도우미 부르기
this.attackPower = this.level * 3; // 전사는 공격력이 높아요
}
// 기능 나만의 방식으로 바꾸기 - 부모 + 확장
attack(target) {
console.log(this.name + " 전사가 검을 휘둘러요!");
// 간단한 강한 공격 (30% 확률 대신 3회마다 1번)
this.attackCount = this.attackCount || 0; // 공격 횟수 저장
this.attackCount++;
if (this.attackCount % 3 === 0) {
const damage = this.attackPower * 2; // 2배 데미지
console.log("🔥 강한 일격! " + damage + "의 데미지!");
return damage;
} else {
// 부모 기능 부르기
return super.attack(target);
}
}
}
// 자식 클래스: 마법사
class Mage extends GameCharacter {
constructor(name, level) {
super(name, level); // 부모 만들기 도우미 부르기
this.mana = this.level * 5; // 마나 = 레벨 × 5
}
// 기능 나만의 방식으로 바꾸기 - 완전 새로 만들기
attack(target) {
if (this.mana >= 3) { // 마나가 3 이상이면
console.log(this.name + " 마법사가 마법을 써요!");
this.mana = this.mana - 3; // 마나 3 소모
const damage = this.attackPower * 1.5; // 1.5배 데미지
console.log("🔮 마법 공격! " + damage + "의 데미지! (마나: " + this.mana + ")");
return damage;
} else {
console.log("마나가 부족해요! 기본 공격을 사용해요.");
return super.attack(target); // 마나 부족시 부모 기능 사용
}
}
}
// 테스트
let warrior = new Warrior("아서", 3);
let mage = new Mage("메를린", 3);
let monster = new GameCharacter("오크", 2);
console.log("=== 캐릭터 상태 ===");
console.log(warrior.getStatus());
console.log(mage.getStatus());
console.log("\n=== 전투 테스트 ===");
warrior.attack(monster);
mage.attack(monster);
이 문제는 나만의 방식 바꾸기를 통해 각 캐릭터만의 특별한 공격 방식을 자연스럽게 구현하는 방법을 연습하는 데 도움이 됩니다.
🤔 조금 더 어려운 문제로 실력 확인하기
기본 연습을 조용히 마쳤다면, 이제 조금 더 깊이 있는 문제들을 통해 나만의 방식으로 바꾸기에 대한 이해를 차분히 확인해보겠습니다.
Q1. 나만의 방식으로 바꾸기와 기능 여러 개 만들기의 차이점을 설명해보세요.
정답: 나만의 방식으로 바꾸기는 부모 클래스의 기능을 자식 클래스에서 같은 이름과 매개변수로 다시 정의하는 것입니다. 반면, 기능 여러 개 만들기는 같은 이름의 기능을 다른 매개변수로 여러 개 정의하는 것인데, 자바스크립트는 기능 여러 개 만들기를 공식적으로 지원하지 않습니다. 나만의 방식 바꾸기는 "기능 변경"에 관한 것이고, 기능 여러 개 만들기는 "다양한 입력 처리"에 관한 것입니다.
Q2. 여러 형태가 왜 중요한지 실제 예시와 함께 설명해보세요.
정답: 여러 형태는 같은 기능 이름으로 서로 다른 물건들을 일관된 방식으로 다룰 수 있게 해주기 때문에 중요합니다. 예를 들어 게임에서 다양한 몬스터들(오크, 드래곤, 슬라임)이 있을 때, 각각 다른 공격 방식을 가지지만 모두 attack()
기능으로 공격할 수 있어요. 이렇게 하면 몬스터 바구니에서 반복문으로 monster.attack()
을 부르기만 하면 되고, 새로운 몬스터를 추가할 때도 기존 코드를 수정할 필요가 없어서 코드가 확장하기 쉬워집니다.
📚 복습 문제 - 이전에 배운 내용 기억하기
이제 이전 단원에서 배운 중요한 내용들을 복습해보며 기억을 새롭게 해볼게요!
🔄 복습 1: super 키워드 활용 (15.3.2 단원 복습)
// 문제: 다음 코드에서 빈 칸을 채워 Vehicle 클래스와 Car 클래스를 완성해보세요.
// 부모 클래스: 차량
class Vehicle {
constructor(brand, model) {
this.brand = brand;
this.model = model;
this.speed = 0;
}
start() {
console.log(this.brand + " " + this.model + " 시동을 걸었어요.");
}
accelerate(amount) {
this.speed = this.speed + amount;
console.log("속도가 " + amount + "km/h 늘어났어요. 현재 속도: " + this.speed + "km/h");
}
}
// 자식 클래스: 자동차 - 빈 칸을 채워주세요!
class Car extends Vehicle {
constructor(brand, model, fuel) {
______(brand, model); // 빈 칸 1: 부모 생성자 호출
this.fuel = fuel;
}
start() {
______.start(); // 빈 칸 2: 부모 메서드 호출
console.log("연료 확인: " + this.fuel + "L");
}
accelerate(amount) {
if (this.fuel > 0) {
______._______(amount); // 빈 칸 3, 4: 부모 메서드 호출
this.fuel--;
console.log("연료 소모! 남은 연료: " + this.fuel + "L");
} else {
console.log("연료가 부족해요!");
}
}
}
// 테스트 코드
let myCar = new Car("현대", "소나타", 5);
myCar.start();
myCar.accelerate(30);
정답:
class Car extends Vehicle {
constructor(brand, model, fuel) {
super(brand, model); // 빈 칸 1: 부모 생성자 호출
this.fuel = fuel;
}
start() {
super.start(); // 빈 칸 2: 부모 메서드 호출
console.log("연료 확인: " + this.fuel + "L");
}
accelerate(amount) {
if (this.fuel > 0) {
super.accelerate(amount); // 빈 칸 3, 4: 부모 메서드 호출
this.fuel--;
console.log("연료 소모! 남은 연료: " + this.fuel + "L");
} else {
console.log("연료가 부족해요!");
}
}
}
해설: super 키워드를 사용해서 부모 클래스의 생성자와 메서드를 호출할 수 있습니다. 생성자에서는 super(매개변수)
로, 메서드에서는 super.메서드명()
으로 부모 기능을 활용할 수 있어요.
🔄 복습 2: 상속과 메서드 오버라이딩 종합 문제
// 문제: 다음 코드의 실행 결과를 예측해보세요.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + "이(가) 소리를 내요.");
}
introduce() {
console.log("안녕하세요, 저는 " + this.name + "이에요.");
this.speak(); // 여기서 어떤 speak()가 호출될까요?
}
}
class Dog extends Animal {
speak() {
console.log(this.name + ": 멍멍!");
}
}
let myDog = new Dog("바둑이");
myDog.introduce(); // 이 코드의 출력 결과는?
정답:
안녕하세요, 저는 바둑이예요.
바둑이: 멍멍!
해설: introduce()
메서드는 Dog 클래스에서 나만의 방식으로 바뀌지 않아 Animal의 메서드가 실행되지만, 그 안에서 호출하는 this.speak()
는 현재 객체(myDog)의 speak()
메서드를 호출합니다. 즉, Dog 클래스에서 나만의 방식으로 바뀐 speak()
메서드가 실행되어 "멍멍!"이 출력됩니다. 이것이 여러 형태의 핵심 개념입니다.
지금까지 나만의 방식으로 바꾸기의 모든 특성과 활용법을 자세히 알아보았습니다. 나만의 방식으로 바꾸기는 특징 물려받기의 강력함을 실제로 활용할 수 있게 해주는 핵심 기능이며, 여러 형태를 통해 더욱 유연하고 확장 가능한 프로그램을 만들 수 있게 해주는 중요한 개념입니다.
✅ 학습 완료 체크리스트
이번 시간에 배운 내용들을 모두 이해했는지 확인해보세요!
학습 내용 | 이해했나요? |
---|---|
메서드 오버라이딩의 기본 개념 | ✅ |
완전 재정의 vs 부모 메서드 확장 | ✅ |
다형성과 메서드 오버라이딩의 관계 | ✅ |
자주 하는 실수들과 해결법 | ✅ |
실전 예제 이해 | ✅ |
📂 마무리 정보
오늘 배운 15.3.3
내용이 여러분의 자바스크립트 지식 상자에 잘 저장되었나요? 다음 시간에는 더 재미있는 내용으로 만나요!
기억할 점: 메서드 오버라이딩은 상속에서 각 클래스의 개성을 표현하는 중요한 방법이에요. 꼭 연습해보시고, 궁금한 점이 있으면 언제든 다시 돌아와서 읽어보세요.
무료 JavaScript 학습 플랫폼에서 단계별 학습과 실시간 코드 실행을 통해
더욱 효과적이고 재미있게 학습하실 수 있습니다.
'15. 클래스로 틀 만들기 (클래스) > 15.3 상속 (Inheritance)' 카테고리의 다른 글
15.3.2 부모님께 도움 요청하기 - 가족의 힘을 빌리는 특별한 방법 (0) | 2025.07.23 |
---|---|
15.3.1 부모의 특징을 물려받기 - 가족처럼 연결되는 특별한 방법 (0) | 2025.07.23 |