ООП не получается [Решено]
От: Doom100500 Израиль  
Дата: 27.06.23 07:13
Оценка:
Да знаю, знаю, что нет в go ООП. Но есть эмуляция, и я хочу решить задачу, которая на плюсах решается тривально привычно:

#include <iostream>
struct FooBar {
    virtual void Foo() = 0;
    virtual void Bar() = 0;
};

// Этот класс имплементирует интерфейс методами по умолчанию 
class foo : public FooBar {
public:
    // Этот метод будет переопределяться в наследниках
    void Foo() override {
        std::cout << "foo::Foo" << std::endl;
    }

    void Bar() override {
        Foo(); // Ожидается, что здесь будет вызван метод наследника
        std::cout << "foo::Bar" << std::endl;
    }
};

//  А вот и наследник с переопределённым методом
class bar : public foo {
public:
    void Foo() override {
        std::cout << "bar::Foo" << std::endl;
    }
};
 
int main() {
    FooBar* fb = new bar();
    fb->Bar();
    delete fb;
    return 0;
}


И всё работает как и ожидалось:

bar::Foo
foo::Bar


https://ideone.com/1Lf02l

Теперь go:

package main

import "log"

type FooBar interface {
    Foo()
    Bar()
}

type foo struct{}

func (*foo) Foo() {
    log.Println("foo::Foo")
}

func (f *foo) Bar() {
    f.Foo() // Ожидается, что здесь будет вызван метод "наследника"
                // Но похоже, что инфорация о типе здесь безвозвратно утеряна.
    log.Println("foo:Bar")
}

type bar struct{ *foo }

func (*bar) Foo() {
    log.Println("bar::Foo")
}

func NewFooBar() FooBar {
    return &bar{&foo{}}
}

func main() {
    fb := NewFooBar()
    fb.Bar()
}


Но нет:
2009/11/10 23:00:00 foo::Foo
2009/11/10 23:00:00 foo:Bar

Program exited.


https://go.dev/play/p/23zNfIZ3Swq

И как же быть?
Пока приходится копировать реализацию Bar() в каждом "наследнике". А это ну... моветон какой-то. Должен же быть способ, не может же быть что так тупо всё и задумывалось.

Гугление проводит к таким ресультатам:
https://www.reddit.com/r/golang/comments/28garc/what_is_the_preferred_way_to_provide_a_default/

Но там нет вызова методов, который я хочу.
Спасибо за внимание
Отредактировано 28.06.2023 8:15 Doom100500 . Предыдущая версия . Еще …
Отредактировано 27.06.2023 7:13 Doom100500 . Предыдущая версия .
Re: ООП не получается
От: LaptevVV Россия  
Дата: 27.06.23 10:08
Оценка:
Наследования в Го не принципиально.
Есть шаблоны
Есть рефлексия.
Наверное, с их помощью можно.
Как — не подскажу, я в Го еще даже не джун...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: ООП не получается
От: Doom100500 Израиль  
Дата: 27.06.23 10:42
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>Наследования в Го не принципиально.

LVV>Есть шаблоны
LVV>Есть рефлексия.
LVV>Наверное, с их помощью можно.
LVV>Как — не подскажу, я в Го еще даже не джун...

С шаблонами дженериками не выйдет. Во-первых нет дженерик методов (может быть либо type, либо func(), но не func()()). Во вторых:
func (f *foo) Bar() {
    f.Foo() // Ожидается, что здесь будет вызван метод "наследника"
                // Но похоже, что инфорация о типе здесь безвозвратно утеряна.
    log.Println("foo:Bar")
}


f *foo не помнит, что он был *bar, поэтому и возникла проблема. Из-за той-же причины и рефлекцией особо не нарефлексируешь. Не говоря уже, что решать такую задачу рефлексией — это вообще фу. Лучше уж копипастить и ждать go2, где предлагаются контракты с реализацией по умолчанию. Но по опыту с дженериками ждать этих введений смысла особо нет, т.к. похоже, что всё равно сделают через анус, и особо с этим ничего и не сделаешь.
Спасибо за внимание
Re: ООП не получается
От: Буравчик Россия  
Дата: 27.06.23 13:37
Оценка:
Здравствуйте, Doom100500, Вы писали:

