Go: 인터페이스를 unexported로 만들어 봐.

15933 단어 Gotech

일단 unexported.


Go에서 동작을 추상화할 때 인터페이스를 사용합니다.
또 인터페이스 분리의 원칙(ISP)을 준수하는 등 드문드문 결합된 코드를 쓸 때 호출자가 요구하는 인터페이스를 사용하지 않는 방법에 의존하지 않도록 정의한다.
코드를 이렇게 쓰면 Go의 유형은 은근히 인터페이스를 충족시키기 때문에 처음부터 export 인터페이스가 필요하지 않겠죠.
위에서 말한 바와 같이 Go에서 유형은 인터페이스를 실현하는 방법에서 이 인터페이스를 만족시킨다(예를 들어 사용하지 않아도 된다implement 등 명시).즉 인터페이스가 unexported라고 해도 인터페이스에 unexported 방법이 없으면 외부 포장의 유형도 인터페이스를 충족시킬 수 있다는 것이다.
package main

// ...

func greetTo(w io.Writer, g greeter) {
	fmt.Fprintln(w, g.Greet())
}

type greeter interface {
	Greet() string
}
package l10n

type JA struct{}

func (JA) Greet() string {
	return "こんにちは"
}

type EN struct{}

func (EN) Greet() string {
	return "Hello"
}
l10n패키지JAEN패키지main인터페이스가 충족되기 때문에greeter패키지main함수의 두 번째 매개 변수는 전달할 수 있다.
package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/tomocy/go-if/l10n"
)

var greeters = map[string]greeter{
	"ja": l10n.JA{}, // l10n.JAはgreeterインターフェイスを満たす
	"en": l10n.EN{}, // l10n.ENはgreeterインターフェイスを満たす
}

func main() {
	if err := run(os.Stdout, os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func run(w io.Writer, args []string) error {
	if len(args) < 1 {
		return errors.New("too few arguments")
	}

	flags := flag.NewFlagSet(args[0], flag.ContinueOnError)
	lang := flags.String("lang", "ja", "language to greet")

	if err := flags.Parse(os.Args[1:]); err != nil {
		return err
	}

	g, ok := greeters[*lang]
	if !ok {
		return fmt.Errorf("unsupported language: %s", *lang)
	}

	greetTo(w, g)

	return nil
}

func greetTo(w io.Writer, g greeter) {
	fmt.Fprintln(w, g.Greet())
}

type greeter interface {
	Greet() string
}
확실합니다. greetTo처럼 많은 포장에서 (파라미터나 유형 필드의 유형으로) 사용할 때 인터페이스를 export할 수 있습니다.
func main() {
	if err := run(os.Stdout, os.Args); err != nil {
		fmt.Fprintln(os.Stderr)
		os.Exit(1)
	}
}

func run(w io.Writer, args []string) error {
	// ...
}
상기 코드는 io.Writer 덕분에 매번 정의io.Writer와 같은 인터페이스가 필요 없고 각 포장이 조화롭다.
그러나 이런 상황에서 (이 인터페이스는 매우 일반적인 행동거지를 나타낸다) 사실 매우 드물다. 많은 경우 ISP를 보호하면서 unexported로 정의할 수 있지 않겠는가.이 경우 다소 중복되는 경우가 있을 것 같은데, 이 부분을 위해 의존도를 높이는 것보다 결합도를 낮추는 게 더 중요하지 않겠나.
A little copying is better than a little dependency.
Go Proverbs - Rob Pike - Gopherfest - November 18, 2015
또한 인터페이스에 방법을 추가할 때 후방 호환성을 갖추지 못하기 때문에 인터페이스 export를 많은 포장에 사용하고 사용법을 알게 된 후에도 늦지 않을 것입니다.

doNotImplement


참고로 위에서 말한 바와 같이 인터페이스의 exported, unexported를 막론하고 unexported 방법을 인터페이스에 추가하면 외부 포장 형식이 만족할 수 없는 인터페이스를 만들 수 있다.
package main

// ...

type greeter interface {
	doNotImplement

	Greet() string
}

type doNotImplement interface {
	doNotImplement()
}
package l10n

// ...

type JA struct{}

func (JA) Greet() string {
	return "こんにちは"
}

func (JA) doNotImplement() {}
아까 예시된 io.Writer 패키지의 main 인터페이스에 unexported를 추가하는 방법이 있다면 greeter 패키지의 l10n 또는 JA 방법이 있어도 EN 어느 것도 만족하지 않기 때문doNotImplement 인터페이스컴파일 오류는 다음과 같습니다.
./main.go:14:2: cannot use l10n.JA literal (type l10n.JA) as type greeter in map value:
        l10n.JA does not implement greeter (missing doNotImplement method)
                have l10n.doNotImplement()
                want doNotImplement()
./main.go:15:2: cannot use l10n.EN literal (type l10n.EN) as type greeter in map value:
        l10n.EN does not implement greeter (missing doNotImplement method)
                have l10n.doNotImplement()
                want doNotImplement()

좋은 웹페이지 즐겨찾기