Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 27.04.24 07:12
Оценка:
Добрый день!

Недавно перечитывал книгу Бертрана Мейера "Объектно-ориентированное конструирование программных систем".
Там все функции классов разделяются на три категории: "конструктор", "аксессор" и "модификатор".

А как же быть с функциями, которые выполняют отложенную\ленивую загрузку\инициализацию?

    get
    {
        if (_myWidget == null)
        {
            _myWidget = Widget.Load(_myWidgetID);
        }

        return _myWidget;
    }


Получается, что это "неправильные" функции?
И ими нельзя пользоваться?

Ведь действительно, когда смотришь на название такой функции, она вводит в заблуждение.
Может быть есть смысл разделить ее на две функции?

Типа так:
    prepare
    {
        if (_myWidget == null)
        {
            _myWidget = Widget.Load(_myWidgetID);
        }
    }
    get
    {
        return _myWidget;
    }


И в клиентском коде везде явно вызывать две функции при необходимости:
    object.prepare();
    object.get();


Хотя, так наверно тоже неправильно, потому что клиент и не должен ничего знать про "отложенную" инициализацию.

Как же быть?
Как лучше делать в таких случаях?
Re: Мутные функции отложенной (ленивой) загрузки\инициализации
От: Maniacal Россия  
Дата: 27.04.24 07:41
Оценка:
Здравствуйте, zelenprog, Вы писали:


Z>А как же быть с функциями, которые выполняют отложенную\ленивую загрузку\инициализацию?


Z>
Z>    get
Z>    {
Z>        if (_myWidget == null)
Z>        {
Z>            _myWidget = Widget.Load(_myWidgetID);
Z>        }

Z>        return _myWidget;
Z>    }
Z>


Ну, это же практически синглтон, только не обязательно глобальный, как ему положено быть.
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 27.04.24 08:18
Оценка:
Здравствуйте, Maniacal, Вы писали:

M>Ну, это же практически синглтон, только не обязательно глобальный, как ему положено быть.


Возможно, такая реализация Синглтона — это тоже не правильно?

В моем вопросе речь идет о "теории", на которой должны строиться классы\архитектура.
Если теорию нарушать — то это рано или поздно приведет к проблемам.
Вот такая реализация Синглтона — тоже ведь нарушает теорию.
Re: Мутные функции отложенной (ленивой) загрузки\инициализации
От: Буравчик Россия  
Дата: 27.04.24 08:31
Оценка: +1
Здравствуйте, zelenprog, Вы писали:

Z>Недавно перечитывал книгу Бертрана Мейера "Объектно-ориентированное конструирование программных систем".

Z>Там все функции классов разделяются на три категории: "конструктор", "аксессор" и "модификатор".

Z>А как же быть с функциями, которые выполняют отложенную\ленивую загрузку\инициализацию?

Z>Получается, что это "неправильные" функции?
Z>И ими нельзя пользоваться?

Нет, получается, что у Мейера неправильная классификация. Либо на нее надо смотреть шире.

Можно (и стоит) учитывать только внешний API класс/функции. Нам совсем не интересно, что происходит под капотом.
Тогда эта функция является "асессором"

Но встречаются совсем странные функции, и при этому очень полезные, get_or_create()
Куда такие относить — еще более непонятно в такой классификации
Best regards, Буравчик
Re[3]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: Maniacal Россия  
Дата: 27.04.24 08:51
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>В моем вопросе речь идет о "теории", на которой должны строиться классы\архитектура.

Z>Вот такая реализация Синглтона — тоже ведь нарушает теорию.

Синглтон это вообще надстройка для отказа от глобальных переменных без отказа от них. А глобальные переменные в классическом случае = неправильная архитектура и костыли.
Re: Мутные функции отложенной (ленивой) загрузки\инициализации
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 27.04.24 08:53
Оценка:
Здравствуйте, zelenprog, Вы писали:


Z>Добрый день!


