0520-Go(5)

망지·2022년 5월 20일
0
post-custom-banner

타입, 메서드, 인터페이스

2.1. 포인터 리시버와 값 리시버

  • 포인터 리시버: 리시버의 타입 앞에 *을 붙인다
var a *int
  • 결정하는 규칙 (p.183)
    • 메서드가 리시버를 수정 => 반드시 포인터 리시버 사용

    • 메서드가 nil 인스턴스를 처리할 필요 => 반드시 포인터 리시버 사용

    • 메서드가 리시버를 수정하지 않음 => 값 리시버 사용 가능

    • 이것은 타입에 선언된 다른 메서드에 따라 결정

    • 같은 타입에 다른 리시버가 포인터 리시버라면 리시버를 수정하지 않는 메서드라도 포인터 리시버 사용 <= 일관성을 위해

위 c 는 포인터 리시버
아래 c 는 값 리시버

사실상 포인터 리시버는 (&C).Increment()이런식으로 사용해야 하는데 그냥 c.으로 해도 Go에서 자동 변환해줌.

포인터 리시버 - 원본이 변경됨
그냥 값 리시버 - 원본이 변경되지 않음.
이것 고려해서 리시버 사용할 것.

=> 위 예제의 경우 포인터리시버를 값 리시버로 변경하면 결과값이 제대로 나오지 않지만, 값 리시버를 포인터리시버로 변경하면 결과값 잘 나옴.
값 리시버 사용한 경우 복사본이든 원본이든 상관없는 내용이기때문.

package main

import (
	"fmt"
	"time"
)

type Counter struct {
	total       int
	lastUpdated time.Time
}

func (c *Counter) Increment() {
	c.total++
	c.lastUpdated = time.Now()
}

func (c Counter) String() string {
	return fmt.Sprintf("total: %d, last updated: %v", c.total, c.lastUpdated)
}

func doUpdateWrong(c Counter) { // 여기 c는 복사본 (함수의 인자가 포인터가 아니기 때문)
	c.Increment() // 마찬가지로 파라미터로 넘겨받음. (복사본) -> wrong. 
	fmt.Println("in doUpdateWrong:", c.String())
}

func doUpdateRight(c *Counter) { //포인터로 받아서 원본이 수정되게 했음
	c.Increment()
	fmt.Println("in doUpdateRight:", c.String())
}

func main() {
	var c Counter
	doUpdateWrong(c)
	fmt.Println("in main:", c.String())
	doUpdateRight(&c) //아까는 직접 메서드를 호출했으니까 &안붙였지만 이번엔 함수의 인자를 넘겨받는거라 &붙여야함.
	fmt.Println("in main:", c.String())
}

2.2. nil 인스턴스를 위한 메서드 작성

  • 포인터 메서드 사용
  • nil 인스턴스 : 슬라이스, 맵, 포인터의 제로 값
    • 위 타입의 제로 값이 nil
package main

import (
	"fmt"
)

type IntTree struct { //이진트리 구조체
	val         int
	left, right *IntTree //재귀적으로 설계됨
}

func (it *IntTree) Insert(val int) *IntTree { //it ; IntTree를 소문자로 받음 . (관례적으로)
	if it == nil {
		return &IntTree{val: val} //왼쪽 이름 오른쪽 파라미터
	}
	if val < it.val {
		it.left = it.left.Insert(val)
	} else if val > it.val {
		it.right = it.right.Insert(val)
	}
	return it
}

func (it *IntTree) Contains(val int) bool {
	switch {
	case it == nil:
		return false
	case val < it.val:
		return it.left.Contains(val)
	case val > it.val:
		return it.right.Contains(val)
	default:
		return true
	}
}

func main() {
	var it *IntTree
	it = it.Insert(5)
	it = it.Insert(3)
	it = it.Insert(10)
	it = it.Insert(2)
	fmt.Println(it.Contains(2))
	fmt.Println(it.Contains(12))
}

