ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 6장 메소드
    프로그래밍/Golang 2019. 11. 4. 00:28
    The Go Programming Language

    6장 메소드

    Go도 객체지향 프로그래밍을 지원함

    Go에서는 객체를 단순히 메소드를 가진 값이나 변수로 정의

    메소드는 특정 타입과 관련된 함수로 정의

    객체지향 프로그램은 메소드를 통해 데이터 구조의 특성과 동작을 포현하므로 사용자는 객체의 구현에 직접 접근할 필요가 없음

     

    6.1 메소드 선언

    메소드는 일반 함수 선언을 변형해 함수명 앞에 부가적인 파라미터를 추가한 형태로 선언

    부가적인 파리미터는 함수의 파라미터 타입에 추가

    gopl.io/ch6/geometry
    package geometry
    import "math"
    type Point struct{ X, Y float64 }
    
    // traditional function
    func Distance(p, q Point) float64 {
    	return math.Hypot(q.X-p.X, q.Y-p.Y) 
    }
    // same thing, but as a method of the Point type
    func (p Point) Distance(q Point) float64 {
    	return math.Hypot(q.X-p.X, q.Y-p.Y) 
    }

    추가 파라미터 p를 메소드의 수신자라 부르는데 이는 초기 객체지향 언어에서 메소드 호출을 "객체에 메시지를 전송한다."라고 하던 전통에 따른 것

    GO에서는 수신자에 this나 self 등의 특별한 이름을 사용하지 않음

    수신자 이름은 다른 파라미터 이름과 마찬가지로 임의로 선택 가능

    수신자 이름은 자주 사용되므로 짧으면서 메소드들에서 일관된 이름을 고르는 것이 좋음

    일반적으로 Point의 p와 같은 타입명의 첫 번째 글자를 사용

    메소드를 호출할 때 수신자의 인수는 메소드명 앞에 씀

    p := Point{1, 2}
    q := Point{4, 6}
    fmt.Println(Distance(p, q)) // "5", function call 
    fmt.Println(p.Distance(q)) // "5", method call

    위의 두 Distance 함수 선언 간에는 충돌이 일어나지 않음

    첫 번째는 패키지 수준 함수 geometry.Distance를 선언

    두 번째는 Point 타입의 메소드를 선언하므로 이름이 Point.Distance가 됨

    p.Distance 표현식은 Point 타입의 수신자 p에 대응하는 Distance 메소드를 선택하므로 셀렉터(selector)라 함

    셀렉터는 p.X에서와 같이 구조체 타입의 필드를 선택할 때에도 사용하며 이는 메소드와 필드가 같은 네임스페이스에 있으므로 구조체 타입 Point에 메소드 X를 선언하면 대상이 확실치 않아서 컴파일러가 거부

     

    6.2 포인터 수신자가 있는 메소드

    함수를 호출하면 각 인자 값의 복사본이 생성되므로 함수에서 변수 값을 변경해야 하거나 인자가 커서 가급적 복사하고 싶지 않은 경우에는 포인터를 이용해 변수의 주소를 전달해야 함

    수신자 변수를 변경해야 하는 메소드에서도 마찬가지로 포인터는 *Point와 같이 포인터 타입에 추가

    func (p *Point) ScaleBy(factor float64) { 
    	p.X *= factor
    	p.Y *= factor 
    }

    위 메소드의 이름은 (*Point).ScaleBy이며 괄호는 필수

    괄호가 없으면 표현식이 *(Point.ScaleBy)로 핵석됨

    수신자 선언에는 명명된 타입(Point)과 이 타입의 포인터(*Point)만 가능

    포인터 타입의 명명된 타입에는 메소드 선언을 허용하지 않음으로써 모호한 표현을 방지

    type P *int
    func (P) f() { /* ... */ } // compile error: invalid receiver type

    (*Point).ScaleBy 메소드는 다음과 같이 *Point 수신자를 통해 호출

    r := &Point{1, 2} 
    r.ScaleBy(2) 
    fmt.Println(*r) // "{2, 4}"
    
    //또는
    p := Point{1, 2}
    pptr := &p 
    pptr.ScaleBy(2) 
    fmt.Println(p) // "{2, 4}"
    
    //또는
    p := Point{1, 2} 
    (&p).ScaleBy(2) 
    fmt.Println(p) // "{2, 4}"

    위 마지막 두 경우는 어색한데 이럴 때 언어의 도움을 받아서 수신자 p가 Point 타입의 변수지만 메소드에 *Point가 필요할 때 다음과 같은 단축 문법을 사용하면 컴파일러가 묵시적으로 변수를 &p로 변경하며 이 동작은 구조체 필드 p.X나 변수 혹은 슬라이스의 원소 perim[0]과 같은 변수에서만 가능

    p.ScaleBy(2)

    위 세 가지 경우는 자주 혼동되는 부분이므로 다시 요약

    첫 번째 수신자 인수의 타입과 수신자 파라미터의 타입이 같을 때 예를 들어 둘 다 T 또는 *T일 때는 다음과 같음

    Point{1, 2}.Distance(q) // Point 
    pptr.ScaleBy(2) // *Point

    두 번째 수신자 인수가 T 타입의 변수이고 수신자 파라미터가 *T 타입일 때 컴파일러가 묵시적으로 변수의 주소를 취함

    p.ScaleBy(2) // implicit (&p)

    세 번째 수신자 인수가 *T 타입이고 파라미터가 T 타입일 때 컴파일러가 묵시적으로 수신자를 역참조해 값을 읽음

    pptr.Distance(q) // implicit (*pptr)

    6.2.1 nil은 유효한 수신자 값

    일부 함수의 인수로 nil 포인터가 허용되는 것과 마찬가지로 일부 메소드도 특히 맵과 슬라이스와 같이 nil이 유의미한 제로 값인 경우 인수로 nil 포인터를 사용

    // An IntList is a linked list of integers. 
    // A nil *IntList represents the empty list. 
    type IntList struct {
    	Value int
    	Tail  *IntList
    }
    // Sum returns the sum of the list elements. 
    func (list *IntList) Sum() int {
    	if list == nil { 
        	return 0
    	}
    	return list.Value + list.Tail.Sum() 
    }

    타입에 수신자 값으로 nil을 허용하는 메소드를 정의할 때 위에서처럼 문서 주석에 명시하는 것이 좋음

     

    6.3 내장 구조체를 통한 타입 조합

    gopl.io/ch6/coloredpoint
    import "image/color"
         type Point struct{ X, Y float64 }
         type ColoredPoint struct {
             Point	// 내장 구조체
    		Color color.RGBA 
         }

    내장 방식은 많은 메소드가 있는 복잡한 타입을 소수의 메소드를 갖는 여러 필드의 조합으로 만들 수 있음

     

    6.4 메소드 값과 표현식

    p := Point{1, 2} 
    q := Point{4, 6}
    distanceFromP := p.Distance // method value
    fmt.Println(distanceFromP(q)) // "5"
    var origin Point // {0, 0}
    fmt.Println(distanceFromP(origin))// "2.23606797749979", 루트 5
    
    scaleP := p.ScaleBy // method value
    scaleP(2)// p becomes (2, 4)
    scaleP(3)//      then (6, 12)
    scaleP(10)//      then (60, 120)
    
    

     

    메소드 값은 패키지 API가 함수 값을 호출하거나 사용자가 해당 함수에서 특정 수신자의 메소드를 호출하려 할 때 유용

    메소드 값은 메소드 표현식과 연관됨

    메소드를 호출할 때는 통상의 함수와 달리 특별히 셀렉터 문법을 이용해 수신자를 지정해야 함

    타입 T에서 T.f나 (*T).f로 작성하는 메소드 표현식은 통상적인 첫 번째 파리미터를 수신자로 받는 함수 값을 산출하므로 일반적인 방법으로 호출할 수 있음

    p := Point{1, 2} 
    q := Point{4, 6}
    distance := Point.Distance // method expression
    fmt.Println(distance(p, q)) // "5"
    fmt.Printf("%T\n", distance)// "func(Point, Point) float64"
    
    scale := (*Point).ScaleBy 
    scale(&p, 2)
    fmt.Println(p) // "{2 4}"
    fmt.Printf("%T\n", scale) //"func(*Point, float64)"

    메소드 표현식은 한 타입의 여러 메소드 중 하나를 선택하고 선택된 메소드를 여러 수신자에서 호출할 때 도움이 됨

    type Point struct{ X, Y float64 }
    func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} } 
    func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }
    type Path []Point
    func (path Path) TranslateBy(offset Point, add bool) {
    	var op func(p, q Point) Point
    	if add {
    		op = Point.Add
        } else { 
        	op =  Point.Sub
    	}
        for i := range path {
            // Call either path[i].Add(offset) or path[i].Sub(offset).
            path[i] = op(path[i], offset) 
        }
    }

    6.5 예제: 비트 벡터 타입

    비트 벡터는 각 비트가 집합에 허용되는 원소를 나타내는 부호 없는 정수 값이나 "워드"의 슬라이스를 사용

    i번째 비트가 설정되면 집합은 i를 포함

     

    6.6 캡슐화

    객체의 사용자가 객체의 변수나 메소드에 접근할 수 없는 경우 객체가 캡슐화돼 있다고 함

    캡슐화는 때로는 정보은닉으로도 불림

    Go에는 식별자의 가시성을 제어하는 단 한 가지 메커니즘이 있는데 대문자로 시작하는 식별자는 정의된 패키지에서 노출되며 대문자가 아니면 노출되지 않음

    패키지의 멤버에 대한 접근 제한 메커니즘은 구조체의 필드나 타입의 메소드에도 적용되며 결과적으로 객체를 캡슐화하려면 구조체로 만들어야 함

    • 장점

    첫째, 사용자가 직접 객체 변수를 수정할 수 없으므로 일부 문장만으로 이 변수에 허용되는 값의 범위를 알 수 있음

    둘째, 구현의 세부 사항을 숨김으로써 사용자가 이후에 변경될 수 있는 부분에 의존하지 않게 해 설계자가 API 호환성을 유지하면서 구현을 자유롭게 진화시킬 수 있음

    셋째, 사용자가 객체의 변수를 임의로 변경할 수 없음

     

     

     

    '프로그래밍 > Golang' 카테고리의 다른 글

    Golang 관련 도서 다운로드 및 번역 문서  (0) 2019.11.13
    7장 인터페이스  (0) 2019.11.06
    5장 함수  (0) 2019.11.03
    4장 복합 타입  (0) 2019.10.30
    Golang - 함수  (0) 2019.08.06
Designed by Tistory.