Z>Недавно перечитывал книгу Бертрана Мейера "Объектно-ориентированное конструирование программных систем".

Z>Там все функции классов разделяются на три категории: "конструктор", "аксессор" и "модификатор".

Z>А как же быть с функциями, которые выполняют отложенную\ленивую загрузку\инициализацию?

Z>Получается, что это "неправильные" функции?
Z>И ими нельзя пользоваться?
Да


Z>Ведь действительно, когда смотришь на название такой функции, она вводит в заблуждение.

Z>Может быть есть смысл разделить ее на две функции?
Z>И в клиентском коде везде явно вызывать две функции при необходимости:
Нет, это называется "двухшаговая инициализация" — источник многих ошибок.

Z>Хотя, так наверно тоже неправильно, потому что клиент и не должен ничего знать про "отложенную" инициализацию.

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

Есть общие правила: "гет-свойства не должны бросать исключения", "конструкторы не должны бросать исключения" (в C++ это просто ошибка)


Z>Как же быть?

Z>Как лучше делать в таких случаях?
Решение простое — factory method
class C {
    protected C(Widget widget) {
        this.widget = widget;
    }

    static C? FromWidgetId(it widgetId)
    {
         var w = Widget.Load(widgetId);
         if w != null? new C(w) : null;
    }
}
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 27.04.24 10:43
Оценка:
Б>Нет, получается, что у Мейера неправильная классификация.

Ну в книге все довольно логично.
Да и на практике все функции укладываются в эту классификацию. Кроме функций отложенной инициализации.

Б>... Либо на нее надо смотреть шире.


Шире — это как?

Б>Можно (и стоит) учитывать только внешний API класс/функции. Нам совсем не интересно, что происходит под капотом.


Так ведь его классификация и относится именно к внешнему описанию функции.
Там у него изначально класс описывается как АТД — абстрактный тип данных, реализация которого неизвестна.

Б>Тогда эта функция является "асессором"


Получается, что ее нельзя так назвать, так как она изменяет сам объект.

Б>Но встречаются совсем странные функции, и при этому очень полезные, get_or_create()

Б>Куда такие относить — еще более непонятно в такой классификации

Эта странная — "отложенное" создание, по сути тоже самое что и отложенная инициализация.
Re[4]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 27.04.24 10:44
Оценка:
Здравствуйте, Maniacal, Вы писали:

M>Синглтон это вообще надстройка для отказа от глобальных переменных без отказа от них. А глобальные переменные в классическом случае = неправильная архитектура и костыли.


Это конечно оффтоп, но все-таки интересно...
А как построить правильную архитектуру без глобальных переменных?
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициали
От: zelenprog  
Дата: 27.04.24 10:54
Оценка:
Здравствуйте, gandjustas, Вы писали:

Z>>Ведь действительно, когда смотришь на название такой функции, она вводит в заблуждение.

Z>>Может быть есть смысл разделить ее на две функции?
Z>>И в клиентском коде везде явно вызывать две функции при необходимости:
G>Нет, это называется "двухшаговая инициализация" — источник многих ошибок.

А как же быть, если действительно какой-то ресурс нужен в зависимости от некоторых условий?
А условия могут сформироваться только в процессе выполнения программы.

G>Проблема не в этом, а в том, что "инициализация", как и любое другое действие, которое может завершиться неуспехом, должно быть явно видно в исходниках.


В исходниках каких? В исходниках клиента?
Просто в исходниках реализации это и так видно.

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


G>Решение простое — factory method

G>
G>class C {
G>    protected C(Widget widget) {
G>        this.widget = widget;
G>    }

G>    static C? FromWidgetId(it widgetId)
G>    {
G>         var w = Widget.Load(widgetId);
G>         if w != null? new C(w) : null;
G>    }
G>}
G>


