더북(TheBook)

이번에는 handle() 함수에 다른 io.Writer를 전달해 보자.

다음 예제에서는 main 함수에서 HTTP 웹 서버를 동작시키고, handle() 함수의 첫 번째 매개변수 io.Writerhttp.ResponseWriter를 전달한다. 즉, 다음 코드는 URL 패스를 http.ResponseWriter에 전달하는 코드이다.

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        handle(w, r.URL.Path[1:])
})
 
    fmt.Println("start listening on port 4000")
    http.ListenAndServe(":4000", nil)
}

fmt 패키지와 http 패키지는 연결 고리가 없다. fmt.Fprintln()에 전달되는 값에는 Wirte() 메서드가 정의되어 있으면 되고, 그저 이 조건만 충족되면 io.Writer로 사용될 수 있다.

특정 라이브러리나 프레임워크와 호환되는 객체를 만들 때 컴파일 언어는 보통 특정 인터페이스나 클래스를 상속받아 구현한다. 그렇게 생성된 클래스는 특정 라이브러리나 프레임워크와 연결 고리가 생겨버리고, 그로 인해 확장성이 떨어지게 된다. 물론 리플렉션을 통해 객체를 동적으로 생성해서 메서드를 호출할 수도 있지만, 여러 이유로 리플렉션 방식은 꼭 필요한 경우가 아니면 사용을 자제하는 것이 좋다.

Go는 인터페이스의 특징 덕에 모듈 간 연계가 매우 쉽다. 먼저 전체 플로우를 제어하는 미들웨어를 만들고, 인터페이스 기반으로 전체 플로우를 제어하여 라이브러리나 패키지를 담을 수 있는 형태로 미들웨어를 만드는 것은 Go의 일반적인 패턴이다.

인터페이스의 이러한 특징은 동적인 느낌으로 프로그래밍할 수 있게 해준다. 인터페이스에 정의된 메서드는 코드 패턴의 컨벤션(관습) 역할을 하고, 컨벤션을 따라 개발하면 다른 모듈과 호환도 쉽다. 그리고 이러한 컨벤션은 컴파일러가 보장해준다. 즉, 컴파일러의 보장을 받으며 동적 코딩을 할 수 있다.

신간 소식 구독하기
뉴스레터에 가입하시고 이메일로 신간 소식을 받아 보세요.