[struct] Go 인터페이스 필드

Go에서 인터페이스는 데이터가 아닌 기능을 정의한다는 사실을 잘 알고 있습니다. 일련의 메소드를 인터페이스에 넣었지만 해당 인터페이스를 구현하는 모든 것에 필요한 필드를 지정할 수 없습니다.

예를 들면 :

// Interface
type Giver interface {
    Give() int64
}

// One implementation
type FiveGiver struct {}

func (fg *FiveGiver) Give() int64 {
    return 5
}

// Another implementation
type VarGiver struct {
    number int64
}

func (vg *VarGiver) Give() int64 {
    return vg.number
}

이제 인터페이스와 그 구현을 사용할 수 있습니다.

// A function that uses the interface
func GetSomething(aGiver Giver) {
    fmt.Println("The Giver gives: ", aGiver.Give())
}

// Bring it all together
func main() {
    fg := &FiveGiver{}
    vg := &VarGiver{3}
    GetSomething(fg)
    GetSomething(vg)
}

/*
Resulting output:
5
3
*/

이제 할 수없는 것은 다음과 같습니다.

type Person interface {
    Name string
    Age int64
}

type Bob struct implements Person { // Not Go syntax!
    ...
}

func PrintName(aPerson Person) {
    fmt.Println("Person's name is: ", aPerson.Name)
}

func main() {
    b := &Bob{"Bob", 23}
    PrintName(b)
}

그러나 인터페이스와 임베디드 구조체를 가지고 놀아 본 후, 저는이 작업을 수행하는 방법을 발견했습니다.

type PersonProvider interface {
    GetPerson() *Person
}

type Person struct {
    Name string
    Age  int64
}

func (p *Person) GetPerson() *Person {
    return p
}

type Bob struct {
    FavoriteNumber int64
    Person
}

포함 된 구조체 때문에 Bob은 Person이 가진 모든 것을 가지고 있습니다. 또한 PersonProvider 인터페이스를 구현하므로 해당 인터페이스를 사용하도록 설계된 함수에 Bob을 전달할 수 있습니다.

func DoBirthday(pp PersonProvider) {
    pers := pp.GetPerson()
    pers.Age += 1
}

func SayHi(pp PersonProvider) {
    fmt.Printf("Hello, %v!\r", pp.GetPerson().Name)
}

func main() {
    b := &Bob{
        5,
        Person{"Bob", 23},
    }
    DoBirthday(b)
    SayHi(b)
    fmt.Printf("You're %v years old now!", b.Age)
}

다음은 위의 코드를 보여주는 Go Playground 입니다.

이 방법을 사용하면 동작이 아닌 데이터를 정의하는 인터페이스를 만들 수 있으며, 해당 데이터를 임베딩하는 것만으로 모든 구조체에서 구현할 수 있습니다. 포함 된 데이터와 명시 적으로 상호 작용하고 외부 구조체의 특성을 인식하지 못하는 함수를 정의 할 수 있습니다. 그리고 모든 것은 컴파일 타임에 확인됩니다! (내가 알 수있는 유일한 방법 은 콘크리트가 아닌에 인터페이스 PersonProvider를 포함하는 것입니다. 런타임에 컴파일되고 실패합니다.)BobPerson

자, 여기 내 질문이 있습니다. 이것은 깔끔한 속임수입니까, 아니면 다르게해야합니까?



답변

확실히 깔끔한 트릭입니다. 그러나 포인터를 노출해도 여전히 사용 가능한 데이터에 직접 액세스 할 수 있으므로 향후 변경을위한 제한된 추가 유연성 만 구입합니다. 또한 Go 규칙은 데이터 속성 앞에 항상 추상화를 두지 않아도 됩니다.

이러한 것들을 종합하면, 나는 주어진 사용 사례에 대해 한쪽 또는 다른 쪽을 향하는 경향이 있습니다. a) 공개 속성 (해당되는 경우 임베딩 사용)을 만들고 구체적인 유형을 전달하거나 b) 데이터를 노출하는 것처럼 보이는 경우 나중에 문제가 발생하면 더 강력한 추상화를 위해 getter / setter를 노출하십시오.

