본문 바로가기
Study/C#

C# 5편: 객체 지향 프로그래밍: 상속과 다형성으로 확장성 높은 코드 작성

by wawManager 2024. 10. 24.
728x90

1. 객체 지향 프로그래밍(OOP) 개요

객체 지향 프로그래밍(OOP)은 프로그램을 여러 객체로 구성하고, 이 객체들이 서로 상호작용하면서 동작하도록 설계하는 프로그래밍 패러다임입니다. C#은 강력한 객체 지향 언어로, 다음의 주요 OOP 원칙을 기반으로 합니다.

  • 상속(Inheritance): 기존 클래스를 기반으로 새로운 클래스를 만들 수 있습니다.
  • 다형성(Polymorphism): 동일한 메서드가 다양한 형태로 동작할 수 있습니다.
  • 캡슐화(Encapsulation): 객체의 데이터를 숨기고, 외부에서의 접근을 제한할 수 있습니다.
  • 추상화(Abstraction): 객체의 복잡성을 감추고, 필요한 부분만 노출합니다.

2. 상속(Inheritance)

상속은 기존 클래스의 특성을 물려받아 새로운 클래스를 정의하는 것을 말합니다. 상속을 사용하면 코드의 재사용성이 높아지고, 기존 클래스를 확장하여 기능을 추가할 수 있습니다.

기본 상속 문법

 
class 부모클래스
{
    // 부모 클래스의 멤버들
}

class 자식클래스 : 부모클래스
{
    // 자식 클래스의 멤버들
}

상속 예시

class Animal // 부모 클래스
{
    public string Name;

    public void Eat()
    {
        Console.WriteLine($"{Name}이(가) 먹고 있습니다.");
    }
}

class Dog : Animal // 자식 클래스
{
    public void Bark()
    {
        Console.WriteLine($"{Name}이(가) 짖습니다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dog myDog = new Dog();
        myDog.Name = "바둑이";
        myDog.Eat(); // 부모 클래스의 메서드 호출
        myDog.Bark(); // 자식 클래스의 메서드 호출
    }
}

위 코드에서 Dog 클래스는 Animal 클래스를 상속받아 Eat 메서드를 사용할 수 있습니다. 또한, Dog 클래스는 자신만의 Bark 메서드를 추가할 수 있습니다.

상속에서의 생성자 호출

자식 클래스가 생성될 때, 먼저 부모 클래스의 생성자가 호출됩니다. 이를 통해 부모 클래스의 초기화 작업이 먼저 이루어집니다.

class Animal
{
    public Animal()
    {
        Console.WriteLine("Animal 생성자 호출");
    }
}

class Dog : Animal
{
    public Dog()
    {
        Console.WriteLine("Dog 생성자 호출");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dog myDog = new Dog(); // 출력: Animal 생성자 호출, Dog 생성자 호출
    }
}

3. 다형성(Polymorphism)

다형성은 동일한 인터페이스나 메서드를 통해 다양한 객체가 서로 다르게 동작할 수 있는 성질을 의미합니다. 즉, 부모 클래스에서 정의한 메서드를 자식 클래스에서 재정의(오버라이딩)하여 각기 다른 동작을 수행할 수 있습니다.

메서드 오버라이딩(Method Overriding)

자식 클래스에서 부모 클래스의 메서드를 재정의하는 것을 메서드 오버라이딩이라고 합니다. C#에서는 virtual 키워드를 사용하여 부모 클래스의 메서드를 오버라이딩할 수 있게 허용하고, 자식 클래스에서는 override 키워드를 사용하여 재정의합니다.

예시: 메서드 오버라이딩

 
class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("동물이 소리를 냅니다.");
    }
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("강아지가 짖습니다.");
    }
}

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("고양이가 야옹합니다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Animal myAnimal = new Animal();
        myAnimal.MakeSound(); // 출력: 동물이 소리를 냅니다.

        Animal myDog = new Dog();
        myDog.MakeSound(); // 출력: 강아지가 짖습니다.

        Animal myCat = new Cat();
        myCat.MakeSound(); // 출력: 고양이가 야옹합니다.
    }
}

4. 캡슐화(Encapsulation)

캡슐화는 객체의 필드(변수)를 외부에서 직접 접근하지 못하도록 숨기고, 대신 메서드를 통해 접근하도록 하는 원칙입니다. 이를 통해 데이터의 무결성을 보호하고, 객체 내부의 상태를 안전하게 관리할 수 있습니다.

