среда, 17 ноября 2021 г.

Go для Java разработчиков: методы и интерфейсы

Метод выглядит как обычное определение функции, за исключением того, что у него есть получатель. Получатель аналогичен ссылке this в методе экземпляра Java.

type MyType struct { i int }

func (p *MyType) Get() int {
    return p.i
}

var pm = new(MyType)
var n = pm.Get()

Это объявляет метод Get, связанный с MyType. Получатель назван p в теле функции.

Методы объявлены для определенных типов. Если вы преобразовываете значение в другой тип, новое значение будет иметь методы нового типа, а не методы старого типа.

Вы можете определить методы для встроенного типа, объявив новый определенный тип, производный от него. Новый тип отличается от встроенного.

type MyInt int

func (p MyInt) Get() int {
    return int(p) // Требуется преобразование.
}

func f(i int) {}
var v MyInt

v = v * v // Операторы базового типа все еще применяются.
f(int(v)) // int(v) не имеет объявленных методов.
f(v)      // INVALID

Интерфейсы

Интерфейс Go похож на интерфейс Java, но любой тип, который предоставляет методы, названные в интерфейсе Go, можно рассматривать как реализацию этого интерфейса. Никакого явного объявления не требуется.

Предположим, что этот интерфейс определен:

type MyInterface interface {
    Get() int
    Set(i int)
}

Поскольку MyType уже имеет метод Get, мы можем заставить MyType удовлетворить интерфейс, добавив

func (p *MyType) Set(i int) {
    p.i = i
}

Теперь любая функция, которая принимает MyInterface в качестве параметра, будет принимать переменную типа *MyType.

func GetAndSet(x MyInterface) {}

func f1() {
    var p MyType
    GetAndSet(&p)
}

В терминах Java определение Set и Get для *MyType привело к тому, что *MyType автоматически реализует MyInterface. Тип может удовлетворять нескольким интерфейсам. Это форма неявной типизации (утиная типизация (англ. Duck typing)).

Когда я вижу птицу, которая ходит, как утка, плавает, как утка, и крякает, как утка, я называю эту птицу уткой. - Джеймс Уиткомб Райли

Встраивание (делегирование)

Встраивание типа как анонимного поля может использоваться для реализации формы подтипов.

type MySubType struct {
    MyType
    j int
}

func (p *MySubType) Get() int {
    p.j++
    return p.MyType.Get()
}

Это реализация MySubType как подтипа MyType.

func f2() {
    var p MySubType
    GetAndSet(&p)
}

Метод Set унаследован от MyType, поскольку методы, связанные с анонимным полем, повышаются до методов включающего типа.

В этом случае, поскольку MySubType имеет анонимное поле типа MyType, методы MyType также становятся методами MySubType. Метод Get был переопределен, а метод Set был унаследован.

Это не то же самое, что подкласс в Java, а форма делегирования. Когда вызывается метод анонимного поля, его получателем является поле, а не окружающая структура. Другими словами, методы анонимных полей не отправляются динамически. Если вам нужен эквивалент динамического поиска метода Java, используйте интерфейс.

func f3() {
    var v MyInterface

    v = new(MyType)
    v.Get() //  Вызов метода Get для *MyType.

    v = new(MySubType)
    v.Get() // Вызов метода Get для *MySubType.
}

Утверждения типа

Переменная, имеющая тип интерфейса, может быть преобразована в другой тип интерфейса с помощью утверждения типа. Это реализуется динамически во время выполнения. В отличие от Java, между двумя интерфейсами не требуется декларирования взаимосвязи.

type Printer interface {
    Print()
}

func f4(x MyInterface) {
    x.(Printer).Print() // утверждения типа для Printer
}

Преобразование в Printer полностью динамическое. Оно будет работать до тех пор, пока динамический тип x (фактический тип значения, хранящегося в x) определяет метод Print.


Читайте также:


Комментариев нет:

Отправить комментарий