Programing

Go 인터페이스 필드

lottogame 2020. 9. 19. 11:55
반응형

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 입니다.

Using this method, I can make an interface that defines data rather than behavior, and which can be implemented by any struct just by embedding that data. You can define functions that explicitly interact with that embedded data and are unaware of the nature of the outer struct. And everything is checked at compile time! (The only way you could mess up, that I can see, would be embedding the interface PersonProvider in Bob, rather than a concrete Person. It would compile and fail at runtime.)

Now, here's my question: is this a neat trick, or should I be doing it differently?


It is definitely a neat trick, and works as long as you're cool giving access to those fields as part of your API. The alternative I'd consider on is keeping the embeddable struct/interface setup, but defining the interface in terms of getters and setters.

Hiding properties behind getters and setters gives you some extra flexibility to make backwards-compatible changes later. Say you someday want to change Person to store not just a single "name" field but first/middle/last/prefix; if you have methods Name() string and SetName(string), you can keep existing users of the Person interface happy while adding new finer-grained methods. Or you might want to be able to mark a database-backed object as "dirty" when it has unsaved changes; you can do that when data updates all go through SetFoo() methods.

So: with getters/setters, you can change struct fields while maintaining a compatible API, and add logic around property get/sets since no one can just do p.Name = "bob" without going through your code.

That flexibility is more relevant when your type does something more complicated. If you have a PersonCollection, it might be internally backed by an sql.Rows, a []*Person, a []uint of database IDs, or whatever. Using the right interface, you can save callers from caring which it is, the way io.Reader makes network connections and files look alike.

One specific thing: interfaces in Go have the peculiar property that you can implement one without importing the package that defines it; that can help you avoid cyclic imports. If your interface returns a *Person, instead of just strings or whatever, all PersonProviders have to import the package where Person is defined. That may be fine or even inevitable; it's just a consequence to know about.

All that said, there's no Go convention that you have to hide all your data. (This is a welcome difference from, say, C++.) The stdlib does things like let you initialize an http.Server with your config and promises that a zero bytes.Buffer is usable. It's fine to do your own stuff like that, and, indeed, I don't think you have to do premature abstraction if the more concrete, data-exposing version works. It's just about being aware of the tradeoffs.

참고URL : https://stackoverflow.com/questions/26027350/go-interface-fields

반응형