오버라이딩과 오버로딩은 이름은 유사하지만 개념 자체는 많은 차이를 보입니다. 각각은 모두 개발 시 빈번하게 쓰입니다. 두 가지의 유용성을 모르고 사용하다 보면 적절치 못한 곳에 사용할 수 있기에 이번 시간은 각각의 개념과 차이 그리고 장단점을 알아보겠습니다.


[오버라이딩의 개념과 사용법]

자식 클래스가 부모 클래스로부터 상속받은 메서드를 재정의하는 것을 의미합니다. 이를 통해 자식 클래스는 부모 클래스의 기본 동작을 변경하거나 확장할 수 있습니다.

[사용법]

동물(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 키워드: 자식 클래스에서 부모 클래스의 메서드를 재정의(오버라이딩)할 때 사용합니다.

[오버라이딩의 장점과 단점]

[장점]

  1. 다형성(Polymorphism): 부모 클래스의 참조 변수가 자식 클래스의 객체를 가리킬 때, 적절한 자식 클래스의 메서드가 호출되도록 합니다.
  2. 코드 재사용성: 부모 클래스의 코드를 재사용하고 필요한 부분만 자식 클래스에서 변경할 수 있어 코드 중복을 줄입니다.
  3. 유연성: 부모 클래스의 메서드 동작을 자식 클래스에서 변경할 수 있어, 다양한 상황에 맞게 유연하게 동작을 정의할 수 있습니다.

아래는 장점에 대한 이해를 돕기위한 예시코드 입니다.

부모 클래스 정의

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 타입의 참조 변수가 SavingsAccountCheckingAccount 객체를 가리킬 때, 각각의 오버라이딩된 메서드(CalculateInterestWithdraw)가 호출됩니다.
  • 이를 통해 다양한 계좌 유형이 동일한 인터페이스를 통해 서로 다르게 동작할 수 있습니다.

코드 재사용성

  • BankAccount 클래스의 메서드(CalculateInterestWithdraw)는 모든 계좌 유형에 공통적으로 적용될 수 있는 기본 동작을 정의합니다.
  • SavingsAccountCheckingAccount 클래스는 이러한 기본 동작을 재사용하고, 필요에 따라 특정 동작만 오버라이딩하여 변경합니다.
  • 이는 코드 중복을 줄이고, 부모 클래스의 코드를 효과적으로 재사용할 수 있게 합니다.

유연성

  • SavingsAccountCheckingAccount 클래스는 BankAccount 클래스의 메서드를 오버라이딩하여, 각각의 계좌 유형에 맞게 동작을 변경합니다.
  • 이를 통해 다양한 계좌 유형에 맞게 유연하게 동작을 정의할 수 있습니다.
  • 예를 들어, accounts 리스트를 통해 각기 다른 계좌 객체를 처리할 때, 올바른 동작이 호출되도록 합니다.

[단점]

  1. 복잡성 증가: 많이 사용하면 코드의 흐름을 따라가기 어려워져서 유지보수가 복잡해질 수 있습니다.
  2. 성능 저하: 메서드 호출 시, 가상 메서드 테이블(vtable)을 사용하므로 약간의 성능 저하가 발생할 수 있습니다.
  3. 버라이딩 실수: 자식 클래스에서 부모 클래스의 메서드를 의도하지 않게 오버라이딩할 경우, 예기치 않은 동작을 초래할 수 있습니다.

복잡성 증가에 대한 예시

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
    }
}

[오버로딩의 개념과 사용법]

하나의 클래스 내에서 동일한 이름을 가진 메서드를 여러 개 정의하는 것을 말합니다. 메서드 이름은 같지만, 매개변수의 타입, 개수, 또는 순서가 달라야 합니다.

[사용법]

  1. 메서드 이름이 같아야 합니다.
  2. 매개변수의 타입, 개수 또는 순서가 달라야 합니다.
  3. 반환 타입만 다른 것은 오버로딩으로 인정되지 않습니다.
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
    }
}

[오버로딩의 장점과 단점]

[장점]

  1. 코드의 가독성 향상: 동일한 기능을 하는 메서드를 같은 이름으로 호출할 수 있어 코드의 가독성이 향상됩니다.
  2. 유연성 증가: 같은 기능을 다양한 입력 값으로 처리할 수 있어 코드의 유연성이 증가합니다.
  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
    }
}

[단점]

  1. 혼동 가능성: 메서드의 매개변수가 너무 많거나 복잡해지면, 어떤 메서드가 호출될지 혼동할 수 있습니다.
  2. 코드의 복잡성 증가: 오버로딩된 메서드가 많아지면 코드가 복잡해지고 유지보수가 어려워질 수 있습니다.
  3. 디버깅 어려움: 어떤 오버로딩된 메서드가 호출될지 판단하기 어려울 수 있어 디버깅이 어려워질 수 있습니다.

예시코드

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); // 크리티컬 공격에 대한 데미지 계산

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다