CodeGym /행동 /C# SELF /추상화와 복잡한 시스템 단순화

추상화와 복잡한 시스템 단순화

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

1. 복잡한 시스템에서 단순함의 열쇠

복잡한 소프트웨어 시스템은 마치 큰 도시 같아: 수천 명의 주민, 도로, 규칙, 연결고리. 만약 모든 객체를 직접 관리하려고 하면 금방 헷갈리고 도시(그리고 너의!) 삶이 악몽이 될 거야. 추상화는 도시의 마스터 플랜 같은 거야: 네가 직접 모든 택시를 따라다니진 않지만, 교통수단엔 노선, 운전자, 승객이 있다는 건 확실히 알고 있지.

왜 복잡한 시스템에 추상화가 필요한가

추상화 없는 코드는 "면발" 같아 — 디테일이 한데 엉켜 있고, 모든 게 직접적으로 연결돼 있어서 작은 것도 전체를 망가뜨릴 수 있어. 추상화는 구현 디테일과 공통 인터페이스를 분리해서, 매번 세부사항에 빠지지 않고 "위에서" 시스템을 다룰 수 있게 해줘.

은행 시스템을 상상해봐: 카드에서 카드로 돈을 보내고 싶을 때, 은행 서버가 어떻게 통신하는지나 데이터베이스 구조가 어떻게 생겼는지 알 필요 없어. 너한텐 단순한 인터페이스가 있어: "한 계좌에서 다른 계좌로 금액 이체" — 이게 바로 추상화 레벨이야.

가끔 추상화는 그냥 미래의 너를 위한 배려야: 명확한 추상화를 통해 코드를 유지보수, 확장, 설명하기가 훨씬 쉬워져.

2. 일상적인 예시

커피머신으로 보는 생활 속 예시

커피를 내릴 때, 펌프나 온도 센서, 밸브 구조까지 알 필요 없어. 버튼만 누르면 결과가 나오지. 프로그래밍에서 추상화도 똑같아: 복잡한 구현을 단순한 인터페이스 뒤에 숨겨줘.


// 추상화 인터페이스 "커피머신"
public abstract class CoffeeMachine
{
    public abstract void MakeEspresso();
    public abstract void MakeCappuccino();
}

// 커피머신 구체 모델 구현
public class FancyCoffeeMachine : CoffeeMachine
{
    public override void MakeEspresso()
    {
        // 에스프레소 만드는 구체적인 단계
        Console.WriteLine("갈고, 누르고, 에스프레소 추출 중...");
    }
    
    public override void MakeCappuccino()
    {
        // 카푸치노 만드는 구체적인 단계
        Console.WriteLine("갈고, 추출하고, 우유 거품 내서 카푸치노 준비...");
    }
}

너는 CoffeeMachine 추상화 객체로만 상호작용하고, 커피 내리는 디테일은 내부에 남아있어.

3. 실제 프로젝트에서의 추상화

인터넷 쇼핑몰 개발

인터넷 쇼핑몰을 만든다고 해보자. 상품, 장바구니, 사용자, 주문, 결제, 배송 등 다양한 엔티티가 있어. 추상화 없이 하면 코드가 금방 거대하고 확장 불가능한 덩어리가 돼버려.

쇼핑몰에서의 추상화 예시

  1. 상품 (Product):
    책이든, 냉장고든, 전자 바우처든, 모든 상품은 Product라는 공통 클래스 추상화로 표현할 수 있어.
  2. 결제 (Payment):
    구매자는 카드, PayPal, 암호화폐 등으로 결제할 수 있는데, 디테일은 중요하지 않아. "결제 처리"라는 추상화가 있지.
  3. 배송 (Delivery):
    택배, 우편, 직접수령 등 다양한 방식이 있지만, 모두 "배송" 추상 클래스를 구현하고, 시스템은 이 공통 타입으로 동작해.

코드 예시: 배송 방식 추상화


public abstract class Delivery
{
    public string Address { get; set; }

    public abstract void Deliver();
}

public class CourierDelivery : Delivery
{
    public override void Deliver()
    {
        Console.WriteLine($"택배로 배송: {Address}");
    }
}

public class PickupDelivery : Delivery
{
    public override void Deliver()
    {
        Console.WriteLine($"수령지에서 직접 수령: {Address}");
    }
}

주문이 완료되면, 창고는 상품이 어떻게 배송되는지 신경 안 써 — 그냥 order.Delivery.Deliver()만 호출하지, 내부 구현은 몰라도 돼. 이게 바로 유연함이야: 새로운 배송 방식을 추가해도 나머지 코드는 그대로야.

4. 예시로 보는 추상화

우리 커리큘럼은 작은 앱을 중심으로 짜여 있어 — 예를 들어 "농장 동물 관리". 지난 시간엔 Animal, Cow, Dog, Cat 등 클래스 계층을 만들었지. 이제 추상화를 적용해서 농장 작업을 관리해보자.

동물 명령 단순화로서의 추상화

