데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      (go-08) Go Type - Method, Interface

      seungkyua 22.12.27
      641 15 0

      Golang 올해 마지막 연재글입니다. 이번에는 Method, Interface에 대한 개념을 정리해봅니다.


      Method

      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 KST

      Timer 에 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
      20

      method 는 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 3


      const 와 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 working


      Interface

      interface 를 잘 활용하면 다형성과 같은 개념을 사용할 수 있다.

      1. interface 선언 (Logic interface)

      2. struct 선언 - 필드로 interface 를 가짐 (Client struct)

      3. 리시버의 메소드 구현 (Client struct 의 interface 호출)

      4. 커스텀 struct 선언 (MyLogic struct)

      5. 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
      false


      interface 는 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 10


      interface 는 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: string

      interface{} 를 받아서 타입을 체크하는 로직을 넣을 수 있다.


      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


      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      seungkyua 님의 최신 블로그

      더보기
      동영상 기고하기