2016-06-26 2 views
1

У меня есть программа, которая пытается реализовать функции в «подклассах», где родитель может проверить, реализован ли интерфейс. Для перспективы он действительно имеет дело с созданием URL REST на основе того, существуют ли методы.Встроенный интерфейс Golang на родительской структуре

Что я натолкнулся на то, что на основе следующего шаблона интерфейсы IList и IGet находятся на объекте TestController, когда реализовано только 1. Когда вызывается интерфейс IGet, я получаю панику.

Я бы предпочел не делать конкретные определения Get/List на базовой структуре, а затем их переопределять, скорее всего, проведет проверку на существование, а затем оттуда.

Вот ссылка идут площадка, а https://play.golang.org/p/5j58fejeJ3

package main 

import "fmt" 

type IGet interface { 
    Get(int) 
} 

type IList interface { 
    List(int) 
} 

type Application struct { 
    name string 
} 

type BaseAppController struct { 
    *Application 

    IGet 
    IList 
} 

type TestController struct { 
    *BaseAppController 
} 

func (ctrl *BaseAppController) Init() { 
    fmt.Println("In Init") 

    if f, ok := interface{}(ctrl).(IGet); ok { 
     fmt.Println("Controller Found GET", f) 
    } else { 
     fmt.Println("Controller NOT Found GET", f) 
    } 

    if f, ok := interface{}(ctrl).(IList); ok { 
     fmt.Println("Controller Found LIST", f) 
    } else { 
     fmt.Println("Controller NOT Found LIST", f) 
    } 
} 

func (ctrl *BaseAppController) Call() { 
    fmt.Println("In Call") 

    if f, ok := interface{}(ctrl).(IGet); ok { 
     fmt.Println("Controller Found GET - going to call", f) 

     f.Get(7) 
    } else { 
     fmt.Println("Controller NOT Found GET - can't call", f) 
    } 
} 

// Test controller implements the Get Method 
func (ctrl *TestController) Get(v int) { 
    fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v) 
} 

func main() { 
    app := Application{"hithere"} 
    ctrl := TestController{&BaseAppController{Application: &app}} 

    ctrl.Init() 

    ctrl.Call() 
} 

ответ

4

Одна вещь, которую вам кажется недостающей, заключается в том, как встраивание интерфейсов влияет на структуру в Go. См., Внедрение, внедряет все методы встроенного типа (структура или интерфейс, не имеет значения) как методы родительского типа, но вызывается с использованием встроенного объекта в качестве получателя.

Практический побочный эффект этого заключается в том, что внедрение интерфейса в структуру гарантирует, что эта структура выполняет интерфейс, который он встраивает, поскольку по определению имеет все методы этого интерфейса. Попытка вызвать любой из этих методов, не определяя что-то, чтобы заполнить это поле интерфейса в структуре, однако, будет паниковать, так как это поле интерфейса по умолчанию равно nil.

В результате ваши утверждения типа будут всегда быть правдой. BaseAppController включает как интерфейсы IGet, так и IList, и поэтому всегда выполняет оба варианта.

Если вы хотите утиную печать, где поведение выборочно включено в зависимости от наличия или отсутствия методов для типа, вам нужно использовать что-то похожее на то, как работает стандартная библиотека io.WriterTo interface. Этот интерфейс и io.ReaderFrom являются дополнительными интерфейсами, которые могут быть реализованы объектами io.Writer и io.Reader для прямой записи или чтения из другого источника, а не пакета io, необходимого для буферизации прочитанных данных или данных, которые должны быть записаны сами.

Основной смысл заключается в том, что вы определяете базовый интерфейс с помощью необходимых методов, и это то, что вы проходите. Затем у вас есть один или несколько дополнительных интерфейсов, которые вы можете проверить пройденный тип, чтобы убедиться, что они выполняются, и если это так, используйте методы дополнительного интерфейса (а если нет, вернитесь к поведению по умолчанию). Вложение не требуется в этом случае.

Внедрение интерфейсов, а не для утиного ввода, - это больше о полиморфизме. Например, если вы хотите получить доступ к базе данных SQL, но хотите иметь возможность обрабатывать как стандартные вызовы и вызовы в рамках транзакции, вы можете создать структуру, которая содержит совместные методы двух типов (sql.DB и sql.Tx), например это:

type dber interface { 
    Query(query string, args ...interface{}) (*sql.Rows, error) 
    QueryRow(query string, args ...interface{}) *sql.Row 
    Exec(query string, args ...interface{}) (sql.Result, error) 
} 

Затем вы делаете структуру, как это:

