C# 5편: 객체 지향 프로그래밍: 상속과 다형성으로 확장성 높은 코드 작성
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. 요약
- 상속은 부모 클래스의 기능을 자식 클래스에서 재사용하고 확장할 수 있는 기능입니다.
- 다형성을 통해 같은 이름의 메서드를 각 클래스에서 다르게 동작하게 할 수 있습니다.
- 캡슐화는 객체의 데이터를 보호하고, 외부에서 접근을 제어하는 기술입니다.
- 추상화는 복잡한 구현을 감추고 필요한 부분만을 외부에 노출합니다.
다음 편에서는 예외 처리와 디버깅에 대해 학습하여 프로그램의 안정성을 높이는 방법을 알아보겠습니다.