2.3. 메서드도 함수이다

  • 메서드를 변수에 할당하거나 함수의 입력으로 반환값으로 사용 가능
    ⇒ 메서드 값(method value)
  • 메서드 (표현)식
    ⇒ 타입 자체로 함수 생성
    • 메서드 표현식을 이용하여 메서드를 호출할 때는 첫 번째 인자로 메서드 리시버를 사용

*타입 출력해보기

2.4 함수와 메서드 비교

2.5. 타입 선언은 상속되지 않는다

  • 다른 타입 기반으로 (사용자 정의) 타입 선언
    • 상속이 아니다.
    • 계층이라는 개념이 없다.
    • 타입들은 서로 동등한 레벨이다.
  • 기본 타입이 내장 타입인 사용자 정의 타입의 경우 해당 기본 타입의 연산자와 함께 사용할 수 있다.
  • 또한 기본 타입과 호환되는 리터럴 및 상수를 대입할 수 있다.

2.6 타입은 실행가능한 문서

  • 사용자 타입을 선언하는 시점
    1) 구조체의 경우 우리가 필요한 시점
    2) 기존 타입을 기반으로 사용자 정의 타입을 선언하는 것은
    • “타입은 문서이다”를 기준으로 삼자
    • 개념을 위한 이름을 타입 이름으로 지정하여 코드를 더 명확하게 하고 기대되는 데이터의 종류를 기술할 목적으로

2.7 열거형을 위한 iota

  • 열거형(enumeration, enum)
    • 카테고리성 데이터, 명목형 데이터를 나타내기 위함
      : 도시 - 서울, 부산, 인천, ...
      이메일 종류 - 미분류, 스팸, 소셜, 비즈니스, ...
    • 기본 값은 정수이지만 그리고 정수를 사용(연산)할 수는 있지만 권장하지 않음
city := []{‘서울’, ‘부산’, ‘인천’, ‘대전’}

myCity := ‘부산’


switch myCity {
case city[0]:
   “서울 출신이시네요”

Go에서는 아래와 같이 가능.

city := []{‘서울’, ‘부산’, ‘인천’, ‘대전’}

myCity := “부산”


switch myCity {
case 서울:
   “서울 출신이시네요”

카테고리는 순서가 큰 상관 X.

  • Go에서 열거형 만들기
    1) 모든 유효한 값을 나타내는 정수 기반의 타입 정의
    2) 값의 집합을 만들기 위해 const 블록 사용
    3) const 블록에서 1)에서 선언한 타입의 첫 번째 상수에 iota 지정

iota 사용하면 아래와 같음. (같은 말 여러번 반복하지 않아서 가독성 좋고 편함)


값출력 위와 같이 되므로 MailCategory 직접 정수 할당한거랑 마찬가지.
그런데 열거형 사용하는 경우 숫자 사용할 거 별로 없을 것.

iota ; 키워드는 아니고 식별자임. -> 자동적으로 값을 채워준다.

3. 구성(composition)을 위한 임베딩 사용

  • composition over inheritance
    : 클래스 상속보다는 객체 구성 선호

8번 라인 - 메서드. 메서드니까 패키지 레벨에 선언해주어야 하구, 값 메서드 사용해서 생성.


승격해서 ID만으로 바로 할당 가능. 원래부터 매니저에 ID가 있었던 것처럼 사용가능하다.
employee의 메소드도 바로 사용 가능. (아래)

임베딩된 항복의 모든 항목이나 메서드는 승격되어 임베딩한 구조체에 포함되고 바로 실행도 가능하다.


-> 명시적으로 표현하기

4. 임베딩은 상속이 아니다

5. 인터페이스

  • 암묵적 인터페이스
    : Go의 유일한 추상 타입

  • 인터페이스의 목적/용도

    • 스펙을 정의
    • 인터페이스를 사용하려면 인터페이스의 요소를 완성/구현(implement)해야 함
    • 이를 통해 타입의 안정성을 높이고 디커플링을 가능(솔로천국 커플지옥)
  • interface 키워드

  • 인터페이스 리터럴

  • interface {
    ...
    }
  • 위 ...에는 인터페이스를 만족시키기 위한 구체적 타입에서 반드시 구현(implement)해야 할 메서드 시그니처 나열

  • Go에서 관례적으로 인터페이스 이름은 끝에 ‘er’을 붙인다

  • 인터페이스는 모든 블록에서 선언 가능