type DBHandle struct { 
    dber 
} 

Теперь вы можете хранить либо sql.DB или sql.Tx в этом dber слот структуры, и все, что с помощью DBHandle (а также все методы DBHandle) может звонить Query(), QueryRow() и Exec() на DBHandle, не зная, вызывается ли они в рамках транзакции или нет (помните, однако, что это поле интерфейса должно быть сначала инициализировано!)

Этот тип функциональности - это то, где внедрение действительно начинает светиться, поскольку оно позволяет функциональность и гибкость близко к полностью полиморфной системе наследования без необходимости явных инструкций «реализует» или «расширяет». Это просто не очень полезно для типа поведения динамической утиной печати.

0

назвать ее эр: Getter вместо IGet
вам не нужно вставлять метод интерфейса в структуры, достаточно того, что ваша структура имеет Get приемник метод.

Вызов любого метода нильпотока значения интерфейса вызывает панику:
вы определили ctrl.IGet но не инициализируется его,
добавить эту строку в ваш первый, если внутри Init():

fmt.Printf("f=%T IGet:T=%T V=%[2]v\n", f, ctrl.IGet) 

выход это:

f=*main.BaseAppController IGet:T=<nil> V=<nil> 

Переменные всегда инициализируются четко определенным значением, а интерфейсы не являются исключением п. Нулевое значение для интерфейса имеет как его тип, так и значение, установленные в nil.
Ваш BaseAppController отсутствует Метод получения интерфейса.

и редактировать последний метод в примере кода для этого:

func (ctrl *BaseAppController) Get(v int) { 
    fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v) 
} 

и ваш код работает на данный момент.

, если вы хотите, чтобы зафиксировать текущий код с минимальными изменениями, просто заменить основную функцию с (обратите внимание, что Ctrl является указателем здесь):

func main() { 
    app := Application{"hithere"} 
    ctrl := &TestController{&BaseAppController{Application: &app}} 
    ctrl.IGet = interface{}(ctrl).(IGet) 
    ctrl.Init() 
    ctrl.Call() 
} 

работает образец кода (с минимальными изменениями):

package main 

import "fmt" 

type IGet interface { 
    Get(int) 
} 

type IList interface { 
    List(int) 
} 

type Application struct { 
    name string 
} 

type BaseAppController struct { 
    *Application 

    IGet 
    IList 
} 

type TestController struct { 
    *BaseAppController 
} 

func (ctrl *BaseAppController) Init() { 
    fmt.Println("In Init") 

    if f, ok := interface{}(ctrl).(IGet); ok { 
     fmt.Println("Controller Found GET", f) 
    } else { 
     fmt.Println("Controller NOT Found GET", f) 
    } 

    if f, ok := interface{}(ctrl).(IList); ok { 
     fmt.Println("Controller Found LIST", f) 
    } else { 
     fmt.Println("Controller NOT Found LIST", f) 
    } 
} 

func (ctrl *BaseAppController) Call() { 
    fmt.Println("In Call") 

    if f, ok := interface{}(ctrl).(IGet); ok { 
     fmt.Println("Controller Found GET - going to call", f) 

     f.Get(7) 
    } else { 
     fmt.Println("Controller NOT Found GET - can't call", f) 
    } 
} 

// Test controller implements the Get Method 
func (ctrl *TestController) Get(v int) { 
    fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v) 
} 

func main() { 
    app := Application{"hithere"} 
    ctrl := &TestController{&BaseAppController{Application: &app}} 
    ctrl.IGet = interface{}(ctrl).(IGet) 
    ctrl.Init() 
    ctrl.Call() 
} 

также это работает (встроенный интерфейс удалены):

package main 

import "fmt" 

type IGet interface { 
    Get(int) 
} 

type IList interface { 
    List(int) 
} 

type Application struct { 
    name string 
} 

type BaseAppController struct { 
    *Application 
} 

type TestController struct { 
    *BaseAppController 
} 

func (ctrl *TestController) Init() { 
    fmt.Println("In Init") 

    if f, ok := interface{}(ctrl).(IGet); ok { 
     fmt.Println("Controller Found GET", f) 
    } else { 
     fmt.Println("Controller NOT Found GET", f) 
    } 

    if f, ok := interface{}(ctrl).(IList); ok { 
     fmt.Println("Controller Found LIST", f) 
    } else { 
     fmt.Println("Controller NOT Found LIST", f) 
    } 
} 

