CodeGym /행동 /C# SELF /계층 구조 만들 때 추상화

계층 구조 만들 때 추상화

C# SELF
레벨 22 , 레슨 3
사용 가능

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() 메서드)을 정하지만, 자동차나 비행기가 어떻게 움직이는지는 몰라. 그건 자식 클래스가 정하지. 더 구체적인 클래스, 예를 들어 ElectricCarJetAirplane은 자기만의 디테일을 추가해서 기능을 확장할 수 있어.

계층 구조 블록도:


.       +------------------+
        |  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. 확장 가능한 앱을 위한 추상화 활용

실전에서 좋은 점:

  1. 코드 유연성 & 확장성: 새 클래스(예: 동물, 탈것, 직원 등) 추가해도 기존 코드 안 건드려도 돼. 루프/메서드는 새 객체도 자동으로 처리 — 추상화에서 정의한 메서드만 구현하면 돼.
  2. 코드 중복 최소화: 공통 속성/메서드(예: 이름, 기본 로직)는 추상 클래스에 한 번만 정의, 자식이 자동 상속.
  3. 대형 프레임워크/시스템에서 흔히 씀: 예를 들어 ASP.NET MVC엔 추상 기본 컨트롤러(ControllerBase)가 있고, WinForms엔 모든 UI 요소의 추상 클래스 Control이 있어. 이 덕분에 프레임워크 확장해도 기존 코드 안정성 유지 가능.

어디서 쓸까?

  • 모든 대형 시스템: 은행 앱(직원, 작업, 트랜잭션), 게임(게임 엔티티, 캐릭터), 비즈니스 앱(카탈로그, 상품, 사용자), 계층적/구조적 데이터 등.
  • 기술 면접: 추상화 기반 클래스 설계/계층 설명은 면접 단골 질문.
  • 프로그램 아키텍처: 추상화로 아키텍처 "뼈대"를 미리 만들 수 있어 — 팀 개발, TDD(테스트 주도 개발), 유지보수에 딱임.

5. 계층 구조 만들 때 흔한 실수와 함정

실수 1: 추상 메서드 구현 빠뜨림.
기본 클래스의 모든 추상 멤버를 자식에서 구현 안 하면 컴파일러가 인스턴스 생성 못 하게 막아. 이건 꼭 지켜야 하는 규칙 — 구체 클래스를 만들고 싶으면 다 구현해야 해.

실수 2: 추상 클래스 다중 상속 시도.
C#에선 여러 클래스를 동시에 상속 못 해, 다 추상 클래스여도 마찬가지. 이건 언어 제한이야. 여러 소스에서 동작을 "상속"하고 싶으면 인터페이스 써. 인터페이스는 추상 클래스랑 비슷한데, 구현 없이 더 가벼워.

실수 3: 중간 추상 클래스 의미를 모름.
이런 클래스는 공통 로직 묶거나 "덜 정의된" 객체 생성을 막으려고 자주 써. 예를 들어 Car는 추상 클래스로 두면, 그냥 "차"만 만들고 구체적으로 휘발유/디젤/전기차인지 안 정하는 걸 막을 수 있어.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION