[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

[Golang] SMTP를 이용한 메일 보내기

Go는 기본으로 제공하는 net//smtp 패키지를 통해 메일을 보낼 수 있습니다. 아래의 코드는 MicroSoft의 Live를 통해 메일을 보내는 코드입니다.

package main

import (
    "fmt"
    "net/smtp"
)

func main() {
    auth := smtp.PlainAuth("", "hjkim@geoservice.co.kr", "@pw", "smtp.live.com")

    from := "hjkim@geoservice.co.kr"
    to := []string{"korea.gisdeveloper@gmail.com", "geoservice@naver.com"}

    headerSubject := "Subject: 아기공룡 둘리 ㅡ 강민규\r\n"
    headerBlank := "\r\n"

    body := `
        요리 보고 저리 봐도 음음 알 수 없는 둘리~ 둘리~ 
        빙하타고 내려와 음음 친구를 만났지만 

        일억년전 옛날이 너무나 그리워 
        보고픈 엄마찾아 모두함께 나가자 하아 하아 

        기쁠때도 슬플때도 음음 우리 곁엔 둘리~ 둘리~ 
        오랜세월 흘러온~ 둘리와 친구되어 

        고~향은 다르지만 모두가 한마음 
        아득한 엄마나라 우리 함께 떠나자~ 하아 하아 

        <후렴> 
        외로운 둘리는 귀여운 아기공룡 
        호이 호이 둘리는 초능력 내친구(재주꾼) 
        호이 호이 호이 호이  
    `

    msg := []byte(headerSubject + headerBlank + body)

    err := smtp.SendMail("smtp.live.com:587", auth, from, to, msg)
    if err != nil {
        panic(err)
    }

    fmt.Println("done")
}

9번 코드에서 smtp 서버의 주소로 smtp.live.com를 지정했으며, 이 서버의 계정(hjkim@geoservice.co.kr)과 암호(@pw)를 지정합니다. 그리고 12번 코드에서 메일을 받을 주소를 배열을 통해 지정할 수 있습니다. 그리고 14번에 메일의 제목을, 17번에서 메일의 내용을 입력하고 이 제목과 내용을 36번 코드에서처럼 하나의 바이트 배열루 묶습니다. 이렇게 묶은 바이트 메일을 38번 코드를 통해 전송하면 됩니다.