И как же быть?
Пока приходится копировать реализацию Bar() в каждом "наследнике". А это ну... моветон какой-то. Должен же быть способ, не может же быть что так тупо всё и задумывалось.
Здравствуйте, LaptevVV, Вы писали:
LVV>Наследования в Го не принципиально. LVV>Есть шаблоны LVV>Есть рефлексия. LVV>Наверное, с их помощью можно. LVV>Как — не подскажу, я в Го еще даже не джун...
С шаблонами дженериками не выйдет. Во-первых нет дженерик методов (может быть либо type, либо func(), но не func()()). Во вторых:
func (f *foo) Bar() {
f.Foo() // Ожидается, что здесь будет вызван метод "наследника"
// Но похоже, что инфорация о типе здесь безвозвратно утеряна.
log.Println("foo:Bar")
}
f *foo не помнит, что он был *bar, поэтому и возникла проблема. Из-за той-же причины и рефлекцией особо не нарефлексируешь. Не говоря уже, что решать такую задачу рефлексией — это вообще фу. Лучше уж копипастить и ждать go2, где предлагаются контракты с реализацией по умолчанию. Но по опыту с дженериками ждать этих введений смысла особо нет, т.к. похоже, что всё равно сделают через анус, и особо с этим ничего и не сделаешь.
Здравствуйте, Буравчик, Вы писали:
Б>Ты хочешь иметь дефолтную реализацию, и переопределить ее в наследниках?
Да, но есть нюанс . Надо вызвать наследника из базы. Я перепишу стартовый пример, чтобы намерения стали понятнее:
package main
type Behavor interface {
CheckCondition() bool//Это могло быть и приватным, но пусть клиентский код тоже сможет проверять условие
DoAction()
}
type defaultBefavior struct{}
func (*defaultBefavior) CheckCondition() bool {
panic("Not implemented!!!")
}
func (b *defaultBefavior) DoAction() {
if !b.CheckCondition() { //Дёрнуть наследникаreturn
}
// Do actual action
// Общий код для всех наследников
}
type stupidBehavior struct {
*defaultBefavior
}
func (b *stupidBehavior) CheckCondition() bool {
//Проверяем внутреннее состояние - разное для всех наследников
// и, в соответсвии с этим, возвращаем результатreturn true
}
func NewStupidBehavior() Behavor {
return &stupidBehavior{
defaultBefavior: &defaultBefavior{},
}
}
func main() {
st := NewStupidBehavior()
st.DoAction() // panics
}
Это не работает, и даже понятно почему: нет никакого наследования и виртуальных таблиц, есть композиция.
И это нормально работает в объектно — ориентированных языках с виртуальными методами и настоящим наследованием.
То, что я хочу, должно выглядеть (для go) примерно так:
func (b Befavior) DoAction() { // здесь сам интерфейс является ресиверомif !b.CheckCondition() { //Дёрнуть наследникаreturn
}
// Do actual action
// Общий код для всех наследников
}
Зато можно вызывать базу из наследников. Сейчас у меня работет такой код:
package main
type Behavor interface {
CheckCondition() bool//Это могло быть и приватным, но пусть клиентский код тоже сможет проверять условие
DoAction()
}
type defaultBefavior struct{}
func (b *defaultBefavior) doActualAction() {
// Общий код для всех наследников
}
type stupidBehavior struct {
*defaultBefavior
}
func (b *stupidBehavior) CheckCondition() bool {
//Проверяем внутреннее состояние - разное для всех наследников
// и, в соответсвии с этим, возвращаем результатreturn true
}
func (b *stupidBehavior) DoAction() {
if b.CheckCondition() {
b.doActualAction()
}
}
func NewStupidBehavior() Behavor {
return &stupidBehavior{
defaultBefavior: &defaultBefavior{},
}
}
func main() {
st := NewStupidBehavior()
st.DoAction()
}
Но это значит, что метод DoAction() должен копироваться без изменений во все наследники.
Сначала я смирился и запилил 3-х наследников. Но потом, когда месяца через два стал пилить четвёртого, мой внутренний перфекционист взбунтовался — мало ли что я забуду скопировать.
Возможно я что-то делаю не так, но я так и не смог придумать как избавиться от копирования. Всё ночь снились дженерики и даже генерация кода.
Сдаётся мне, что должен быть какой-то идиоматический путь избежать такой копипасты, но я его не вижу...
Здравствуйте, Doom100500, Вы писали:
D>Да, но есть нюанс . Надо вызвать наследника из базы. Я перепишу стартовый пример, чтобы намерения стали понятнее:
Зачем вызывать наследника из базы? Откуда взялось такое ограничение?
Если этого ограничения нет, то предложенный код раз все и делает:
Algorithm.Run() — это твой DoAction()
Step1 — это CheckCondition()
Все поведение, которое может переопределить наследник, нужно выделить в интерфейс (steps). При этом можно предоставить дефолтные steps, чтоб наследнику не пришлось переопределять все шаги. В отдельном классе (не базе, в моем случае Algorithm) прописать любую обобщенную логику
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, Doom100500, Вы писали:
D>>Да, но есть нюанс . Надо вызвать наследника из базы. Я перепишу стартовый пример, чтобы намерения стали понятнее:
Б>Зачем вызывать наследника из базы? Откуда взялось такое ограничение?
Я же всё рассказал. Потратил время на самовыражение. Описал задачу, признал, что возможно мыслю не по go, и спросил как можно избежать копирования.
Б>Если этого ограничения нет, то предложенный код раз все и делает: Б>Algorithm.Run() — это твой DoAction() Б>Step1 — это CheckCondition()
Б>Все поведение, которое может переопределить наследник, нужно выделить в интерфейс (steps). При этом можно предоставить дефолтные steps, чтоб наследнику не пришлось переопределять все шаги. В отдельном классе (не базе, в моем случае Algorithm) прописать любую обобщенную логику Б>
Б>if step1() then step2() else step3()
Б>
Такой код вопросов не вызывает, но, как я и говорил выше есть нюанс — в посте выше всё описано.
D>Вообще непонятно для кого я написал два длинных поста? Неужели даже олды разучились читать? Походу надо на тиктоке в видео вопросы задавать.
Я написал, что в Go я пока даже не джун.
Поэтому предложенные мной умозрительные варианты — чисто из общих соображений.
Тебе не подошли.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Если нет vtable, то эмулируем и её при помощи передачи функции коллбэка в "базу":
package main
import "log"
type Behavor interface {
CheckCondition() bool
DoAction()
}
type defaultBefavior struct {
checkCondition func() bool// Покоцанная эмуляция vtable
}
func (b *defaultBefavior) DoAction() {
if !b.checkCondition() { //Дёрнуть наследника через callbackreturn
}
// Do actual action
// Общий код для всех наследников
}
type stupidBehavior struct {
*defaultBefavior
}
func (b *stupidBehavior) CheckCondition() bool {
//Проверяем внутреннее состояние - разное для всех наследников
// и, в соответсвии с этим, возвращаем результат
log.Println("stupidBefaior CheckCondition")
return true
}
func NewStupidBehavior() Behavor {
b := &stupidBehavior{
defaultBefavior: &defaultBefavior{},
}
b.defaultBefavior.checkCondition = b.CheckCondition // Но пришлось усложнить конструкторreturn b
}
func main() {
st := NewStupidBehavior()
st.DoAction()
}
Здравствуйте, Doom100500, Вы писали:
D>Описал задачу, признал, что возможно мыслю не по go
наверное надо начинать мыслить "по go" ООП утратил во многом актуальность ещё с приходом лямбд. Сейчас не надо писать менеджера, который переопределяет метод doAction и захватывает функциональный объект Checker, который в свою очередь переопределяет метод check. Масло масляное: делатель, который делает, проверяющий проверяет. Можно не плодить сущностей и просто написать функцию:
Здравствуйте, sergii.p, Вы писали:
SP>Здравствуйте, Doom100500, Вы писали:
D>>Описал задачу, признал, что возможно мыслю не по go
SP>наверное надо начинать мыслить "по go"
checker не такой уж и stupid, на самом деле: там структура с внутренним состоянием, по которому и принимаются решния.
SP>Но нет же. В нас вдолбили, что просто написать функцию — это уровень джуна.
package main
type fooInterface interface {
foo()
}
type barInterface interface {
bar(foo fooInterface)
}
type a struct {
}
func (a a) foo() {
println("a.foo")
}
func (a a) bar(foo fooInterface) {
foo.foo()
println("a.bar")
}
type b struct {
a
}
func (b b) foo() {
println("b.foo")
}
func main() {
var a = a{}
a.bar(a)
var b = b{}
b.bar(b)
}
package main
type fooInterface interface {
foo()
}
type barInterface interface {
bar(foo fooInterface)
}
type a struct {
}
func (a a) foo() {
println("a.foo")
}
func (a a) bar(foo fooInterface) {
foo.foo()
println("a.bar")
}
type b struct {
a
}
func (b b) foo() {
println("b.foo")
}
func main() {
var a = a{}
a.bar(a)
var b = b{}
b.bar(b)
}
Здравствуйте, Doom100500, Вы писали:
D>Я же всё рассказал. Потратил время на самовыражение. Описал задачу, признал, что возможно мыслю не по go, и спросил как можно избежать копирования.
Так я же тоже для тебя код написал, "потратил время".
И показал, как избежать копирования.
D>Такой код вопросов не вызывает, но, как я и говорил выше есть нюанс — в посте выше всё описано.
Ты хочешь сделать как привык в других языках, но в го именно так нельзя сделать, это ограничение языка.
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, Doom100500, Вы писали:
D>>Я же всё рассказал. Потратил время на самовыражение. Описал задачу, признал, что возможно мыслю не по go, и спросил как можно избежать копирования.
Б>Так я же тоже для тебя код написал, "потратил время". Б>И показал, как избежать копирования.
И как именно тот код помог мне решить проблему, которую я описал ?
D>>Такой код вопросов не вызывает, но, как я и говорил выше есть нюанс — в посте выше всё описано.
Б>Ты хочешь сделать как привык в других языках, но в го именно так нельзя сделать, это ограничение языка.
Вот я и спрашивал как правильно решить то, что я описывал.
Твой код это не решил никак, а показал то, что я и так знаю (об этом и был второй пост).
Здравствуйте, Doom100500, Вы писали:
D>И как именно тот код помог мне решить проблему, которую я описал ?
Этот код позволяет:
1. Иметь дефолтные реализации (методы)
2. При необходимости переопределять их в наследниках
3. Иметь общую логику, которая использует правильную версию методов (т.е. переопределенные методы)
Здравствуйте, Буравчик, Вы писали:
Б>Здравствуйте, Doom100500, Вы писали:
D>>И как именно тот код помог мне решить проблему, которую я описал ?
Б>Этот код позволяет: Б>1. Иметь дефолтные реализации (методы) Б>2. При необходимости переопределять их в наследниках Б>3. Иметь общую логику, которая использует правильную версию методов (т.е. переопределенные методы)
Это всё я и так освоил, и попросил помощи в конкретном нюансе, и даже объяснил зачем мне это надо.
Б>Разве не это тебе было нужно?
То, что, мне было нужно я описал в первом посте, а когда увидел, что меня не поняли — уточнил во втором.
И ещё я считаю, что экпертное мнение индивидуума, который даже не удосужился прочитать вопрос никакого уважения не достойно.
Я сначала подумал, что ято я тупой, но потом в треде появились люди, которые поняли сразу.
Извини, но в третьий раз объяснять то-же самое человеку, который это даже не прочитает я не буду.