D>Но там нет вызова методов, который я хочу.


Ты хочешь иметь дефолтную реализацию, и переопределить ее в наследниках?

Примерно так можно:
package main

import "log"

type Steps interface {
    Step1()
    Step2()
}

type BaseSteps struct{}

var _ Steps = &BaseSteps{} // assert implements

func (b BaseSteps) Step1() {
    log.Println("base step 1")
}

func (b BaseSteps) Step2() {
    log.Println("base step 2")
}

type Algorithm struct {
    steps Steps
}

func (a Algorithm) Run() {
    a.steps.Step1()
    a.steps.Step2()
}

type ConcreteSteps struct { // переопределит step1, но оставит дефолтный step2
    BaseSteps
}

var _ Steps = &ConcreteSteps{} // assert implements

func (b ConcreteSteps) Step1() {
    log.Println("concrete step 1")
}

func main() {
    Algorithm{steps: ConcreteSteps{}}.Run()
}


Выводит ожидаемые

2009/11/10 23:00:00 concrete step 1
2009/11/10 23:00:00 base step 2


https://go.dev/play/p/OlO-l5PjXgT

P.S. И, да, в go вообще много странного. Лишь бы не как у других
Best regards, Буравчик
Re[2]: ООП не получается
От: Doom100500 Израиль  
Дата: 27.06.23 15:17
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Ты хочешь иметь дефолтную реализацию, и переопределить ее в наследниках?


Да, но есть нюанс . Надо вызвать наследника из базы. Я перепишу стартовый пример, чтобы намерения стали понятнее:

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
    // Общий код для всех наследников
}

Но так в go нельзя. Но что-то похожее обещали в go2 (но это, как всегда, неточно):
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md

Зато можно вызывать базу из наследников. Сейчас у меня работет такой код:
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-х наследников. Но потом, когда месяца через два стал пилить четвёртого, мой внутренний перфекционист взбунтовался — мало ли что я забуду скопировать.

Возможно я что-то делаю не так, но я так и не смог придумать как избавиться от копирования. Всё ночь снились дженерики и даже генерация кода.
Сдаётся мне, что должен быть какой-то идиоматический путь избежать такой копипасты, но я его не вижу...
Спасибо за внимание
Отредактировано 27.06.2023 15:23 Doom100500 . Предыдущая версия . Еще …
Отредактировано 27.06.2023 15:18 Doom100500 . Предыдущая версия .
Отредактировано 27.06.2023 15:17 Doom100500 . Предыдущая версия .
Re: ООП не получается
От: LaptevVV Россия  
Дата: 27.06.23 17:01
Оценка: -1
https://www.swig.org/Doc3.0/Go.html
Посмотри тут.
Это С++ + Go
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[3]: ООП не получается
От: Буравчик Россия  
Дата: 27.06.23 17:26
Оценка: -1
Здравствуйте, Doom100500, Вы писали:

D>Да, но есть нюанс . Надо вызвать наследника из базы. Я перепишу стартовый пример, чтобы намерения стали понятнее:


Зачем вызывать наследника из базы? Откуда взялось такое ограничение?

Если этого ограничения нет, то предложенный код раз все и делает:
Algorithm.Run() — это твой DoAction()
Step1 — это CheckCondition()

Все поведение, которое может переопределить наследник, нужно выделить в интерфейс (steps). При этом можно предоставить дефолтные steps, чтоб наследнику не пришлось переопределять все шаги. В отдельном классе (не базе, в моем случае Algorithm) прописать любую обобщенную логику
if step1() then step2() else step3()
Best regards, Буравчик
Re[4]: ООП не получается
От: Doom100500 Израиль  
Дата: 28.06.23 05:36
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, Doom100500, Вы писали:


D>>Да, но есть нюанс . Надо вызвать наследника из базы. Я перепишу стартовый пример, чтобы намерения стали понятнее:


Б>Зачем вызывать наследника из базы? Откуда взялось такое ограничение?


Я же всё рассказал. Потратил время на самовыражение. Описал задачу, признал, что возможно мыслю не по go, и спросил как можно избежать копирования.

