Menu
Coddy logo textTech

의존성 주입

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 45번째.

의존성 주입은 구조체가 의존성을 내부에서 생성하는 대신 외부에서 받는 기법입니다. Go에서는 인터페이스를 사용하여 이 패턴을 자연스럽고 강력하게 구현할 수 있습니다.

구조체 내부에 특정 구현을 하드코딩하는 대신 인터페이스를 받습니다. 이렇게 하면 구조체의 코드를 변경하지 않고도 구현을 교체할 수 있습니다:

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

이제 구체 타입이 아니라 인터페이스에 의존하는 구조체를 만드세요:

type OrderService struct {
    notifier Notifier  // 인터페이스를 통해 주입된 의존성
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

OrderService는 이메일을 사용하는지 SMS를 사용하는지 알 필요도 없고 신경 쓰지도 않습니다. 서비스를 생성할 때 dependency를 주입합니다:

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email: 주문 완료: Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: 주문 완료: Phone
}

이 접근 방식은 코드를 더 유연하고 테스트하기 쉽게 만들어 줍니다. 테스트 중에는 실제로 메시지를 보내지 않는 모의 notifier를 주입할 수 있습니다. 프로덕션에서는 실제 구현을 주입합니다. 두 시나리오 모두에서 구조체는 변경되지 않습니다.

challenge icon

챌린지

쉬움

의존성 주입의 강력한 기능을 보여 주는 logging system을 만들어 봅시다. service가 로그가 실제로 어디로 전달되는지 알거나 신경 쓰지 않아도, 서로 다른 destination에 로그를 쓸 수 있는 service를 만들 것입니다.

코드를 세 개의 파일로 구성합니다:

  • logger.go: Logger interface를 Define하고, Log(message string) string method를 요구하도록 합니다. 그런 다음 두 가지 logger 구현을 만듭니다:
    • ConsoleLogger: Prefix field를 가집니다. Its Log method는 [CONSOLE] [Prefix]: [message]를 반환합니다.
    • FileLogger: Filename field를 가집니다. Its Log method는 [FILE:[Filename]] [message]를 반환합니다.
  • service.go: Logger interface(구체적인 type이 아님)에 의존하는 AppService struct를 만듭니다. Logger를 accepts하고 AppService 포인터를 반환하는 constructor function NewAppService를 포함합니다. AppServiceDoWork(task string) string이라는 method를 추가합니다. 이 method는 injected logger를 사용해 Processing: [task] message를 log하고, logger가 반환하는 값을 그대로 반환합니다.
  • main.go: input에서 configuration을 읽고, 두 logger type을 모두 만든 다음, 각각을 별도의 AppService instance에 inject하고, 제공된 task와 함께 각 service에서 DoWork를 Call합니다. 각 service Call의 결과를 출력합니다.

다음 input이 제공됩니다:

  • Line 1: Console logger prefix
  • Line 2: File logger filename
  • Line 3: 처리할 task

예를 들어 INFO, app.log, user authentication이 주어지면 output은 다음과 같아야 합니다:

[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authentication

AppService는 console에 log하는지 file에 log하는지 알 필요가 없다는 점에 주목하세요. 단순히 injected된 logger에서 Log를 Call합니다. 이러한 flexibility가 의존성 주입의 본질입니다. 동일한 service code가 완전히 다른 logging 구현과 함께 작동합니다.

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// 콘솔 로거 접두사 읽기
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// 파일 로거 파일 이름 읽기
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// 처리할 작업 읽기
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: 접두사로 ConsoleLogger 생성
	
	// TODO: 파일 이름으로 FileLogger 생성
	
	// TODO: ConsoleLogger가 주입된 AppService 생성
	
	// TODO: FileLogger가 주입된 또 다른 AppService 생성
	
	// TODO: 각 서비스에서 task로 DoWork를 호출하고 결과 출력
	fmt.Println("result1")
	fmt.Println("result2")
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 Go 컴파일러