본문 바로가기
  • 때아때 v1.5.2
공부

[TIL][객체 지향 프로그래밍 입문] 250715 객체, 추상화, 조립, 기능과 책임분리, 의존과 DI - DIP

by 때려쳐아니때려치지마 2025. 7. 15.

 

https://inf.run/5TpsG

 

기능과 책임 분리

  • 기능을 누가 제공할것인지
    • 기능을 분리하고
    • 각 객체에게 어떤 기능을할지 배분하는것

기능을 분리하는법

  • 패턴 적용
    • 전형적인 역할 분리 (AOP, GoF…)
  • 계산 기능 분리
    • 계산이 필요한 코드는 계산식만 따로 떼서 분리하기
  • 외부 연동 분리
    • rest통신같은거 url만 매개로 받아서 돌려쓸수있게 기능분리
  • 조건별 분기는 추상화
    • 연속적인 ifelse는 추상화하는걸 고민해보기→ 공통기능을 추상화해서 상속으로

⇒ 기능을 잘 분리하면 테스트에 용이!

 

의존과 DI

의존

  • 기능 구현을 위해 다른 구성 요소를 사용하는것
  • 의존은 변경이 전파될 가능성을 의미

의존 대상이 많을때

  1. 기능이 많은 경우, 기능별로 분리 고려
    1. 기능별로 클래스로 분리를 고려한다 (한 클래스에 여러 함수로 기능을 때려넣지말고 각 클래스로 분리)
  2. 묶어보기
    1. 여러 의존 대상을 단일 기능으로 묶어볼수 있는지 검토 (묶어서 추상화)

의존 대상 객체를 직접 생성하면?

→ 생성 클래스가 바뀌면 의존하는 코드도 바뀜

의존 대상 객체를 직접 생성하지 않는 방법

  • 팩토리, 빌더
  • 의존 주입 (dependency injection)
  • 서비스 로케이터 (service locator)

DI (Dependency Injection)

의존 대상을 직접 생성하지 않고 외부에서 의존 객체를 생성자나 메서드를 이용해서 주입하는 방식

예시) email로 메세지를 보내는 기능에서 sms로 확장되는 경우

using System;

// ============================================
// 1. DI 사용 전 (강결합)
// ============================================

public class EmailService
{
    public void SendEmail(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

public class UserService_Without_DI
{
    private EmailService emailService;
    
    public UserService_Without_DI()
    {
        // EmailService에 직접 의존 (강결합)
        emailService = new EmailService();
    }
    
    public void RegisterUser(string username)
    {
        Console.WriteLine($"User {username} registered");
        emailService.SendEmail($"Welcome {username}!");
    }
}
// ============================================
// 사용 예시
// ============================================

public class Program
{
    public static void Main()
    {
        Console.WriteLine("=== DI 사용 전 ===");
        
        // 1. DI 없이 사용 (EmailService에 고정됨)
        var userService1 = new UserService_Without_DI();
        userService1.RegisterUser("Alice");
        
        // SMS로 바꾸려면? → UserService_Without_DI 코드를 수정해야 함!
    }
}
// ============================================
// 2. DI 사용 후 (느슨한 결합)
// ============================================

// 인터페이스로 추상화
public interface INotificationService
{
    void SendNotification(string message);
}

public class EmailService_DI : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

public class SMSService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"SMS sent: {message}");
    }
}

public class UserService_With_DI
{
    private readonly INotificationService notificationService;
    
    // 생성자를 통한 의존성 주입
    public UserService_With_DI(INotificationService notificationService)
    {
        this.notificationService = notificationService;
    }
    
    public void RegisterUser(string username)
    {
        Console.WriteLine($"User {username} registered");
        notificationService.SendNotification($"Welcome {username}!");
    }
}
// ============================================
// 사용 예시
// ============================================

public class Program
{
    public static void Main()
    {
        // 2. DI로 사용 (런타임에 구현체 선택 가능)
        var emailService = new EmailService_DI();
        var smsService = new SMSService();
        
        var userService2 = new UserService_With_DI(emailService);
        userService2.RegisterUser("Bob");
        
        var userService3 = new UserService_With_DI(smsService);
        userService3.RegisterUser("Charlie");
        
        Console.WriteLine("\\n=== 테스트 용이성 ===");
        
        // 3. 테스트용 Mock 객체 사용
        var mockService = new MockNotificationService();
        var userService4 = new UserService_With_DI(mockService);
        userService4.RegisterUser("TestUser");
    }
}

// 테스트용 Mock 클래스
public class MockNotificationService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Mock notification: {message}");
    }
}

장점

  1. 상위 타입을 사용할 경우 의존대상이 바뀌면 조립기 설정만 변경하면됨
  2. 의존하는 객체 없이 대역 객체를 사용해서 테스트 가능

DIP (Dependancy Inversion Principle)

: 의존 역전의 원칙

  • 고수준 모듈은 저수준 모듈의 구현에 의존하면 안됨
  • 저수준 모듈이 고수준 모듈에서 정의한 추상타입에 의존해야함

고수준 모듈, 저수준 모듈

고수준 : 기능의 정책, 기획에 가까움

저수준 : 실제 (고수준)기능이 실행될 하위 수준의 기능 구현

  • 고수준 입장에서 저수준 모듈을 추상화 해야함 = 종속의 방향

댓글