Б>Если этого ограничения нет, то предложенный код раз все и делает:

Б>Algorithm.Run() — это твой DoAction()
Б>Step1 — это CheckCondition()

Б>Все поведение, которое может переопределить наследник, нужно выделить в интерфейс (steps). При этом можно предоставить дефолтные steps, чтоб наследнику не пришлось переопределять все шаги. В отдельном классе (не базе, в моем случае Algorithm) прописать любую обобщенную логику

Б>
Б>if step1() then step2() else step3()
Б>


Такой код вопросов не вызывает, но, как я и говорил выше есть нюанс — в посте выше всё описано.
Спасибо за внимание
Re[2]: ООП не получается
От: Doom100500 Израиль  
Дата: 28.06.23 05:42
Оценка:
Здравствуйте, LaptevVV, Вы писали:

LVV>https://www.swig.org/Doc3.0/Go.html

LVV>Посмотри тут.
LVV>Это С++ + Go

Мне не надо смешивать C++ и go.

Вообще непонятно для кого я написал два длинных поста? Неужели даже олды разучились читать? Походу надо на тиктоке в видео вопросы задавать.
Спасибо за внимание
Отредактировано 28.06.2023 5:43 Doom100500 . Предыдущая версия .
Re[3]: ООП не получается
От: LaptevVV Россия  
Дата: 28.06.23 06:25
Оценка:
D>Вообще непонятно для кого я написал два длинных поста? Неужели даже олды разучились читать? Походу надо на тиктоке в видео вопросы задавать.
Я написал, что в Go я пока даже не джун.
Поэтому предложенные мной умозрительные варианты — чисто из общих соображений.
Тебе не подошли.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: ООП не получается [Решено]
От: Doom100500 Израиль  
Дата: 28.06.23 07:25
Оценка: 1 (1) -1
Всё, придумал.

Если нет 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() { //Дёрнуть наследника через callback
        return
    }
    // 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()
}


https://go.dev/play/p/e6lAjK9hPwx

По большому счёту шило на мыло, но так моему внутреннему перфекционисту стало немного лучше.
Спасибо за внимание
Отредактировано 28.06.2023 8:16 Doom100500 . Предыдущая версия .
Re[5]: ООП не получается
От: sergii.p  
Дата: 28.06.23 08:17
Оценка: +1
Здравствуйте, Doom100500, Вы писали:

D>Описал задачу, признал, что возможно мыслю не по go


наверное надо начинать мыслить "по go" ООП утратил во многом актуальность ещё с приходом лямбд. Сейчас не надо писать менеджера, который переопределяет метод doAction и захватывает функциональный объект Checker, который в свою очередь переопределяет метод check. Масло масляное: делатель, который делает, проверяющий проверяет. Можно не плодить сущностей и просто написать функцию:

func doAction(check CheckFunc){
    if(!check()){ return }
}

...

func getStupidChecker() CheckFunc {
    return func() bool {
        return true;
    };
}

...

doChecker(getStupidAction())


Но нет же. В нас вдолбили, что просто написать функцию — это уровень джуна.
Re[6]: ООП не получается
От: Doom100500 Израиль  
Дата: 28.06.23 08:22
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>Здравствуйте, Doom100500, Вы писали:


D>>Описал задачу, признал, что возможно мыслю не по go


SP>наверное надо начинать мыслить "по go"


Так я и не против

SP>
SP>func doAction(check CheckFunc){
SP>    if(!check()){ return }
SP>}

SP>...

SP>func getStupidChecker() CheckFunc {
SP>    return func() bool {
SP>        return true;
SP>    };
SP>}

SP>...

SP>doChecker(getStupidAction())

SP>


checker не такой уж и stupid, на самом деле: там структура с внутренним состоянием, по которому и принимаются решния.

SP>Но нет же. В нас вдолбили, что просто написать функцию — это уровень джуна.


Мне это не вдалбливали.

А вообще решение найдено, и оно меня устроило: https://go.dev/play/p/e6lAjK9hPwx
Спасибо за внимание
Re[6]: ООП не получается
От: Doom100500 Израиль  
Дата: 28.06.23 08:25
Оценка:
Здравствуйте, sergii.p, Вы писали:


