개발노트

4. [ASP .NET Core] 의존성 역전 원칙 (DIP) 본문

서버 개발/ASP .NET Core

4. [ASP .NET Core] 의존성 역전 원칙 (DIP)

mroh1226 2024. 11. 17. 02:19
반응형

DIP (Dependency Inversion Principle)란?

DIP(의존성 역전 원칙)는 SOLID 원칙 중 하나로, 상위 모듈이 하위 모듈에 의존하지 않도록 설계하는 것을 말합니다.
즉, 구체적인 클래스 대신 추상화(인터페이스나 추상 클래스)에 의존해야 합니다.


DIP 적용 전

public class Dog
{
    public void Eat()
    {
        Console.WriteLine("The dog is eating.");
    }
}

public class Cat
{
    public void Eat()
    {
        Console.WriteLine("The cat is eating.");
    }
}

// AnimalFeeder 클래스가 Dog와 Cat에 의존
public class AnimalFeeder
{
    private Dog dog;
    private Cat cat;

    public AnimalFeeder()
    {
        dog = new Dog();
        cat = new Cat();
    }

    public void FeedDog()
    {
        dog.Eat();
    }

    public void FeedCat()
    {
        cat.Eat();
    }
}

DIP 적용 후

IAnimal.cs (인터페이스 Eat, 추상화)
// 동물의 공통 행동을 정의하는 인터페이스
public interface IAnimal
{
    void Eat();
}

 

Dog, Cat (IAnimal 상속 받음, Eat 구현, 하위 클래스)
// Dog 클래스
public class Dog : IAnimal
{
    public void Eat()
    {
        Console.WriteLine("The dog is eating.");
    }
}

// Cat 클래스
public class Cat : IAnimal
{
    public void Eat()
    {
        Console.WriteLine("The cat is eating.");
    }
}

 

AnimalFeeder.cs (인터페이스인 IAnimal에만 의존, 모듈)
// AnimalFeeder 클래스는 인터페이스(IAnimal)에만 의존
public class AnimalFeeder
{
    private readonly IAnimal _animal;

    public AnimalFeeder(IAnimal animal)
    {
        _animal = animal;
    }

    public void Feed()
    {
        _animal.Eat();
    }
}

 

Program.cs (사용)
class Program
{
    static void Main(string[] args)
    {
        // Dog 객체를 주입하여 동작
        IAnimal dog = new Dog();
        var feeder = new AnimalFeeder(dog);
        feeder.Feed(); // 출력: The dog is eating.

        // Cat 객체를 주입하여 동작
        IAnimal cat = new Cat();
        feeder = new AnimalFeeder(cat);
        feeder.Feed(); // 출력: The cat is eating.
    }
}

DIP의 주요 개념 요약

  1. 상위 모듈(AnimalFeeder)은 하위 모듈(Dog, Cat)에 의존하지 않는다.
  2. 상위와 하위 모듈 모두 추상화(IAnimal)에 의존한다.
  3. 구체적인 구현체(Dog, Cat)는 추상화(IAnimal)를 구현한다.

DIP가 필요한 이유

  • 확장성: 새로운 동물 클래스(Bird, Fish 등)를 추가하더라도 AnimalFeeder를 수정할 필요가 없습니다.
  • 테스트 가능성: 인터페이스를 통해 목(Mock) 객체를 쉽게 주입할 수 있습니다.
  • 유연성: 클래스 간 결합도를 낮추고, 시스템을 재사용 가능하게 만듭니다.
[High-Level Module]
   AnimalFeeder
         |
         ▼
   [Abstraction]
      IAnimal
     /       \
    ▼         ▼
[Low-Level] [Low-Level]
   Dog         Cat

 


DIP와 DI 같이 사용

1. DIP로 상위(AnimalFeeder), 하위(cat,dog) 클래스가 추상화 클래스(IAnimal)에 의존하게 됨

2. DI로 제어 의존권은 컨테이너(container)에게 줌
즉, AnimalFeeder에 IAnimal 주입을 컨테이너가 알아서 해줌(AnimalFeeder에서 IAnimal를 직접 생성 등록안함)

using SimpleInjector;

class Program
{
    static void Main(string[] args)
    {
        // DI 컨테이너 생성
        var container = new Container();

        // IAnimal에 대한 구현체(Dog) 등록
        container.Register<IAnimal, Dog>();

        // AnimalFeeder에 IAnimal 구현체 주입
        container.Register<AnimalFeeder>();

        // AnimalFeeder 인스턴스 가져오기
        var feeder = container.GetInstance<AnimalFeeder>();
        feeder.Feed(); // 출력: The dog is eating.
    }
}