func (ctrl *TestController) Call() { 
    fmt.Println("In Call") 

    if f, ok := interface{}(ctrl).(IGet); ok { 
     fmt.Println("Controller Found GET - going to call", f) 

     f.Get(7) 
    } else { 
     fmt.Println("Controller NOT Found GET - can't call", f) 
    } 
} 

// Test controller implements the Get Method 
func (ctrl *TestController) Get(v int) { 
    fmt.Printf("Hi name=%s v=%d\n", ctrl.name, v) 
} 

func main() { 
    app := Application{"hithere"} 
    ctrl := TestController{&BaseAppController{Application: &app}} 

    ctrl.Init() 

    ctrl.Call() 
} 

выход:

In Init 
Controller Found GET &{0xc082026028} 
Controller NOT Found LIST <nil> 
In Call 
Controller Found GET - going to call &{0xc082026028} 
Hi name=hithere v=7 
+0

Реальный код никогда не имеет Iget (только ленивый для примера). Вторая версия, которую вы отправили, помещает Init в окончательную структуру, что, конечно же, дает ему доступ к интерфейсам. Если моя цель состоит в том, чтобы иметь эффективный и абстрактный базовый класс, где мне не нужно иметь конкретную реализацию на каждом контроллере, это невозможно. Возможно, это не так, но это было бы очень полезно. FYI - Большая часть этого я пытаюсь понять возможности, что можно сделать с интерфейсами. – koblas

+0

@koblas Совершенно возможно, вы просто делаете это назад. Вместо встраивания интерфейсов базовый класс _can_, но _doesn't иметь to_ реализовать (поскольку вложение их по определению заставляет их реализовать их), вставьте базовый класс в дополнительные структуры, которые также реализуют «IGet» или «IList», и имеют интерфейс с необходимыми методами для прохождения. Пример: https://play.golang.org/p/SXuGc8fAMY – Kaedys

0

Не следует смешивать интерфейс вложения с структуры вложения.

Если вы вставляете интерфейсы в структуру, вы фактически добавляете новые поля в структуру с именем интерфейса, поэтому, если вы не инициируете их, вы получите панику, потому что они ноль.

Ваш BaseAppController ожидает кого-то, чтобы заполнить IGet и IList полей с чем-то, что satifies IGet и IList интерфейсы соответственно.

Это то, что ваш BaseAppController структура действительно выглядит следующим образом:

type BaseAppController struct { 
    Application *Application 
    IGet  IGet 
    IList  IList 
} 

Кажется, что вы пытаетесь сделать программирование Java-стиль в Go и не заканчивается хорошо.

+1

Это больше кода стиля Python (утка), если вы «X» и реализуете «Y», а затем добавьте следующее поведение. Хотя я понимаю, что это действительно невозможно. – koblas

+1

Не имеет значения, является ли это структурой или интерфейсом. В обоих случаях вы добавляете другое поле в свою структуру, которое будет иметь то же имя, что и внедренный тип. И это паникует, потому что в интерфейсе Go внутри есть всего два указателя - указатель на тип информации и указатель на данные. Если поле ввода с интерфейсом равно nil, то оба указателя равны нулю, и вы будете испытывать панику при попытке что-то сделать с ним. То же самое, если вы вставляете указатель на структуру - поле поддержки по умолчанию будет равно нулю, и вы получите панику при попытке получить к ней доступ. – creker

0

Если вы хотите наследовать любую структуру, состоящие из базовой структуры, вы можете написать это (некрасивый) код, который работает, как пакет отражает:

package main 

import (
"fmt" 
"unsafe" 
) 

type i interface{ 
    i() interface{} 
    ct(i) 
} 

type t1 struct{ 
    rptr unsafe.Pointer 
} 

func(x *t1) i() interface{} { 
    // parent struct can view child changed value, stored in rptr, as original value type with changes after store, instead of i interface 
    rv:= *(*i)(x.rptr) 
    fmt.Printf("%#v %d\n", rv, rv.(*t2).a) 
    return rv 
} 

func(x *t1) ct(vv i){ 
    // store pointer to child value of i interface type 
    // we can store any of types, i is for the sample 
    x.rptr = unsafe.Pointer(&vv) 
} 

type t2 struct{ 
    t1 
    a int 
} 


func main() { 
    t:=&t2{} 
    t.ct(t) // store original 
    t.a = 123 // change original 
    ti:=(t.i()).(*t2) // t.i() is a method of parent (embedded) struct, that return stored value as original with changes in interface{} 
    fmt.Printf("%#v %d\n",ti, ti.a) 
} 

Этот образец не хорошо. Лучший образец может включать в себя поле интерфейса:

type t1 struct{ 
    original i 
} 
Смежные вопросы