Хм... Но по сути это ведь тоже самое.
В клиентском коде как будет выглядеть вызов этой фабрики?
Отредактировано 27.04.2024 11:10 zelenprog . Предыдущая версия .
Re[3]: Мутные функции отложенной (ленивой) загрузки\инициали
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 27.04.24 11:27
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>А как же быть, если действительно какой-то ресурс нужен в зависимости от некоторых условий?

Z>А условия могут сформироваться только в процессе выполнения программы.
Как написал выше

G>>Проблема не в этом, а в том, что "инициализация", как и любое другое действие, которое может завершиться неуспехом, должно быть явно видно в исходниках.


Z>В исходниках каких? В исходниках клиента?

В вызывающем коде

Z>С другой стороны, такая "неявная" инициализация изменяет используемый клиентом объект, но клиент про это ничего не знает.

Это верно только когда инициализация не может быть не успешна, а это далеко не всегда так.

Z>Хм... Но по сути это ведь тоже самое.

Не совсем

Z>В клиентском коде как будет выглядеть вызов этой фабрики?

Не то же самое, так как метод может вернуть не только ссылку на объект, но и какие-то сложные значения.
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 27.04.24 12:24
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Есть общие правила: "гет-свойства не должны бросать исключения", "конструкторы не должны бросать исключения" (в C++ это просто ошибка)

Что делать если объект не сконструировался? Методы valid и прочие проверки на NULL худшее что может быть, а потом его ещё и уничтожить надо, а если в деструкторе как-то хитро уничтожается всё, то и ожидание чего-то может быть, или другие проблемы.
Sic luceat lux!
Re: Мутные функции отложенной (ленивой) загрузки\инициализации
От: scf  
Дата: 27.04.24 12:53
Оценка:
Здравствуйте, zelenprog, Вы писали:


Z>А как же быть с функциями, которые выполняют отложенную\ленивую загрузку\инициализацию?


Z>
Z>    get
Z>    {
Z>        if (_myWidget == null)
Z>        {
Z>            _myWidget = Widget.Load(_myWidgetID);
Z>        }

Z>        return _myWidget;
Z>    }
Z>


Как правило, это ошибка проектирования. Хорошо написанный софт проходит через две фазы: фазу инициализации, когда создаются объекты и устанавливаются зависимости между ними, и фазу выполнения.
Бывает ленивая инициализация, когда инициализирующую функцию оборачивают в кеш, но эта ленивость не должна быть выражена в коде напрямую, а завернута в DI фреймворк, монады или другой неинтрузивный механизм.

Синглтоны делают ленивую инициализацию неуправляемой, т.к. а) зависимости синглотона неявные и могут быть вызваны из любого места кода б) нельзя контролировать ленивость-неленивость.
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 27.04.24 13:32
Оценка:
Здравствуйте, scf, Вы писали:

scf>Как правило, это ошибка проектирования ...


Значит, Бертран Мейер прав.

scf>Бывает ленивая инициализация, когда инициализирующую функцию оборачивают в кеш, но эта ленивость не должна быть выражена в коде напрямую, а завернута в DI фреймворк, монады или другой неинтрузивный механизм.


А разве "обернутость" ленивой инициализации исправит ошибку проектирования?

scf>Синглтоны делают ленивую инициализацию неуправляемой, т.к. а) зависимости синглотона неявные и могут быть вызваны из любого места кода б) нельзя контролировать ленивость-неленивость.


Как надо сделать с точки зрения правильного проектирования?
Re[3]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 27.04.24 13:53
Оценка:
Здравствуйте, Kernan, Вы писали:

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


G>>Есть общие правила: "гет-свойства не должны бросать исключения", "конструкторы не должны бросать исключения" (в C++ это просто ошибка)

K>Что делать если объект не сконструировался?
Если в конструкторе проблема, то должно быть исключение. Других вариантов нет.
Если метод, возвращающий объект, то можно сделать опциональный тип, вернуть null итд
Re[3]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: scf  
Дата: 27.04.24 14:59
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>А разве "обернутость" ленивой инициализации исправит ошибку проектирования?