6. 인터페이스는 타입에 안정적인 덕 타이핑이다.

package main

type LogicProvider struct {}

func (lp LogicProvider) Process(data string) string{ // 여기랑 11번라인이랑 같아서 연결됨. 암묵적으로 간접적으로. 
	return ""

}

type Logic interface {
	Process(data string) string // 메서드 모양과 인터페이스 모양이 같다.
}

type Client struct {
	L Logic
}

func (c Client) Program() {
	data := 어디선가 데이터 얻어옴
	c.L.Process(data)
}

func main() {
	c := Client{
		L: LogicProvider {},
	}
	
	c.Program()
}

7. 임베딩과 인터페이스

8. 인터페이스를 받고 구조체 반환하기

  • 디자인 패턴: program to interface not to implementation(함수)
  • Go: 인터페이스를 받고 구조체를 반환해라
    => 함수로 실행되는 비즈니스 로직은 인터페이스를 통해 실행
    => 함수의 출력은 구체 타입이어야 함

9. 인터페이스와 nil

10. 빈 인터페이스는 어떤 것도 표현하지 않는다

아무타입이나 아무 값 인터페이스에 저장할 수 있다.

11. 타입 단언 및 타입 스위치

  • 인터페이스 변수에 할당된 구체 타입 확인

  • 구체 타입이 다른 인터페이스를 구현했는지 확인

  • 타입 단언(Type Assertion)

    • 인터페이스 변수.(타입)
  • 타입 스위치 = 인터페이스.(type) + switch 문

Go의 동시성(Concurrency)

1. 동시성 사용 시점

  • 동시성은 병렬성이 아니다
  • 동시성 코드가 병렬적(동시에)으로 실행되는지는 하드웨어와 알고리즘에 의해 결정된다
  • 동시성은 동시에 실행되는 것이 시간이 얼마 걸리지 않을 때 사용하는 것은 좋지 않다.
  • 동시성은 공짜가 아니다

2. 고루틴

  • 프로세스
  • 스레드
  • OS의 스케줄러 및 Go 런타임의 스케줄러
    • Go 런타임 스케줄러의 장점(p.279~280)
  • 고루틴: go 키워드
    • Go 런타임에서 관리하는 가벼운 프로세스
    • 모든 함수가 고루틴이 될 수 있으나
    • 비즈니스 로직을 구현한 클로저를 고루틴으로 실행하는 것이 관례
      cf) 동기(synchronous) vs. 비동기(asynchronous)
      ex) 동기 통신 vs 비동기 통신
      동기 I/O vs 비동기 I/O

3. 채널

  • 고루틴(스레드)은 채널을 통해 통신한다.
  • make 함수로 생성
  • 내장 타입
  • 채널의 제로 값은 슬라이스, 맵, 포인터 처럼 nil
  • 기본적으로 버퍼 없음
  • 버퍼 있는 채널: make에서 수용량 지정

채널에 데이터를 쓰거나 읽을 수 없는 상태 -> blocking

기타

  • getter setter 객체지향에서 사용하는 디자인?
    객체 내부의 변수의 값을 쓰게하는 함수가 getter
    값을 읽어오는 class 메서드가 setter
 class Counter {
 	int cnt:
    String name:
    
    int getCnt()
    void setCnt(int cnt)
    String getName()
    void setName(String name)
 }
  • 자바에서,
String myName = null -> 이건 O
myName.upper() ->이건 X

하지만 Go는 포인터 리시버로 (포인터 메서드로) nil 받을 수 있다. => nil에 대해서 메서드 호출 가능. 자바는 불가능.

  • Go는 상속도 없도 다형성 지원 안함.
profile
꾸준히, 차근차근
post-custom-banner

0개의 댓글