당신은 속성별로 이것을 평가할 것입니다. 예를 들어, 일부 데이터가 구현에 따라 다르거 나 다른 이유로 표현을 변경하려는 경우 속성을 직접 노출하고 싶지 않은 반면 다른 데이터 속성은 공개로 만드는 것이 순익이 될만큼 안정적 일 수 있습니다.


getter 및 setter 뒤에 속성을 숨기면 나중에 이전 버전과 호환되는 변경을 수행 할 수있는 추가 유연성이 제공됩니다. 언젠가 Person는 단일 “이름”필드가 아니라 이름 / 중간 / 성 / 접두사를 저장 하도록 변경하고 싶다고 가정 해 보겠습니다 . Name() string및 메서드가있는 경우 새로운 세분화 된 메서드를 추가하는 동안 인터페이스 SetName(string)의 기존 사용자를 Person만족 시킬 수 있습니다 . 또는 저장되지 않은 변경 사항이있는 경우 데이터베이스 지원 개체를 “더티”로 표시 할 수 있습니다. 데이터 업데이트가 모두 SetFoo()메소드를 거치면 그렇게 할 수 있습니다 .

따라서 getter / setter를 사용하면 호환되는 API를 유지하면서 구조체 필드를 변경할 수 있으며, 아무도 p.Name = "bob"코드를 거치지 않고 는 할 수 없기 때문에 속성 get / set에 대한 논리를 추가 할 수 있습니다 .

이러한 유연성은 유형이 복잡하고 코드베이스가 클 때 더 적합합니다. 가있는 경우 , a , a 데이터베이스 ID 또는 기타 항목에 PersonCollection의해 내부적으로 지원 될 수 있습니다 . 올바른 인터페이스를 사용 하면 네트워크 연결과 파일을 똑같이 보이게 하는 방식으로 발신자가 신경 쓰지 않도록 할 수 있습니다 .sql.Rows[]*Person[]uintio.Reader

한 가지 구체적인 사항 : interfaceGo의 s에는 정의하는 패키지를 가져 오지 않고도 구현할 수있는 고유 한 속성이 있습니다. 순환 가져 오기방지 할 수 있습니다 . 인터페이스가 *Person문자열이나 다른 것 대신를 반환하면 모두 정의 된 PersonProviders패키지를 가져와야합니다 Person. 그것은 괜찮을 수도 있고 심지어 불가피 할 수도 있습니다. 알아야 할 결과 일뿐입니다.


그러나 Go 커뮤니티에는 유형의 공용 API에서 데이터 멤버를 노출하는 것에 대한 강력한 규칙이 없습니다 . 오히려 낙담보다는, 주어진 경우에 대비하여 API의 일부로 속성에 대한 공용 액세스를 사용하는 것이 합리적인지의 판단에 남아 있는 노출이 가능 복잡한 수 이상을 구현 변화를 방지하기 때문이다.

예를 들어 stdlib는 http.Server구성으로 를 초기화 하고 0 bytes.Buffer을 사용할 수 있다고 약속하는 것과 같은 작업을 수행합니다 . 그런 식으로 자신의 작업을 수행하는 것은 괜찮습니다. 실제로 더 구체적이고 데이터를 노출하는 버전이 작동 할 것 같다면 선제 적으로 추상화해서는 안된다고 생각합니다. 장단점을 인식하는 것뿐입니다.


답변

내가 올바르게 이해한다면 하나의 구조체 필드를 다른 구조체 필드에 채우고 싶습니다. 확장을 위해 인터페이스를 사용하지 않는 것이 제 의견입니다. 다음 접근 방식으로 쉽게 할 수 있습니다.

package main

import (
    "fmt"
)

type Person struct {
    Name        string
    Age         int
    Citizenship string
}

type Bob struct {
    SSN string
    Person
}

func main() {
    bob := &Bob{}

    bob.Name = "Bob"
    bob.Age = 15
    bob.Citizenship = "US"

    bob.SSN = "BobSecret"

    fmt.Printf("%+v", bob)
}

https://play.golang.org/p/aBJ5fq3uXtt

참고 PersonBob선언. 이렇게하면 포함 된 struct 필드 Bob를 일부 구문 설탕과 함께 구조에서 직접 사용할 수 있습니다 .


답변