Да. Т.к. такая ленивость становится не фичей кода (которая имеет некоторую стоимость реализации и поддержки), а фичей платформы.

Z>Как надо сделать с точки зрения правильного проектирования?


Серебряной пули, к сожалению, пока не изобрели и ответ зависит от ЯП, библиотек и навыков программирования. Но есть общее правило: сервисные классы должны быть иммутабельными и должны получать свои зависимости через конструктор. Как именно передавать — здесь уже возможны варианты. Можно отказаться от ленивости и вызывать конструкторы руками, можно использовать любимый фреймворк на аннотациях, можно lazy values прикрутить, а можно всё завернуть в монады.
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: SkyDance Земля  
Дата: 27.04.24 16:26
Оценка: 5 (1) +4
scf>Бывает ленивая инициализация, когда инициализирующую функцию оборачивают в кеш, но эта ленивость не должна быть выражена в коде напрямую, а завернута в DI фреймворк, монады или другой неинтрузивный механизм.

Ага, чтоб уж наверняка был happy debugging. Порой очень хочется заставить всех любителей наворотить сложность на пустом месте заниматься отладкой чужого софта, написанного примерно так же.
Re[3]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: Sharov Россия  
Дата: 28.04.24 11:03
Оценка:
Здравствуйте, zelenprog, Вы писали:

Z>В моем вопросе речь идет о "теории", на которой должны строиться классы\архитектура.

Z>Если теорию нарушать — то это рано или поздно приведет к проблемам.
Z>Вот такая реализация Синглтона — тоже ведь нарушает теорию.

Это своего рода отложенный конструктор.
Кодом людям нужно помогать!
Re[2]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: sergii.p  
Дата: 29.04.24 10:45
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Есть общие правила: "гет-свойства не должны бросать исключения", "конструкторы не должны бросать исключения" (в C++ это просто ошибка)


впервые слышу. Вообще в языках с исключениями это стандартная практика — кинуть исключение из конструктора. Из деструктора да — бросать не стоит, но из конструктора — на усмотрение программиста.


G>Решение простое — factory method

G>
G>class C {
G>    protected C(Widget widget) {
G>        this.widget = widget;
G>    }

G>    static C? FromWidgetId(it widgetId)
G>    {
G>         var w = Widget.Load(widgetId);
G>         if w != null? new C(w) : null;
G>    }
G>}
G>


но тогда ленивость ушла в никуда. Я ведь хочу получить объект С без создания widget.
Re[4]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 29.04.24 13:14
Оценка:
Z>>А разве "обернутость" ленивой инициализации исправит ошибку проектирования?

scf>Да. Т.к. такая ленивость становится не фичей кода (которая имеет некоторую стоимость реализации и поддержки), а фичей платформы.


А какая разница?
Объясни пожалуйста подробнее.
Почему ленивость как фича кода хуже, чем фича платформы с точки зрения проектирования?
Ведь клиент в любом из этих случаев не узнает про эту ленивость. А именно это незнание клиента и является кривым проектированием.

Z>>Как надо сделать с точки зрения правильного проектирования?


scf>... Можно отказаться от ленивости и вызывать конструкторы руками ...

Где вызвать? В коде клиента?
Тогда это нарушает инкапсуляцию.
Re[3]: Мутные функции отложенной (ленивой) загрузки\инициализации
От: zelenprog  
Дата: 29.04.24 13:18
Оценка:
scf>>Бывает ленивая инициализация, когда инициализирующую функцию оборачивают в кеш, но эта ленивость не должна быть выражена в коде напрямую, а завернута в DI фреймворк, монады или другой неинтрузивный механизм.

SD>Ага, чтоб уж наверняка был happy debugging. Порой очень хочется заставить всех любителей наворотить сложность на пустом месте заниматься отладкой чужого софта, написанного примерно так же.


А как сделать, чтобы не наворачивать сложности?
Объясни плиз.
Мне очень хочется разобраться в этом вопросе.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.