SP>Масло масляное: делатель, который делает, проверяющий проверяет.


Код в посте минимизирован и абстрагирован. Имена ни о чём не говорят. Это псвдо код для описания вопроса.
Спасибо за внимание
Re: ООП не получается [Решено]
От: vsb Казахстан  
Дата: 28.06.23 08:31
Оценка: +1
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)
}
Re[2]: ООП не получается [Решено]
От: Doom100500 Израиль  
Дата: 28.06.23 08:42
Оценка:
Здравствуйте, vsb, Вы писали:

  Code
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)
}


Тоже вариант. Но вот это:

vsb>a.bar(a)


  Выглядит не очень.
Выглядит как идеоматичный говнокод.
Спасибо за внимание
Отредактировано 28.06.2023 8:48 Doom100500 . Предыдущая версия . Еще …
Отредактировано 28.06.2023 8:43 Doom100500 . Предыдущая версия .
Re[5]: ООП не получается
От: Буравчик Россия  
Дата: 28.06.23 11:38
Оценка:
Здравствуйте, Doom100500, Вы писали:

D>Я же всё рассказал. Потратил время на самовыражение. Описал задачу, признал, что возможно мыслю не по go, и спросил как можно избежать копирования.


Так я же тоже для тебя код написал, "потратил время".
И показал, как избежать копирования.

D>Такой код вопросов не вызывает, но, как я и говорил выше есть нюанс — в посте выше всё описано.


Ты хочешь сделать как привык в других языках, но в го именно так нельзя сделать, это ограничение языка.
Best regards, Буравчик
Re[6]: ООП не получается
От: Doom100500 Израиль  
Дата: 28.06.23 11:48
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, Doom100500, Вы писали:


D>>Я же всё рассказал. Потратил время на самовыражение. Описал задачу, признал, что возможно мыслю не по go, и спросил как можно избежать копирования.


Б>Так я же тоже для тебя код написал, "потратил время".

Б>И показал, как избежать копирования.

И как именно тот код помог мне решить проблему, которую я описал ?

D>>Такой код вопросов не вызывает, но, как я и говорил выше есть нюанс — в посте выше всё описано.


Б>Ты хочешь сделать как привык в других языках, но в го именно так нельзя сделать, это ограничение языка.


Вот я и спрашивал как правильно решить то, что я описывал.
Твой код это не решил никак, а показал то, что я и так знаю (об этом и был второй пост).
Спасибо за внимание
Re[7]: ООП не получается
От: Буравчик Россия  
Дата: 28.06.23 12:44
Оценка:
Здравствуйте, Doom100500, Вы писали:

D>И как именно тот код помог мне решить проблему, которую я описал ?


Этот код позволяет:
1. Иметь дефолтные реализации (методы)
2. При необходимости переопределять их в наследниках
3. Иметь общую логику, которая использует правильную версию методов (т.е. переопределенные методы)

Разве не это тебе было нужно?
Best regards, Буравчик
Re[8]: ООП не получается
От: Doom100500 Израиль  
Дата: 28.06.23 13:05
Оценка: -1
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, Doom100500, Вы писали:


D>>И как именно тот код помог мне решить проблему, которую я описал ?


Б>Этот код позволяет:

Б>1. Иметь дефолтные реализации (методы)
Б>2. При необходимости переопределять их в наследниках
Б>3. Иметь общую логику, которая использует правильную версию методов (т.е. переопределенные методы)

Это всё я и так освоил, и попросил помощи в конкретном нюансе, и даже объяснил зачем мне это надо.

Б>Разве не это тебе было нужно?


То, что, мне было нужно я описал в первом посте, а когда увидел, что меня не поняли — уточнил во втором.
И ещё я считаю, что экпертное мнение индивидуума, который даже не удосужился прочитать вопрос никакого уважения не достойно.

Я сначала подумал, что ято я тупой, но потом в треде появились люди, которые поняли сразу.

Извини, но в третьий раз объяснять то-же самое человеку, который это даже не прочитает я не буду.
Спасибо за внимание
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.