접근 제한자(Access Modifiers)

  • public: 어디서든 접근이 가능합니다.
  • private: 해당 클래스 내부에서만 접근이 가능합니다.
  • protected: 해당 클래스 및 상속받은 자식 클래스에서만 접근 가능합니다.

예시: 캡슐화

class Person
{
    private string name; // private 필드

    // public 속성: 필드에 간접 접근
    public string Name
    {
        get { return name; }
        set 
        {
            if (!string.IsNullOrEmpty(value))
            {
                name = value;
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        person.Name = "Alice"; // set 접근자 사용
        Console.WriteLine(person.Name); // get 접근자 사용
    }
}

위 코드에서 name 필드는 private으로 선언되어 직접 접근이 불가능하며, Name 속성을 통해 간접적으로 접근할 수 있습니다. 이로써 필드 값을 설정하거나 가져올 때 추가적인 로직(유효성 검사 등)을 추가할 수 있습니다.

5. 추상화(Abstraction)

추상화는 객체의 복잡한 구현을 숨기고, 필요한 인터페이스만을 외부에 제공하는 원칙입니다. C#에서는 추상 클래스인터페이스를 통해 추상화를 구현할 수 있습니다.

추상 클래스(Abstract Class)

추상 클래스는 상속받는 클래스가 반드시 구현해야 할 메서드를 정의할 수 있습니다. 추상 메서드는 본문이 없고, 자식 클래스에서 구현해야 합니다.

abstract class Animal
{
    public abstract void MakeSound(); // 추상 메서드
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("강아지가 짖습니다.");
    }
}

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("고양이가 야옹합니다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Animal myDog = new Dog();
        myDog.MakeSound(); // 출력: 강아지가 짖습니다.

        Animal myCat = new Cat();
        myCat.MakeSound(); // 출력: 고양이가 야옹합니다.
    }
}

인터페이스(Interface)

인터페이스는 클래스가 구현해야 할 메서드의 목록을 정의합니다. 클래스는 여러 개의 인터페이스를 구현할 수 있으며, 인터페이스를 통해 객체의 행동을 추상화할 수 있습니다.

interface IFlyable
{
    void Fly(); // 메서드 시그니처만 정의
}

class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("새가 날고 있습니다.");
    }
}

class Airplane : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("비행기가 날고 있습니다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        IFlyable bird = new Bird();
        bird.Fly(); // 출력: 새가 날고 있습니다.

        IFlyable airplane = new Airplane();
        airplane.Fly(); // 출력: 비행기가 날고 있습니다.
    }
}

6. 실습 예제: 상속과 다형성

C#에서 상속과 다형성을 활용하여 간단한 동물원을 구현해 보겠습니다.

using System;

abstract class Animal
{
    public string Name { get; set; }

    public abstract void Speak();
}

class Lion : Animal
{
    public override void Speak()
    {
        Console.WriteLine($"{Name}이(가) 포효합니다!");
    }
}

class Parrot : Animal
{
    public override void Speak()
    {
        Console.WriteLine($"{Name}이(가) 말을 합니다!");
    }
}

class Zoo
{
    public void ShowAnimal(Animal animal)
    {
        animal.Speak();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Lion lion = new Lion { Name = "사자" };
        Parrot parrot = new Parrot { Name = "앵무새" };

        Zoo zoo = new Zoo();
        zoo.ShowAnimal(lion); // 출력: 사자이(가) 포효합니다!
        zoo.ShowAnimal(parrot); // 출력: 앵무새이(가) 말을 합니다!
    }
}

7. 요약

  • 상속은 부모 클래스의 기능을 자식 클래스에서 재사용하고 확장할 수 있는 기능입니다.
  • 다형성을 통해 같은 이름의 메서드를 각 클래스에서 다르게 동작하게 할 수 있습니다.
  • 캡슐화는 객체의 데이터를 보호하고, 외부에서 접근을 제어하는 기술입니다.
  • 추상화는 복잡한 구현을 감추고 필요한 부분만을 외부에 노출합니다.

다음 편에서는 예외 처리와 디버깅에 대해 학습하여 프로그램의 안정성을 높이는 방법을 알아보겠습니다.

728x90