오버라이딩과 오버로딩은 이름은 유사하지만 개념 자체는 많은 차이를 보입니다. 각각은 모두 개발 시 빈번하게 쓰입니다. 두 가지의 유용성을 모르고 사용하다 보면 적절치 못한 곳에 사용할 수 있기에 이번 시간은 각각의 개념과 차이 그리고 장단점을 알아보겠습니다.
[오버라이딩의 개념과 사용법]
자식 클래스가 부모 클래스로부터 상속받은 메서드를 재정의하는 것을 의미합니다. 이를 통해 자식 클래스는 부모 클래스의 기본 동작을 변경하거나 확장할 수 있습니다.
[사용법]
동물(Animal) 클래스와 이를 상속받는 개(Dog) 클래스 및 고양이(Cat) 클래스를 사용하는 예시 코드입니다.
부모 클래스 정의
public class Animal
{
// virtual 키워드를 사용하여 메서드를 정의
public virtual void MakeSound()
{
Console.WriteLine("동물이 소리를 냅니다");
}
}
자식 클래스에서 메서드 오버라이딩
public class Dog : Animal
{
// override 키워드를 사용하여 메서드를 오버라이딩
public override void MakeSound()
{
Console.WriteLine("개가 멍멍 짖습니다");
}
}
public class Cat : Animal
{
// override 키워드를 사용하여 메서드를 오버라이딩
public override void MakeSound()
{
Console.WriteLine("고양이가 야옹합니다");
}
}
사용 예시
using System;
public class Program
{
public static void Main()
{
Animal myAnimal = new Animal();
myAnimal.MakeSound(); // "동물이 소리를 냅니다" 출력
Dog myDog = new Dog();
myDog.MakeSound(); // "개가 멍멍 짖습니다" 출력
Cat myCat = new Cat();
myCat.MakeSound(); // "고양이가 야옹합니다" 출력
// 다형성 예시
Animal anotherDog = new Dog();
anotherDog.MakeSound(); // "개가 멍멍 짖습니다" 출력
Animal anotherCat = new Cat();
anotherCat.MakeSound(); // "고양이가 야옹합니다" 출력
}
}
virtual
키워드: 부모 클래스의 메서드에 사용하여 이 메서드가 오버라이딩될 수 있음을 명시합니다.override
키워드: 자식 클래스에서 부모 클래스의 메서드를 재정의(오버라이딩)할 때 사용합니다.
[오버라이딩의 장점과 단점]
[장점]
- 다형성(Polymorphism): 부모 클래스의 참조 변수가 자식 클래스의 객체를 가리킬 때, 적절한 자식 클래스의 메서드가 호출되도록 합니다.
- 코드 재사용성: 부모 클래스의 코드를 재사용하고 필요한 부분만 자식 클래스에서 변경할 수 있어 코드 중복을 줄입니다.
- 유연성: 부모 클래스의 메서드 동작을 자식 클래스에서 변경할 수 있어, 다양한 상황에 맞게 유연하게 동작을 정의할 수 있습니다.
아래는 장점에 대한 이해를 돕기위한 예시코드 입니다.
부모 클래스 정의
public class BankAccount
{
public virtual void CalculateInterest()
{
Console.WriteLine("기본 이자 계산");
}
public virtual void Withdraw(decimal amount)
{
Console.WriteLine($"기본 계좌에서 {amount} 원 출금");
}
}
자식 클래스에서 메서드 오버라이딩
public class SavingsAccount : BankAccount
{
public override void CalculateInterest()
{
Console.WriteLine("저축 계좌 이자 계산");
}
public override void Withdraw(decimal amount)
{
Console.WriteLine($"저축 계좌에서 {amount} 원 출금");
}
}
public class CheckingAccount : BankAccount
{
public override void CalculateInterest()
{
Console.WriteLine("당좌 계좌 이자 계산");
}
public override void Withdraw(decimal amount)
{
Console.WriteLine($"당좌 계좌에서 {amount} 원 출금");
}
}
사용 예시코드
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
// 다형성: 부모 클래스의 참조 변수가 자식 클래스의 객체를 가리킴
BankAccount savings = new SavingsAccount();
BankAccount checking = new CheckingAccount();
// 적절한 자식 클래스의 메서드가 호출됨
savings.CalculateInterest(); // "저축 계좌 이자 계산" 출력
savings.Withdraw(500); // "저축 계좌에서 500 원 출금" 출력
checking.CalculateInterest(); // "당좌 계좌 이자 계산" 출력
checking.Withdraw(500); // "당좌 계좌에서 500 원 출금" 출력
// 코드 재사용성: 부모 클래스의 코드를 재사용하고 필요한 부분만 자식 클래스에서 변경
BankAccount genericAccount = new BankAccount();
genericAccount.CalculateInterest(); // "기본 이자 계산" 출력
genericAccount.Withdraw(500); // "기본 계좌에서 500 원 출금" 출력
// 유연성: 부모 클래스의 메서드 동작을 자식 클래스에서 변경
List<BankAccount> accounts = new List<BankAccount> { new SavingsAccount(), new CheckingAccount(), new BankAccount() };
foreach (var account in accounts)
{
account.CalculateInterest();
account.Withdraw(1000);
}
// 출력:
// "저축 계좌 이자 계산"
// "저축 계좌에서 1000 원 출금"
// "당좌 계좌 이자 계산"
// "당좌 계좌에서 1000 원 출금"
// "기본 이자 계산"
// "기본 계좌에서 1000 원 출금"
}
다형성(Polymorphism)
BankAccount
타입의 참조 변수가SavingsAccount
와CheckingAccount
객체를 가리킬 때, 각각의 오버라이딩된 메서드(CalculateInterest
와Withdraw
)가 호출됩니다.- 이를 통해 다양한 계좌 유형이 동일한 인터페이스를 통해 서로 다르게 동작할 수 있습니다.
코드 재사용성
BankAccount
클래스의 메서드(CalculateInterest
와Withdraw
)는 모든 계좌 유형에 공통적으로 적용될 수 있는 기본 동작을 정의합니다.SavingsAccount
와CheckingAccount
클래스는 이러한 기본 동작을 재사용하고, 필요에 따라 특정 동작만 오버라이딩하여 변경합니다.- 이는 코드 중복을 줄이고, 부모 클래스의 코드를 효과적으로 재사용할 수 있게 합니다.
유연성
SavingsAccount
와CheckingAccount
클래스는BankAccount
클래스의 메서드를 오버라이딩하여, 각각의 계좌 유형에 맞게 동작을 변경합니다.- 이를 통해 다양한 계좌 유형에 맞게 유연하게 동작을 정의할 수 있습니다.
- 예를 들어,
accounts
리스트를 통해 각기 다른 계좌 객체를 처리할 때, 올바른 동작이 호출되도록 합니다.
[단점]
- 복잡성 증가: 많이 사용하면 코드의 흐름을 따라가기 어려워져서 유지보수가 복잡해질 수 있습니다.
- 성능 저하: 메서드 호출 시, 가상 메서드 테이블(vtable)을 사용하므로 약간의 성능 저하가 발생할 수 있습니다.
- 오버라이딩 실수: 자식 클래스에서 부모 클래스의 메서드를 의도하지 않게 오버라이딩할 경우, 예기치 않은 동작을 초래할 수 있습니다.
복잡성 증가에 대한 예시
public class Employee
{
public virtual void Work()
{
Console.WriteLine("Employee is working");
}
}
public class Manager : Employee
{
public override void Work()
{
Console.WriteLine("Manager is planning");
}
}
public class Director : Manager
{
public override void Work()
{
Console.WriteLine("Director is strategizing");
}
}
using System;
public class Program
{
public static void Main()
{
Employee employee = new Employee();
employee.Work(); // "Employee is working" 출력
Employee manager = new Manager();
manager.Work(); // "Manager is planning" 출력
Employee director = new Director();
director.Work(); // "Director is strategizing" 출력
}
}
오버라이딩 실수 예시
public class PaymentProcessor
{
public virtual void ProcessPayment()
{
Console.WriteLine("Processing payment in base class");
}
}
public class CreditCardPaymentProcessor : PaymentProcessor
{
public override void ProcessPayment()
{
// 실수로 부모 클래스 메서드를 오버라이딩하지 않음
base.ProcessPayment();
Console.WriteLine("Processing credit card payment");
}
}
using System;
public class Program
{
public static void Main()
{
PaymentProcessor paymentProcessor = new CreditCardPaymentProcessor();
paymentProcessor.ProcessPayment();
// 예상 출력:
// Processing payment in base class
// Processing credit card payment
// 실제 출력:
// Processing payment in base class
}
}
[오버로딩의 개념과 사용법]
하나의 클래스 내에서 동일한 이름을 가진 메서드를 여러 개 정의하는 것을 말합니다. 메서드 이름은 같지만, 매개변수의 타입, 개수, 또는 순서가 달라야 합니다.
[사용법]
- 메서드 이름이 같아야 합니다.
- 매개변수의 타입, 개수 또는 순서가 달라야 합니다.
- 반환 타입만 다른 것은 오버로딩으로 인정되지 않습니다.
public class Calculator
{
// 두 개의 정수를 더하는 메서드
public int Add(int a, int b)
{
return a + b;
}
// 세 개의 정수를 더하는 메서드
public int Add(int a, int b, int c)
{
return a + b + c;
}
// 두 개의 실수를 더하는 메서드
public double Add(double a, double b)
{
return a + b;
}
}
// 메인
using System;
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
// 두 개의 정수를 더하기
Console.WriteLine(calculator.Add(2, 3)); // 출력: 5
// 세 개의 정수를 더하기
Console.WriteLine(calculator.Add(1, 2, 3)); // 출력: 6
// 두 개의 실수를 더하기
Console.WriteLine(calculator.Add(1.1, 2.2)); // 출력: 3.3
}
}
[오버로딩의 장점과 단점]
[장점]
- 코드의 가독성 향상: 동일한 기능을 하는 메서드를 같은 이름으로 호출할 수 있어 코드의 가독성이 향상됩니다.
- 유연성 증가: 같은 기능을 다양한 입력 값으로 처리할 수 있어 코드의 유연성이 증가합니다.
- 유지보수성 향상: 하나의 이름으로 다양한 매개변수 조합을 처리할 수 있어, 새로운 메서드를 추가할 때 기존 코드에 미치는 영향이 적습니다.
예시코드
public class Printer
{
// 문자열을 출력하는 메서드
public void Print(string message)
{
Console.WriteLine(message);
}
// 정수를 출력하는 메서드
public void Print(int number)
{
Console.WriteLine(number);
}
// 실수를 출력하는 메서드
public void Print(double number)
{
Console.WriteLine(number);
}
}
public class Program
{
public static void Main()
{
Printer printer = new Printer();
// 문자열 출력
printer.Print("Hello, world!"); // 출력: Hello, world!
// 정수 출력
printer.Print(123); // 출력: 123
// 실수 출력
printer.Print(45.67); // 출력: 45.67
}
}
[단점]
- 혼동 가능성: 메서드의 매개변수가 너무 많거나 복잡해지면, 어떤 메서드가 호출될지 혼동할 수 있습니다.
- 코드의 복잡성 증가: 오버로딩된 메서드가 많아지면 코드가 복잡해지고 유지보수가 어려워질 수 있습니다.
- 디버깅 어려움: 어떤 오버로딩된 메서드가 호출될지 판단하기 어려울 수 있어 디버깅이 어려워질 수 있습니다.
예시코드
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public double Add(int a, double b)
{
return a + b;
}
public double Add(double a, int b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
// 어떤 Add 메서드가 호출될지 혼동 가능성
Console.WriteLine(calculator.Add(1, 2)); // 출력: 3 (int, int)
Console.WriteLine(calculator.Add(1.0, 2.0)); // 출력: 3.0 (double, double)
Console.WriteLine(calculator.Add(1, 2.0)); // 출력: 3.0 (int, double)
Console.WriteLine(calculator.Add(1.0, 2)); // 출력: 3.0 (double, int)
Console.WriteLine(calculator.Add(1, 2, 3)); // 출력: 6 (int, int, int)
}
}
[오버라이딩과 오버로딩의 차이와 사용 상황 구분]
각각의 차이는 오버라이딩은 여러 동작을 재정의 하는 곳에 쓰이고, 오버로딩은 같은 메서드를 다양한 매개변수를 다루는데 사용이 됩니다. 아래는 게임 개발에서 어떨 때 오버라이딩과 오버로딩이 사용되는지를 보여줍니다.
[오버라이딩]
부모 클래스에는 캐릭터의 이동 메서드가 있을 수 있습니다. 자식 클래스에서는 이 메서드를 재정의하여 각 캐릭터마다 다른 이동 로직을 구현할 수 있습니다. 아래는 그 예시 입니다.
// 부모 클래스
public class Character
{
public virtual void Move()
{
Console.WriteLine("Character is moving");
}
}
// 자식 클래스
public class Player : Character
{
public override void Move()
{
Console.WriteLine("Player is running");
}
}
// 게임에서의 사용
Character character = new Player();
character.Move(); // 출력: "Player is running"
[오버로딩]
캐릭터의 데미지를 계산하는 메서드가 있을 수 있습니다. 이 메서드를 오버로딩하여 캐릭터가 받는 데미지를 다르게 계산하는 경우를 처리할 수 있습니다. 예를 들어, 물리 공격에 대한 데미지 계산 메서드와 마법 공격에 대한 데미지 계산 메서드를 구현할 수 있습니다.
public class DamageCalculator
{
public int CalculateDamage(int attackPower)
{
return attackPower;
}
public int CalculateDamage(int attackPower, int defensePower)
{
return attackPower - defensePower;
}
public int CalculateDamage(int attackPower, float criticalMultiplier)
{
return (int)(attackPower * criticalMultiplier);
}
}
// 게임에서의 사용
DamageCalculator calculator = new DamageCalculator();
int baseDamage = calculator.CalculateDamage(100); // 데미지 계산
int damageWithDefense = calculator.CalculateDamage(100, 20); // 방어력을 고려한 데미지 계산
int damageWithCritical = calculator.CalculateDamage(100, 1.5f); // 크리티컬 공격에 대한 데미지 계산