23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
Golang 올해 마지막 연재글입니다. 이번에는 Method, Interface에 대한 개념을 정리해봅니다.
method 는 struct 에 종속되어 실행할 수 있는 함수라고 생각하면 된다.
struct 에 종속되었다고 표현하는 것을 receiver 라 하며 아래 func (p Person) String() string 메소드를 보면 쉽게 이해가 된다.
type Person struct {
FirstName string
LastName string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s, %s, %d", p.FirstName, p.LastName, p.Age)
}
func main() {
p := Person{
FirstName: "Seungkyu",
LastName: "Ahn",
Age: 52,
}
fmt.Println(p.String())
}Person 타입의 p.String() 으로 메소드를 호출할 수 있다.
리시버는 포인터 리시버와 밸류 리시버로 나누어 진다.
리시버의 필드 값을 바꾸고 싶다면 포인터 타입을 활용한다(함수에서 포인터 아규먼트를 활용하는 것과 같다).
또한 nil 인스턴스를 활용하고 싶다면 포인터 타입을 쓴다. 리시버의 필드 값을 바꾸고 싶지 않다면 밸류 타입을 쓴다.
func main() {
var t = Timer{scheduled: time.Now()}
fmt.Println(t.String())
t.AddMinute(10)
fmt.Println(t.String())
(&t).AddMinute(10)
fmt.Println(t.String())
}
type Timer struct {
scheduled time.Time
}
func (t *Timer) AddMinute(m int) {
t.scheduled = t.scheduled.Add(time.Minute * time.Duration(m))
}
func (t Timer) String() string {
return fmt.Sprintf("scheduled: %v", t.scheduled)
}
--- output ---
scheduled: 2022-08-29 23:23:23.841625 +0900 KST m=+0.000193377
scheduled: 2022-08-29 23:33:23.841625 +0900 KST
scheduled: 2022-08-29 23:43:23.841625 +0900 KSTTimer 에 scheduled 시간의 값을 변경하기 위해서 포인터 리시버를 사용하여 필드 값을 변경했다.
main 함수에서 Timer 변수를 포인터가 아닌 일반 변수로 선언했음에도 불구하고 t.AddMinute(10) 를 호출한 후에 값이 변경되었음을 알 수 있다.
이는 t.AddMinute(10) 과 (&t).AddMinute(10) 이 동일하기 때문이다.
func main() {
adder := Adder{start: 10}
fmt.Println(adder.Add(10))
func1 := adder.Add // method value
fmt.Println(func1(10))
func2 := Adder.Add // method expression
fmt.Println(func2(adder, 10))
}
type Adder struct {
start int
}
func (a Adder) Add(val int) int {
return a.start + val
}
--- output ---
20
20
20method 는 function 과 비슷하다. method 를 변수에 할당할 수 있으며 이를 method value 라고 한다.
타입 자체 method 로 부터 function 을 생성할 수 있는데 이를 method expression 이라고 하며 첫번째 파라미터로 리시버를 전달한다.
type Animal int
const (
dog Animal = iota
cat
cow
pig
)
fmt.Println(dog, cat, cow, pig)
--- output ---
0 1 2 3const 와 iota 를 활용하면 enum 처럼 사용할 수 있다.
type Logic interface {
Process(s string) string
}
type Client struct {
L Logic
}
func (c Client) Program() {
p := c.L.Process("My Process")
fmt.Println(p)
}
// 나만의 Process 로직 만들기
type MyLogic struct{}
func (ml MyLogic) Process(s string) string {
return fmt.Sprintf("%s is working", s)
}
func main() {
c := Client{
L: MyLogic{},
}
c.Program()
}
--- output ---
My Process is workinginterface 를 잘 활용하면 다형성과 같은 개념을 사용할 수 있다.
interface 선언 (Logic interface)
struct 선언 - 필드로 interface 를 가짐 (Client struct)
리시버의 메소드 구현 (Client struct 의 interface 호출)
커스텀 struct 선언 (MyLogic struct)
MyLogic 리시버의 Logic interface 구현 메소드 선언 - 커스텀 비즈니스 로직 구현
struct 의 필드로 interface 가진다는 의미는 해당 interface 를 구현한 어떠한 struct 도 넣을 수 있다는 의미이다.
var s *string
fmt.Println(s == nil)
var i interface{}
fmt.Println(i == nil)
i = s
fmt.Println(i == nil)
--- output ---
true
true
falseinterface 는 zero value 로 nil 을 갖는다.
type MyInt int
var i interface{}
var mi MyInt = 10
i = mi
fmt.Println(i.(MyInt) + 1)
i2, ok := i.(int)
if !ok {
fmt.Println(fmt.Errorf("unexpected type for %v", i))
return
}
fmt.Println(i2 + 1)
--- output ---
11
unexpected type for 10interface 는 interface.(Type) 으로 형변환 가능하다.
type MyInt int
type MyRead struct{}
func (m MyRead) Read(p []byte) (n int, err error) {
return 0, nil
}
func check(i interface{}) {
switch j := i.(type) {
case nil:
fmt.Printf("j: %T\n", j)
case int:
fmt.Printf("j: %T\n", j)
case MyInt:
fmt.Printf("j: %T\n", j)
case io.Reader:
fmt.Printf("j: %T\n", j)
case string:
fmt.Printf("j: %T\n", j)
case bool, rune:
fmt.Printf("j: %T\n", j)
default:
fmt.Printf("j: %T\n", j)
}
}
func main() {
var a interface{}
check(a)
var b int
check(b)
var c MyInt
check(c)
var d bool
check(d)
var e rune
check(e)
var f MyRead
check(f)
var g string
check(g)
}
--- output ---
j: <nil>
j: int
j: main.MyInt
j: bool
j: int32
j: main.MyRead
j: stringinterface{} 를 받아서 타입을 체크하는 로직을 넣을 수 있다.
Interface 를 활용한 방법을 알아보자. 먼저 DataStore 와 Logger interface 를 만든다.
type DataStore interface {
UserNameForID(userID string) (string, bool)
}
type Logger interface {
Log(message string)
}LoggerAdapter 는 Log 메소드를 구현한 함수하기 때문에 Logger 타입이다.
type LoggerAdapter func(message string)
func (lg LoggerAdapter) Log(message string) {
lg(message)
}
func LogOutput(message string) {
fmt.Println(message)
}type 이 함수이면 다음과 같이 사용할 수 있다.
즉 LogOutput 함수를 LoggerAdapter 함수로 타입을 변환한다. 이렇게 되면 LogOutput 은 결론적으로 Logger 타입이라 할 수 있다.
l := LoggerAdapter(LogOutput)SimpleDataStore 는 UserNameForID 메소드를 구현했으므로 DataStore 타입이다.
type SimpleDataStore struct {
userData map[string]string
}
func (sds SimpleDataStore) UserNameForID(userID string) (string, bool) {
name, ok := sds.userData[userID]
return name, ok
}
func NewSimpleDataStore() SimpleDataStore {
return SimpleDataStore{
userData: map[string]string{
"1": "Fred",
"2": "Mary",
"3": "Pat",
},
}
}SimpleLogic 은 Logger 아 DataStore 인터페이스를 가지는 struct 이다.
또한 BusinessLogic 인터페이스를 구현하였기 때문에 BusinessLogic 타입 이기도 하다.
// business logic
type BusinessLogic interface {
SayHello(userID string) (string, error)
}
type SimpleLogic struct {
l Logger
ds DataStore
}
func (sl SimpleLogic) SayHello(userID string) (string, error) {
sl.l.Log("in SayHello for " + userID)
name, ok := sl.ds.UserNameForID(userID)
if !ok {
return "", errors.New("unknown user")
}
return "Hello, " + name, nil
}
func (sl SimpleLogic) SayGoodbye(userID string) (string, error) {
sl.l.Log("in SayGoodbye for " + userID)
name, ok := sl.ds.UserNameForID(userID)
if !ok {
return "", errors.New("unknown user")
}
return "Goodbye, " + name, nil
}
func NewSimpleLogic(l Logger, ds DataStore) SimpleLogic {
return SimpleLogic{
l: l,
ds: ds,
}
}Controller 는 Logger 아 BusinessLogic 을 가진 struct 이다.
type Controller struct {
l Logger
logic BusinessLogic
}
func (c Controller) Greeting(w http.ResponseWriter, r *http.Request) {
c.l.Log("In SayHello")
userID := r.URL.Query().Get("user_id")
message, err := c.logic.SayHello(userID)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.Write([]byte(message))
}
func NewController(l Logger, bl BusinessLogic) Controller {
return Controller{
l: l,
logic: bl,
}
}func main() {
l := LoggerAdapter(LogOutput)
ds := NewSimpleDataStore()
logic := NewSimpleLogic(l, ds)
c := NewController(l, logic)
http.HandleFunc("/hello", c.Greeting)
http.ListenAndServe(":7777", nil)
}
$ curl -X GET "localhost:7777/hello?user_id=1"
Hello, Fred
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.