golang - OOP 프로그래밍 하기

캡슐화

  • Go에서는 구조체와 해당 필드, 함수, 메소드의 범위는 이름의 선두가 대문자, 소문자로 결정된다.
  • 대문자이면 public, 소문자라면 같은 패키지 내로 닫힌 범위로.
  • 그러므로 아래와 같이 쓰는 것으로 캡슐화를 실현할 수 있다.
//human.go
package class

type Human struct {
    // 필드 이름을 소문자로 하여 첫 스코프를 같은 패키지 내로 닫는다
    name string
}

// 생성자
// 구조체가 public이라도 그 필드가 닫힌 스코프의 경우
// 패키지 외부에서 {}을 사용한 초기화는 할 수 없다.
func NewHuman(name string) *Human {
    return &Human{ name: name }
}

// 캡슐화
// 메소드 이름을 대문자부터 시작함으로써 public 범위로 한다.
func (human Human) GetName() string {
    return human.name
}

// setter는 수신기를 포인터로 하지 않으면 값이 변경되지 않는다
func (human *Human) SetName(name string) {
    human.name = name
}
// main.go
package main

import (
    "./class"
    "fmt"
)

func main() {
    human := class.NewHuman("alice")
    fmt.Println(human.GetName())
    human.SetName("bob")
    fmt.Println(human.GetName())
}
bash
$ go run main.go
alice
bob

주의

  • human.go에서 생성자라고 썼지만 Go에는 생성자라는 구문은 없다. 대신 New로 시작되는 함수를 정의하고, 그 내부 구조체를 생성하는 것이 통례.
  • 같은 패키지 내에서는 액세서를 경유하지 않고 접근할 수 있다.

Embed

  • Go에 계승은 없지만 대신 다른 형을 심는(Embed) 방식으로 행동을 확장할 수 있다.
  • 내장된 형(아래에서는 Human)의 필드와 메소드는 박은 형(아래에서는 Man)이 구현한 것처럼 행세한다.
// human.go
/*생략*/

// Human의 메소드
func (human Human) Check() {
    fmt.Printf("%s is a Human.\n", human.name)
}

type Man struct {
    *Human // Human을 내장
}

func NewMan(name string) *Man {
    return &Man{ Human: NewHuman(name) }
}
// main.go
/*생략*/

func main() {
    man := class.NewMan("bob")
    man.Check() // Human의 메소드를 자신이 구현한 것처럼 불러낼 수 있다.
}
// bash
$ go run main.go
bob is a Human.

오버 라이드

  • 동명의 메소드를 자신의 형을 수신기로 하고 재정의함으로써 메소드를 오버 라이드 할 수 있다.
// human.go
/*생략*/

func (man Man) Check( ) {
    fmt.Printf("%s is a Man.\n", man.name)
}
방금 전과 같은 main.go를 실행하면
bash
$ go run main.go
bob is a Man.

주의

박은 형(Man)을 내장한 형(Human)로 다룰 수 없다. 예를 들어,

var man *class.Human = class.NewMan("bob")

로 하면 컴파일 오류가 된다.
이는 처음에 올린 공식 문서에도 있듯이 Go에는 타입 계층이 없어서 Human과 Man은 완전히 다른 형태로 되기 때문이다.
Embed는 어디까지나 내장이지 계승이 아니다.

폴리 모피즘

  • Go에서는 승계를 사용한 폴리 모피즘은 실현 불가능하지만, interface을 구현함으로써 폴리 모피즘을 실현할 수 있다.
// human.go
/*생략*/

type Speaker interface {
    Speak()
}

/*생략*/

// Human에 Speaker를 구현
func (human Human) Speak() {
    fmt.Println("I am Humman.")
}

/*생략*/

//Human은 내장하지 않는다
type Woman struct {
    name string
}

func NewWoman(name string) *Woman {
    return &Woman{ name: name }
}

// Woman에 Speaker를 구현
func (woman Woman) Speak() {
    fmt.Println("I am Woman.")
}
// main.go
/*생략*/

func main() {
    human := class.NewHuman("alice")
    woman := class.NewWoman("emily")

    // Human도 Woman도 Speaker 인터페이스로 다룰 수 있다.
    speak(human)
    speak(woman)
}

func speak(speaker class.Speaker) {
    speaker.Speak()
}
// bash
$ go run main.go
I am a Humman.
I am a Woman.

interface를 구현된 타입을 내장

  • 어떤 interface를 구현된 타입을 내장한 구조체 또한 그 interface로 다룰 수 있다.
  • 위의 예에서는 Man은 Human를 내장하고 있으므로, Man자체는 Speaker를 구현하지 않아도 Speaker로서 다룰 수 있다.
// main.go
/*생략*/

func main() {
    human := class.NewHuman("alice")
    man := class.NewMan("bob")
    woman := class.NewWoman("emily")
    speak(human)
    speak(man)
    speak(woman)
}

func speak(speaker class.Speaker) {
    speaker.Speak()
}
bash
$ go run main.go
I am a Humman.
I am a Humman.
I am a Woman.

물론 Man이 Speak메서드를 오버 라이드 할 수도 있다.

출처

  • http://qiita.com/kitoko552/items/a6698c68379a8cd8b999

이 글은 2019-01-08에 작성되었습니다.