Interface
- 인터페이스는 어떤 객체가 정해진 행동을 할 수 있다면, 어떤 곳에서 쓰일 수 있다는 object의 행동을 명시한다.
- 인터페이스의 이름은 보통 method의 이름에서 파생된다.
Conversion
- type Sequence []int일때 프린팅에 필요한 메서드인 String 함수를 만든다면 사실 Sprint 함수가 slice를 가지고 하는 일을 반복하는 꼴이 된다. 이때 Sprint 를 실행하기 전에 Sequence 타입을 []int로 변환할 수 있다. 이름만 무시하면 동일하기 때문에 conversion이 가능한 것이고, 새로운 값을 만들어 내지 않고 현재 값에 새로운 타입이 있는 것처럼 일시적으로 행동하게 된다. (int -> floating point로 변환할 때는 새로운 값을 만드는 legal conversion 이다.)
- 이렇게 다른 메소드들의 집합을 사용하기 위해서 타입을 변경하는 것은 Go에서 자연스러운 표현방식이다. 여러개의 타입으로도 변환이 가능하며, 각 타입이 주어진 작업의 일정 부분을 감당하게 되는 것이다.
Type switch
type switch performs several type assertions in series and runs the first case with a matching type.
switch 구문은 인터페이스 변수의 동적 타입을 확인하는 데 사용된다.
type Stringer interface {
String() string
}
var value interface{} //value provided by caller
switch str := value.(type) {
case string:
return str
case Stringer:
return str.String()
}
이때 이 코드는 value가 string이면 인터페이스가 잡고 있는 actual string value를 그리고 Stringer 타입이라면 String method를 실행한 결과를 내보낸다.
Type Assertion
A type assertion provides access to an interface value's underlying concrete value.
만약 하나의 타입만 관심있고, 그냥 그 값만 추출하는 것이 목적이라면, Type switch 보단 Type Assertion을 사용한다.
type 키워드 대신 typeName을 집어넣어서 값을 추출한다. value.(string) 이런 식으로 string 값을 extract하고자 할때 이 value가 string을 가지고 있지 않으면 프로그램은 runtime error를 내고 죽는다. 이때 "comma, ok" idiom을 사용해서 있는지 없는지 안전하게 테스트할 수 있다. comma ok구문에서 Type assertion이 실패할 경우, str, ok := value.(string)에서 str은 string의 제로값인 빈 문자열을 가지게 된다.
** 문서 안에서 순서가 switch문부터 나와서 type switch문이 먼저 나왔지만, 이보다도 하나의 값으로 conversion 하기를 원한다면 type assertion을 통해서 얻을 수 있다는 것이다.
Generality
오직 interface를 implement하기 위해서 만들어진 type일 경우에, 더욱이 interface 가 아닌 method를 export하지 않고 싶을 때, 그 타입 자체는 export할 필요가 없다. 단지 interface만 export하는 것으로 interface바깥에 정의된 다른 behavior들은 의미가 없다는 것을 나타낼 수 있다. 이런 경우에, constructor는 구현 타입 보다는 인터페이스 값을 반환해야한다.
(이 아래 부분 이해 안감.)
Interfaces & Methods
거의 모든것에 method를 가질수 있는 것은 결국 모든 것이 interface가 될 수 있는 조건을 만족시킨다는 것이기도 하다. Handler Interface를 예로 들을 수 있는데, 이 handler 를 구현하는 어떤 객체도 HTTP Request를 처리할 수 있게 된다.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
여기서 ResponseWriter 역시 클라이언트 응답 반환하는데 필요한 method들을 제공하는 interface이다. standard Write 메서드를 포함하고 있어서 io.Writer가 사용되는 곳이면 어디든 사용할 수 있다. Request는 클라이언트로부터 오는 request의 내용을 담은 struct이다. (fmt.Fprint를 사용하면, responseWriter에 응답을 기록할 수 있다. )
- 만약에 내부 상태가 페이지 방문을 알아야한다면? 웹페이지에 채널을 연결하면 된다. channel 을 연결한 형태는 receiver가 channel이고 ServeHTTP 함수 내에서 그 채널에 값을 sending하는 것이다.
- 만약에 server binary를 부를 때 사용된 argument 들을 나타내고 싶다면?
func ArgServer() {
fmt.Println(os.Args)
}
위와 같은 함수를 호출하면 되는데, 이럴 때 사실 이걸 위한 type을 만드는 건 별로 좋은 방법이 아니다. 더 cleaner way가 있다.
pointer와 interface이외에 모든 type에 대해서 method를 정의할 수 있기 때문에, function을 위한 method 도 정의할 수 있다.
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
f(w, req)
}
HandlerFunc은 ServeHTTP method를 가지는 하나의 타입이다. receiver는 함수이고, 메서드 안에서 해당 함수를 호출한다. 이것은 채널을 연결했던 것과 별반 다르지 않다.
// Argument server.
func ArgServer(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(w, os.Args)
}
이렇게 ArgServer와 HandlerFunc는 같은 signature를 가지기 때문에, type conversion을 통해서 ArgServer를 HanlderFunc타입으로 바꿔줄 수 있다. 바꿔주게 되면, HanlderFunc 의 ServeHTTP 메서드를 실행하게 된다.
http.Handle("/args", http.HandlerFunc(ArgServer))
The blank identifier
The blank identifier can be assigned or declared with any value of any type, with the value discarded harmlessly.
- 에러값 무시는 안됨. 나쁜 관행
- Unused Imports & variables 가 있다면 compile이 안됨. 컴파일이 되게 만들고 싶다면, 아래와 같이 할 수 있다.
- var _ = fmt.Printf : import error를 안내게 하기 위해서는 import 구문 다음에 위치하게 만들고, 주석을 달아줘서 나중에 정리해할 때 찾기 쉽도록 한다.
- _ = fd
- import for side effect: 만약에 explicit use는 없지만, side effect를 위해서 import를 해야할 수도 있는데, 이때 blank identifier가 사용될 수 있다. 예를 들어 net/http/pprof패키지에서는 이 패키지의 init function을 실행하면, 디버깅 정보를 제공하는 http handler를 등록하게 된다. 이것이외에도 exported된 API를 가지고 있지만, 대다수의 경우에는 핸들러 등록만 필요하고, 다른 정보는 웹페이지를 통해서 접근한다. 그럴 경우에 import _ "net/http/pprof" 이런식으로 import 될 수 있다. 이런 구문을 가지고 있다면 이 파일에서는 패키지가 이름을 가지지 않기 때문에 사용될 가능성이 없다는 것을 뜻한다.
Interface checks
- 타입이 interface를 구현했음을 명시적으로 declare하지 않아도 된다. 대신에 interface의 method를 구현하기만 하면 된다. 거의 모든 경우에, interface conversion 은 static하며, 그렇기 때문에 compile할 때 체크가 된다.
- 하지만 몇몇 경우에서는 interface check가 run-time에 이루어지기도 한다. encoding/json 패키지에서 예시를 들 수 있다. JSON encoder가 Marshaler interface 를 구현하는 value를 받는다면, 이 encoder는 그 value의 marshalling method를 사용해서 JSON 으로 바꾸는 작업을 하게 된다. encoder는 이 성질을 runtime으로 type assertion을 사용해서 체크한다.
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}
이런 상황이 일어나는 경우는 어떤 type을 구현하는 package내에서 그 타입이 interface를 실제로 만족함을 보장해야 하는 때이다. 예를 들어 json.RawMessage같이 custom JSON representation 을 필요로하는 type인 경우에, json.Marshaler를 무조건 만족해야한다. 하지만 static conversion 구문이 없다면, compile할 때 verify하지 못한다.
만약에 interface를 만족하지 못하게 되면, JSON encoder는 작동은 하겠지만, custom implementation을 실행하진 않는다. 이런 경우, implementation이 맞다는 것을 보장하기 위해서, 전역에서 blank identifier를 사용한다. 이 선언에서 (*RawMessage)가 Marshaler로 conversion 되기 위해서는 *RawMessage가 Marshaler를 implement해야하고, 이 특성은 compile할 때 체크가 된다. 그래서 json.Marshaler interface가 만약 변동이 있을 때, package는 더 이상 compile 되지 않을 것이고 그때 코드를 update해야함을 알 수 있게 된다. 이 구조에서 blank identifier를 사용했기 때문에 단지 타입 검사만을 위해서 존재하는 구문임을 알 수 있다. ( 이 선언은 하나의 interface를 만족하는 모든 타입에 사용하지말고, 코드에 존재하는 static conversion이 없는 경우에만 사용하라. )
var _ json.Marshaler = (*RawMessage)(nil)
궁금증
질문:
interface를 구현하는 것과 달리 어떤 타입에 대해서 method를 만드는 것은 사용하는 용도가 어떻게 다른가?
어떤 다른 여러가지 타입이 하나의 interface를 구현했다고 할 수 있는 것이 차이인가?
어떤 struct가 있고, 그 struct안에 member변수같은 것들이 선언되어 있고, 그리고 그 struct가 특정 interface의 구조를 implement했을 때 그 interface 타입을 통해서 member 함수를 실행하는 것처럼 하며, 여러 type이 같은 interface를 implement해서 같은 메서드를 호출할 수 있는 것?
질문
만약에 servehttp 메소드의 receiver가 포인터가 아니라면 value로 값을 가져오는 것인가?
(real server에서는 concurrent한 값에 대한 access를 위해서 sync 나 atomic 패키지를 참고해서 고쳐야한다.)
'Backend with Golang' 카테고리의 다른 글
Rate Limiting (1) | 2023.09.06 |
---|---|
Effective Go - Part 4: Embedding & Concurrency (0) | 2023.08.16 |
Effective Go - Part 2 (0) | 2023.08.10 |
Effective Go - Part 1 (1) | 2023.08.09 |
[Network] IP Class, Subnet Mask, CIDR (0) | 2023.08.03 |