이제 "농장 프로세스"를 구현해야 한다고 해보자: 매일 모든 동물이 먹이를 받고, 각자 행동(예: 우유를 주거나 짖기)을 해. 동물 종류마다 따로 프로시저를 만들고 싶진 않지.


public abstract class Animal
{
    public string Name { get; set; }
    public abstract void Feed();
    public abstract void MakeSound();
}

public class Cow : Animal
{
    public override void Feed()
    {
        Console.WriteLine($"{Name}: 풀을 먹는다.");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name}: 음메!");
    }
}

public class Dog : Animal
{
    public override void Feed()
    {
        Console.WriteLine($"{Name}: 뼈를 맛있게 먹는다.");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name}: 멍멍!");
    }
}

왜 이게 편할까? 이제 동물 종류에 상관없이 똑같이 처리할 수 있어:


List<Animal> farmAnimals = new List<Animal>
{
    new Cow { Name = "부련카" },
    new Dog { Name = "샤릭" }
};

foreach (Animal animal in farmAnimals)
{
    animal.Feed();
    animal.MakeSound();
}

거위, 양, 라마를 추가해도 반복문은 그대로야!

5. 추상화가 결합도를 줄여주는 방법

결합도 (coupling)는 프로그램의 서로 다른 부분이 얼마나 강하게 연결되어 있는지야. 결합도가 높으면 학교 급식실 같아: 주전자가 고장나면, 심지어 파스타엔 필요 없어도 아무도 차를 못 끓여. 추상화는 결합도를 낮춰줘: 네가 인터페이스나 추상 클래스로만 작업하고, 실제 구현이 뭔진 몰라도 돼.

시각적 다이어그램: 추상화 레벨과 의존성


+--------------------+     +------------------------+
|   상위 레벨 코드   | --> |      추상화            |
|      (예: Order)   |     |  (추상 클래스 /        |
|                    |     |      인터페이스)        |
+--------------------+     +------------------------+
                                     /      \
                                    /        \
                   +------------------+    +-----------------+
                   | 구현 1           |    | 구현 2          |
                   | (CourierDelivery)|    | (PickupDelivery)|
                   +------------------+    +-----------------+
상위 레벨 코드는 구체 구현이 아니라 추상화만 다뤄

추상화의 또 다른 장점들

  • 유연성: 새로운 객체 타입을 빠르게 추가하거나, 동작을 바꿔도 나머지 코드는 건드릴 필요 없어.
  • 확장성: 시스템이 쉽게 확장돼. 쇼핑몰 예시에서 새로운 배송 방식을 추가하려면 그냥 자식 클래스만 만들면 돼.
  • 테스트 용이성: 추상화 덕분에 단위 테스트 작성이 쉬워져 (구현을 "대체"할 수 있음).
  • "개방/폐쇄 원칙" (Open/Closed Principle, OCP): 코드는 확장엔 열려 있고(새 구현 추가 가능), 수정엔 닫혀 있어(기존 코드는 안 건드려도 됨).

6. 추상화 없는 코드의 문제

추상화 없으면 코드는 금방 타입 체크, 중복, 조건문 스파게티가 돼버려. 예를 들어, 이렇게 하면 안 돼:


// 안티패턴: 추상화 없이 고통만 남음
if (animal is Cow)
{
    ((Cow)animal).Feed();
}
else if (animal is Dog)
{
    ((Dog)animal).Feed();
}
else if (animal is Cat)
{
    ((Cat)animal).Feed();
}
// 계속 반복...

이런 코드는 유지보수하기 힘들어: 양을 추가하면 조건문도 다 바꿔야 하고, 동물이 춤추는 기능이 생기면 프로젝트 전체에 복붙해야 해.

7. 추상화 설계에서 흔한 실수

실수 1: 상속을 너무 남발함.
초보자들은 복잡한 클래스 계층을 만들고 싶어하는데, 사실 컴포지션이 더 쉽고 안전한 경우가 많아. "가지고 있다"는 게 항상 상속이어야 하는 건 아니야. 때로는 객체를 내부에 넣는 게 상속보다 나아.

실수 2: 추상 클래스가 아무것도 추상화하지 않음.
가끔 추상 클래스에 상속받는 쪽에서 전혀 안 쓰는 속성이나 메서드를 넣어버려. 이러면 단일 책임 원칙이 깨지고, 코드 관리가 어려워져. 추상 클래스는 핵심 동작만 정의해야 해, 아무거나 다 넣는 창고가 아니야.

실수 3: 코드 중복이 있는데 추상화가 없음.
여러 클래스에 반복되는 로직이 있다면, 공통 추상 부모를 만들 때가 된 거야. 이런 실수는 몰라서가 아니라, 급하게 하거나 설계가 부족해서 자주 나와.

2
과제
C# SELF, 레벨 22, 레슨 4
잠금
동물로 추상화 예제
동물로 추상화 예제
2
과제
C# SELF, 레벨 22, 레슨 4
잠금
추상화와 상품 배송
추상화와 상품 배송
1
설문조사/퀴즈
추상 클래스, 레벨 22, 레슨 4
사용 불가능
추상 클래스
추상화랑 추상 클래스
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION