[Golang] 변수가 어떤 interface의 매서드를 제공하는지 여부 확인하기

다음과 같은 타입이 있다고 치십시다.

type Boy int

func (b Boy) fight() {
	fmt.Println("Kaho-!")
}

func (b Boy) laugh() {
	fmt.Println("Kuwek-!")
}

type Girl float32

func (b Girl) laugh() {
	fmt.Println("kaka~")
}

또 다음과 같은 interface가 있다고 치자구요.

type Laughter interface {
	laugh()
}

type Fighter interface {
	fight()
}

이제 main 함수에서 Boy와 Girl 타입에 대한 객체를 정의할껀데요. 이 2개의 객체 변수가 Laughter, Fighter 인터페이스를 충족하는지의 여부를 검사하는 코드는 아래와 같습니다.

func main() {
	var b Boy
	var g Girl

	if _, ok := interface{}(b).(Laughter); ok {
		fmt.Println("Boy is laughter.")
	} else {
		fmt.Println("Boy is not laughter.")
	}

	if _, ok := interface{}(g).(Fighter); ok {
		fmt.Println("Girl is fighter.")
	} else {
		fmt.Println("Girl is not fighter.")
	}
}

그 결과는 예상하는 것처럼 아래와 같습니다.

Boy is laughter.
Girl is not fighter.

[Golang] 구조체 객체 생성

언어 Go는 필드를 하나의 개념으로 묶는 구조체 타입을 제공합니다. 아래는 (X, Y) 좌표를 나타내는 Point 타입을 나타냅니다.

type Point struct {
    x float64
    y float64
}

이 Point 타입을 객체로 만드는 방법은 너무….. 많아도 너무 마나. (마나? 히어로즈에서 제이나는 늘 마나가 부족하지..) 큭.. 여튼.. 많지만, 알고보면 일관성이 있어 이해가 어렵지 않은데요. 아래와 같은 다양한 방식을 지원합니다.

{
    var p Point
    p.x = 10.0
    p.y = 10.0
    fmt.Println(p)
}

{
    var p Point = Point{10.0, 10.0}
    fmt.Println(p)
}

{
    var p = Point{10.0, 10.0}
    fmt.Println(p)
}

{
    var p = Point{x: 10.0, y: 10.0}
    fmt.Println(p)
}

{
    var p *Point = new(Point)
    p.x = 10.0
    p.y = 10.0
    fmt.Println(p)
}

객체 생성 방식이 5개 정도 되는데요. 실행해 보면 다음과 같습니다.

{10 10}
{10 10}
{10 10}
{10 10}
&{10 10}

마지막 방식은 new를 통해 포인트 변수로 객체를 생성했습니다. 불행인지, 아니면 의도적인 것인지.. new를 통해 바로 구조체의 필드값을 지정할 수 없는데요. 이럴 때는 하나의 패턴으로 다음처럼 생성자 함수를 만들어 활용할 수 있습니다.

func NewPoint(x, y float64) *Point {
    return &Point{x, y}
}

위의 함수의 사용은 다음과 같습니다.

{
    var p = NewPoint(10.0, 10.0)
    fmt.Println(p)
}

실행 결과는 아래와 같습니다.

&{10 10}

[Golang] 객체 Pool

Golang에서 객체 재활용을 위한 pool을 제공하는데요. 이 객체 풀에 대한 내용을 정리해 봅니다.

먼저 풀에 넣고 재활용하고자 하는 객체 타입을 아래처럼 정의합니다.

type DB struct {
    bConnected bool
}

func (c *DB) connect() {
    c.bConnected = true
    fmt.Println("[connection completed]")
}

func (c *DB) query() {
    if c.bConnected {
        fmt.Println("    [query completed]")
    } else {
        fmt.Println("    [could not query]")
    }
}

데이터베이스 객체에 대해 흉내를 낸 DB 타입인데요. DB는 미리 DB 서버에 연결을 한번 맺어 놓고 계속 쿼리를 날립니다. 이 DB 타입은 DB 서버에 연결하는 connect 매서드와 쿼리를 날리는 query 매서드가 있고, DB 서버에 연결이 되어 있는지를 확인할 수 있는 bConnected 필드 변수를 가지고 있는데, 이 필드 변수는 connect 매서드를 호출하면 true로 변경됩니다.

자, 이제 이 DB 객체를 미리 연결해 놓고 풀에서 가져다 재활용하는 코드를 살펴보겠습니다.

