Как добавить больше функциональности при использовании существующих интерфейсов в golang? - PullRequest
0 голосов
/ 09 ноября 2018

Предположим, у меня есть интерфейс Foo, и я добавляю структуру, для которой нужны методы Foo, а также несколько дополнительных методов. В таком случае из следующих двух, что считается наилучшей практикой? Или, если есть какой-то другой более подходящий третий способ, предложите.

Подход 1

type Foo interface {
   methodA()
}

type Bar struct {
}

func (b Bar) methodA () {
   ...
}

func (b Bar) methodB () {
   ...
}

Подход 2

type Foo interface {
   methodA()
}

type Bar struct {
   Foo    // this can be initialized with any concrete implementation of Foo
}

func (b Bar) methodB () {
   ...
}

Кроме того, было бы замечательно, если бы можно было указать, в каких сценариях вышеуказанные подходы лучше подходят? Спасибо!

1 Ответ

0 голосов
/ 09 ноября 2018

Техническое примечание: первый метод гарантирует вам (помимо ошибок при инициализации структуры), что вы можете вызвать methodA для Bar, второй - нет, потому что вы должны инициализировать поле интерфейса чем-то, что не имеет этого интерфейса, чтобы не иметь ошибка нуля почтения.

Обратите внимание, что использование второго метода methodA вызывается не для Bar , а для встроенного объекта Foo !

Второй метод полезен, если у вас есть общая реализация, которая может совместно использоваться многими объектами и является самодостаточной, т. Е. Вы хотите знать, реализует ли объект метод Log, чтобы быть уверенным, что вы можете что-то регистрировать с этим объектом: в этом случае вы можете иметь метод, который возвращает Logger и установить поле интерфейса с этим. Пример следует:

package main

import "fmt"

type Logger interface {
    Log(string)
}

type ConsoleLogger struct {} // See? No external dependencies

func (Cl ConsoleLogger) Log(msg string) {
    fmt.Println(msg)
}

type A1 struct {
    Logger
}

type A2 struct {
    Logger
}

func main() {
    a := A1{ConsoleLogger{}}
    b := A2{ConsoleLogger{}}
    a.Log("Test")
    b.Log("Test")
}

Встраивание объектов полезно для диспетчеризации вызовов методов, помните, что в конце концов это просто синтаксический сахар, поэтому, кроме передачи содержащего объекта, вы не можете использовать его поля.

Если бы Logger интерфейс должен был использовать данные внешнего объекта (A1 и A2) каким-то образом, этот метод был бы неудобен, потому что вам пришлось бы инициализировать объект интерфейса, который затем сохранял бы некоторую ссылку на необходимые данные с тратой памяти в некоторых случаях.

ИМХО первый метод заставляет вас писать больше кода, но вы более свободны в реализации интерфейса, и вы можете смешать два подхода, внедрив Logger, а затем переопределить метод Log в A1 struct.

Кроме того, вы можете , тем не менее, передать что-то для создания чего-либо, реализующего интерфейс:

package main

import "fmt"

type Logger interface {
    Log(string)
}

type ConsoleLogger struct {
    Prepend string // string to prepend to log message
}

func (Cl ConsoleLogger) Log(msg string) {
    fmt.Println(Cl.Prepend + "-" + msg)
}

type A1 struct {
    Logger
}

type A2 struct {
    Logger
}

func (a A2) Log(msg string) { // Overriding implementation
    fmt.Println("In A2")
    a.Logger.Log(msg) // Call the original interface value!
}

func main() {
    a := A1{ConsoleLogger{"A1"}}
    b := A2{ConsoleLogger{"A2"}}
    a.Log("Test")
    b.Log("Test")
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...