추가 예제

using System;
using System.Collections.Generic;

// 게임 아이템 인터페이스
public interface IGameItem
{
    string Name { get; }
    decimal Price { get; }
    void DisplayInfo();
}

// 재료 아이템 클래스
public class MaterialItem : IGameItem
{
    public string Name { get; }
    public decimal Price { get; }

    public MaterialItem(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    public void DisplayInfo()
    {
        Console.WriteLine($"[재료] {Name}, 가격: {Price}골드");
    }
}

// 장비 아이템 클래스
public class EquipmentItem : IGameItem
{
    public string Name { get; }
    public decimal Price { get; }

    public EquipmentItem(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    public void DisplayInfo()
    {
        Console.WriteLine($"[장비] {Name}, 가격: {Price}골드");
    }
}

// 주문 클래스
public class Order
{
    private readonly IList<IGameItem> _gameItems;

    public Order()
    {
        _gameItems = new List<IGameItem>();
    }

    // 아이템 추가
    public void AddGameItem(IGameItem gameItem)
    {
        _gameItems.Add(gameItem);
        Console.WriteLine($"아이템 '{gameItem.Name}'이(가) 추가되었습니다.");
    }

    // 아이템 제거
    public void RemoveGameItem(IGameItem gameItem)
    {
        if (_gameItems.Contains(gameItem))
        {
            _gameItems.Remove(gameItem);
            Console.WriteLine($"아이템 '{gameItem.Name}'이(가) 제거되었습니다.");
        }
        else
        {
            Console.WriteLine($"'{gameItem.Name}'은(는) 주문 목록에 없습니다.");
        }
    }

    // 주문 처리
    public void ProcessOrder()
    {
        Console.WriteLine("주문 처리 중...");
        foreach (var gameItem in _gameItems)
        {
            gameItem.DisplayInfo();
        }
        Console.WriteLine($"총 아이템 수: {_gameItems.Count}");
    }
}

 

Program.cs
class Program
{
    static void Main(string[] args)
    {
        // 게임 아이템 생성
        IGameItem item1 = new MaterialItem("철광석", 15);
        IGameItem item2 = new EquipmentItem("장검", 120);
        IGameItem item3 = new MaterialItem("목재", 5);
        IGameItem item4 = new EquipmentItem("방패", 80);

        // 주문 생성
        var order = new Order();

        // 아이템 추가
        order.AddGameItem(item1);
        order.AddGameItem(item2);

        // 주문 처리 (현재 2개 아이템)
        order.ProcessOrder();

        Console.WriteLine();

        // 아이템 제거 및 추가
        order.RemoveGameItem(item1);
        order.AddGameItem(item3);
        order.AddGameItem(item4);

        // 주문 처리 (현재 3개 아이템)
        order.ProcessOrder();
    }
}

 

[콘솔 출력]


아이템 '철광석'이(가) 추가되었습니다.
아이템 '장검'이(가) 추가되었습니다.
주문 처리 중...
[재료] 철광석, 가격: 15골드
[장비] 장검, 가격: 120골드
총 아이템 수: 2

아이템 '철광석'이(가) 제거되었습니다.
아이템 '목재'이(가) 추가되었습니다.
아이템 '방패'이(가) 추가되었습니다.
주문 처리 중...
[장비] 장검, 가격: 120골드
[재료] 목재, 가격: 5골드
[장비] 방패, 가격: 80골드
총 아이템 수: 3

 

 

구조적 확장 가능성

  1. 새로운 아이템 유형 추가
    • PotionItem(예: 체력 물약), QuestItem(예: 특정 퀘스트에 필요한 아이템) 등으로 확장 가능.
  2. 아이템 분류 필터링
    • 주문 내 특정 유형의 아이템만 처리하도록 추가 기능 구현 가능.
  3. 아이템 속성 추가
    • EquipmentItem에 내구도나 공격력 속성 추가, MaterialItem에 등급 속성 추가.
  4. DIP 유지
    • 인터페이스를 통해 새로운 아이템을 추가하더라도 Order 클래스는 기존 코드를 변경하지 않음.

이런 구조를 통해 DIP를 준수하며 유연한 설계를 유지할 수 있습니다.

반응형
Comments