func main() {
    pool := sync.Pool{
        New: func() interface{} {
            r := new(DB)
            r.bConnected = false
            return r
        },
    }

    for i := 0; i < 10; i++ {
        go func() {
            c := pool.Get().(*DB)
            if !c.bConnected {
                c.connect()
            }

            c.query()
            pool.Put(c)
        }()
    }

    fmt.Scanln()
}

Key Point가 되는 부분만을 살펴보면, 먼저 Golang의 Pool 객체를 생성하기 위해서 2~8번처럼 New 필드에 재사용할 객체를 생성해주는 함수를 정의해줘야 합니다. 4번처럼 앞서 DB 타입의 객체를 생성하고, 아직 connect 매서드를 호출하지 않았으므로 5번에서 bConnected를 false로 지정합니다. 물론 지정하지 않아도 Null 값으로 false가 저장되어 있지만 명확히 하는 것이 좋으니 이리 했습니다. 그리고 6번에서 생성한 DB 객체를 반환합니다. 이 New 함수는 풀에 객체가 부족할때만 호출되고 재활용 가능한 여분의 객체가 있다면 New 함수 호출 없이 여분의 객체 중 하나를 가져다가 사용합니다.

이제 10번의 for 문에서 10번에 걸쳐 db 객체를 통해 쿼리를 날립니다. go 루틴으로 쿼리를 동시에 10번 날리는 경우인데요. go 루틴의 함수를 보면.. 먼저 12번에서 DB 객체를 풀에서 가져옵니다. 여기서 타입단언(Type Assertion)을 통해 DB 타입이라는 것을 분명히 합니다. 그리고 13번에서 아직 연결이 되어 있지 않다면 connect 함수를 호출하구요. 17번에서 쿼리를 날립니다. DB 객체를 다 사용했다면 18번 코드에서 다시 풀에 반환합니다.

만약 풀을 사용하지 않았다면 10번의 DB 연결과 10번의 DB 쿼리가 발생할 것입니다만.. 우리는 풀을 사용했기 때문에 DB 쿼리는 10번을 했지만 DB 연결은 그보다 적을 것입니다. 제 PC에서는의 실행 결과는 아래와 같습니다.

[connection completed]
    [query completed]
    [query completed]
    [query completed]
    [query completed]
    [query completed]
    [query completed]
    [query completed]
[connection completed]
    [query completed]
    [query completed]
[connection completed]
    [query completed]

실행할때마다 그 결과는 매번 달라지기는 했지만, 분명한 것은 DB 쿼리에 대한 query completed 문자열은 항상 10번 표시되고 connection completed는 그보다 적게 표시된다는 것입니다.

[Golang] 열거형(enum)

고는 열거형 타입을 지원하지 않습니다만, 그 열거형의 목적을 달성할 수 있습니다. 아래의 코드는 12개의 달(Month)에 대한 고 언어적 열거형 타입의 정의입니다.

type Month int

const (
	January Month = 1 + iota
	February
	March
	April
	May
	June
	July
	August
	September
	October
	November
	December
)

var months = [...]string{
	"January",
	"February",
	"March",
	"April",
	"May",
	"June",
	"July",
	"August",
	"September",
	"October",
	"November",
	"December",
}

func (m Month) String() string { return months[(m-1)%12] }

언어에서 근본적으로 열거형을 지원하지 않다보니, 다소 불편하고 어색하기는 합니다. 주지해야 할 코드만을 살펴보면, 4번에서 상수형 값 정의시 iota는 그 이후의 상수값들을 1씩 더해 정의되어지도록 지정합니다. 즉, January에 1의 값으로 정의되고, 그 다음인 February는 2의 값을 자동으로 갖도록한다는 것입니다. 그리고 33번은 Month를 enum 타입으로써 그 문자열 명칭을 갖도록 매서드를 추가해 주는 것입니다. 달의 순환이라는 개념을 위해 12로 나눈 나머지값을 인덱스 값으로 취했습니다. 활용하는 코드는 아래와 같습니다.

func main() {
	month := December

	if month == December {
		fmt.Println("Found a December")
	}

	month = month + Month(2)
	fmt.Println(month)

	month = January + Month(2)
	fmt.Println(month)

	month++
	fmt.Println(month)

	day := 34
	month = Month(day % 31)
	fmt.Println(month)

	val := int(month) + 4
	fmt.Println(val)

	month = Month(val) + 1
	fmt.Println(month)
}

우리가 원하는 enum 타입의 정의대로 그 결과값을 그대로 얘상할 수 있는데요. 결과는 아래와 같습니다.

Found a December
February
March
April
March
7
August