1. 들어가기
추상화를 새의 시야에서 한 번 보자: 큰 사무실을 상상해봐, 여러 직원이 각자 다른 일을 하고 있어. 다들 공통 직책이 있을 수 있지 — "직원", 근데 각자 맡은 일은 달라. 근데 네가 사장이라면, 모든 직원이 "일하기"만 하면 되는 거야. 그게 어떻게 되는지는 신경 안 써: 프로그래머는 코드 쓰고, 회계는 보고서 찍고. 이런 "모두를 위한 공통 계약은 추상화로, 세부는 전문가에게"라는 아이디어가 제대로 된 클래스 계층 구조의 핵심이야.
코드로 보면?
모든 건 기본 추상 클래스에서 시작해. 이 클래스는 모든 자식이 꼭 해야 하는 걸 정의하지.
public abstract class Employee
{
public string Name { get; }
public Employee(string name)
{
Name = name;
}
// 추상 메서드 — 모든 직원이 따라야 하는 계약
public abstract void DoWork();
}
그 다음은 파생 클래스(자식) — 프로그래머랑 회계:
public class Programmer : Employee
{
public Programmer(string name) : base(name) { }
public override void DoWork()
{
Console.WriteLine($"{Name} 코드 작성 중");
}
}
public class Accountant : Employee
{
public Accountant(string name) : base(name) { }
public override void DoWork()
{
Console.WriteLine($"{Name} 돈 계산 중");
}
}
이제 봐봐: 직원 리스트를 만들 수 있어 — 회계든 프로그래머든, 뭐든 추가 가능(추상화는 이런 거 완전 환영이야!).
Employee[] office = new Employee[]
{
new Programmer("바샤"),
new Accountant("타냐")
};
foreach (Employee emp in office)
{
emp.DoWork(); // 각자 자기 일 함
}
바샤 코드 작성 중
타냐 돈 계산 중
봐봐, 얼마나 깔끔하고 범용적이고, 무엇보다 확장성 좋아졌는지. 새 클래스, 예를 들어 Manager 추가해도 기존 코드는 손댈 필요 없어!
2. 추상화 기반 계층 구조 만들기
거의 모든 OOP 교재에 동물 예제가 빠지지 않으니까, 우리도 전통을 따라가자.
추상 클래스 — "큰 독재자" 역할
public abstract class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
// 모든 동물은 소리 낼 수 있어야 함
public abstract void MakeSound();
// 근데 다 날 수 있는 건 아니니까:
public virtual void Fly()
{
Console.WriteLine("나는 못 날아.");
}
}
자식 클래스: 소리 내기 구현, 다른 메서드도 오버라이드 가능
public class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name}: 야옹!");
}
}
public class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name}: 멍멍!");
}
}
public class Eagle : Animal
{
public Eagle(string name) : base(name) { }
public override void MakeSound()
{
Console.WriteLine($"{Name}: 끼익!");
}
public override void Fly()
{
Console.WriteLine($"{Name} 하늘을 날아!");
}
}
이제 여러 동물로 컬렉션 만들어보자:
Animal[] zoo = new Animal[]
{
new Cat("바르식"),
new Dog("샤릭"),
new Eagle("오렐")
};
foreach (Animal animal in zoo)
{
animal.MakeSound();
animal.Fly();
}
바르식: 야옹!
나는 못 날아.
샤릭: 멍멍!
나는 못 날아.
오렐: 끼익!
오렐 하늘을 날아!
이제 어떤 자식 클래스든 쉽게 쓸 수 있지: 컬렉션에 뭐가 들어있는지 신경 안 써도 돼. 추상화가 "계약"을 챙기고, 구현 세부는 각 클래스가 알아서 해.
3. 여러 단계의 추상화
추상화는 단순히 기본 클래스랑 그 자식만 나누는 게 아니야. 복잡한 시스템에선 여러 단계로 추상화가 들어가, 한 추상 클래스가 또 다른 추상 클래스를 기반으로 만들어지기도 해. 이건 마치 레이어 케이크(아니면 슈렉이 말한 양파) 같아: 각 레이어가 불필요한 디테일을 숨기고 필요한 것만 남겨.
예시: 탈것 계층 구조
. Vehicle (abstract)
/ | \
Car Airplane Boat
ElectricCar JetAirplane SailBoat
여기서 Vehicle이 공통 원칙(예: Move() 메서드)을 정하지만, 자동차나 비행기가 어떻게 움직이는지는 몰라. 그건 자식 클래스가 정하지. 더 구체적인 클래스, 예를 들어 ElectricCar나 JetAirplane은 자기만의 디테일을 추가해서 기능을 확장할 수 있어.
계층 구조 블록도:
. +------------------+
| Vehicle | <--- 추상 클래스
+------------------+
/ \
+-------+ +-------+
| Car | | Boat | <--- 중간 추상 또는 구체 클래스
+-------+ +-------+
코드: 기본 추상 클래스
public abstract class Vehicle
{
public string Model { get; }
public Vehicle(string model)
{
Model = model;
}
// 추상 메서드
public abstract void Move();
}
중간 추상 클래스
가끔 중간 단계에도 추상화가 필요해!
public abstract class Car : Vehicle
{
public Car(string model) : base(model) { }
public override void Move()
{
Console.WriteLine($"{Model} 도로를 달려.");
}
// 추상 메서드 — 모든 차가 전기차는 아니니까:
public abstract void RefuelOrCharge();
}
구체 구현
public class ElectricCar : Car
{
public ElectricCar(string model) : base(model) { }
public override void RefuelOrCharge()
{
Console.WriteLine($"{Model} 전기로 충전 중.");
}
}
public class GasolineCar : Car
{
public GasolineCar(string model) : base(model) { }
public override void RefuelOrCharge()
{
Console.WriteLine($"{Model} 휘발유 주유 중.");
}
}
결과: 여러 단계의 추상화 덕분에 새 클래스나 기능 추가가 쉬워져.
시각화: 클래스 계층 예시
| 클래스 | 타입 | 추상 | 부모 | 특별 동작 |
|---|---|---|---|---|
| Vehicle | 기본 | 예 | - | 추상 메서드 Move() |
| Car | 중간 | 예 | Vehicle | 추상 RefuelOrCharge() |
| ElectricCar | 최종 | 아니오 | Car | RefuelOrCharge() 구현 |
| GasolineCar | 최종 | 아니오 | Car | RefuelOrCharge() 구현 |
| Boat | 최종 | 아니오 | Vehicle | Move() 구현 |
4. 확장 가능한 앱을 위한 추상화 활용
실전에서 좋은 점:
- 코드 유연성 & 확장성: 새 클래스(예: 동물, 탈것, 직원 등) 추가해도 기존 코드 안 건드려도 돼. 루프/메서드는 새 객체도 자동으로 처리 — 추상화에서 정의한 메서드만 구현하면 돼.
- 코드 중복 최소화: 공통 속성/메서드(예: 이름, 기본 로직)는 추상 클래스에 한 번만 정의, 자식이 자동 상속.
- 대형 프레임워크/시스템에서 흔히 씀: 예를 들어 ASP.NET MVC엔 추상 기본 컨트롤러(ControllerBase)가 있고, WinForms엔 모든 UI 요소의 추상 클래스 Control이 있어. 이 덕분에 프레임워크 확장해도 기존 코드 안정성 유지 가능.
어디서 쓸까?
- 모든 대형 시스템: 은행 앱(직원, 작업, 트랜잭션), 게임(게임 엔티티, 캐릭터), 비즈니스 앱(카탈로그, 상품, 사용자), 계층적/구조적 데이터 등.
- 기술 면접: 추상화 기반 클래스 설계/계층 설명은 면접 단골 질문.
- 프로그램 아키텍처: 추상화로 아키텍처 "뼈대"를 미리 만들 수 있어 — 팀 개발, TDD(테스트 주도 개발), 유지보수에 딱임.
5. 계층 구조 만들 때 흔한 실수와 함정
실수 1: 추상 메서드 구현 빠뜨림.
기본 클래스의 모든 추상 멤버를 자식에서 구현 안 하면 컴파일러가 인스턴스 생성 못 하게 막아. 이건 꼭 지켜야 하는 규칙 — 구체 클래스를 만들고 싶으면 다 구현해야 해.
실수 2: 추상 클래스 다중 상속 시도.
C#에선 여러 클래스를 동시에 상속 못 해, 다 추상 클래스여도 마찬가지. 이건 언어 제한이야. 여러 소스에서 동작을 "상속"하고 싶으면 인터페이스 써. 인터페이스는 추상 클래스랑 비슷한데, 구현 없이 더 가벼워.
실수 3: 중간 추상 클래스 의미를 모름.
이런 클래스는 공통 로직 묶거나 "덜 정의된" 객체 생성을 막으려고 자주 써. 예를 들어 Car는 추상 클래스로 두면, 그냥 "차"만 만들고 구체적으로 휘발유/디젤/전기차인지 안 정하는 걸 막을 수 있어.
GO TO FULL VERSION