Войти
ФлеймФорумПрограммирование

Почему в ООП языках есть объявление подклассов, но нет объявления надклассов? (4 стр)

Страницы: 1 2 3 4 5 Следующая »
#45
19:53, 18 янв. 2020

Dmitry_Milk
> Скажем, упомянутая выше сериализация.
Откуда вызывающая сторона знает, как правильно сериализовать объект, чтобы не нарушить его внутреннюю логику? Откуда вызывающая сторона вообще знает, какие есть поля у объекта?


#46
(Правка: 20:12) 20:12, 18 янв. 2020

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

Реализовано (С++) как ты и написал, через виртуальный метод сериализации, заполняющий байтовый буфер. Но в результате получается в методах сериализации нагромождения IF-ов от "назначения сериализации (сеть/БД)" а также от версии протокола. За каким хреном типы этих объектов должны знать про какие-то "версии сетевых протоколов", когда их назначение вовсе не сетевое?

#47
21:08, 18 янв. 2020

Dmitry_Milk
> Реализовано (С++) как ты и написал, через виртуальный метод сериализации,
> заполняющий байтовый буфер. Но в результате получается в методах сериализации
> нагромождения IF-ов от "назначения сериализации (сеть/БД)" а также от версии
> протокола.
а не проще для этого друзей использовать? т.к. сериализация это как раз задача для друзей?
тогда виртуальные методы нафик не нужны

#48
22:09, 18 янв. 2020

skalogryz
> а не проще для этого друзей использовать?

Наверное можно было бы комбинацию из виртуальной функции и друзей. Чтоб виртуальный метод Serialize внутри себя вызывал соответствующую дружественную перегруженную функцию из файла, ответстветственного за сериализации. Но уже что сделано, то сделано, да и не я разработчик.

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

#49
23:13, 18 янв. 2020

Dmitry_Milk
> Нет, конечно можно сказать - у всех сериализуемых типов должен быть метод
> Serialize, выделяющий/заполняющий байтовый буфер, который потом можно
> использовать куда угодно - в файл, на консоль, в сеть... Но если тебе в файл
> надо "как быстрее", в сеть - "как компактнее", а на консоль - "как понятнее
> человеку", то такой подход начинает хромать.
Я вовсе не утверждаю, что такое решение здесь подойдёт, но, возможно, оно подарит полезные мысли.
Кратко - вместо того, чтобы сериализовать напрямую объект→поток; мы формализуем некоторую очень простую промежуточную форму (у меня - подобие json, только ещё проще); и затем реализуем два раздельных, независимых этапа преобразования - объект→форма и форма→поток.

#50
23:48, 18 янв. 2020

Dmitry_Milk
> Тогда алгебраический тип и паттерн-матчинг.
Не совсем понимаю о чем ты и как это стыкуется с темой.
Пример можно?

pahaa
> ну так я и прошу конкретный пример

class Something
class Boolean : Something
class Number  : Something
class String  : Something

something = new String

json.Serialize(something)
xml.Serialize(something)
yaml.Serialize(something)
Здесь данные (Something и остальная иерархия) и алгоритмы их обработки (json, xml, yaml) разделены.
Вместо something.ToJSON(), something.ToXML(), something.ToYAML() используем json.Serialize(), xml.Serialize(something), yaml.Serialize(something).
#51
0:56, 19 янв. 2020

Great V.
> Здесь данные (Something и остальная иерархия) и алгоритмы их обработки (json,
> xml, yaml) разделены.
Как раз наоборот, здесь большая связанность. Алгоритмы не удастся применить в программе, для которой не определён любой из подклассов Something.
Если сделать так:

json.Serialize(something.getDataProvider())
то эта проблема уйдёт. И станет ровно так, как я и написал в начале.
Но мы вообще вроде о полноте группы классов разговаривали. Этот пример точно об этом был?
#52
(Правка: 6:48) 6:37, 19 янв. 2020

pahaa
> json.Serialize(something.getDataProvider())
чё-т... как-то... хм...

вариант №1.
Класс сам себя сереализует в конкретный носитель данных.

something.Serialize(json);
аналогичный вариант:
something.ToJson();
+ сравнительная простота и ясность. (т.к. отсуствуют сторонние сущности)
- привязка к конкретному типу данных

вариант №2
Класс сам себя сериализует, в абстрактный носитель данных (i.e. ISerializer)

something.Serialize(json.getSerializer());
+ возможность расширяемости носителей
- дополнительная сущность ISerializer

вариант №3
Класс предлагает, абстрактный источник данных, с которым умеет работать носитель данных

json.Serialize(something.getDataProvider())

вариант №4
Дополнительный класс, который умеет в конкертый источник данных

somethingJsoner.Serialize(json, something);
- somethingJsoner должен знать о внутренеей структуре something. При изменении something, можно и забыть поменять somethingJsoner

вариант №5
Дополнительный класс, который умеет в абсрактный источник данных

somethingDataProvider.Serialize(json.getSerializer(), something);

Dmitry_Milk
> Но в любом случае, уже в красивую схему разграничения полномочий не
> укладывается - все равно сторонний (хоть и дружественный) код должен быть в
> курсе внутреннего устройства и иметь соответствующий доступ.
всё то, что может/должно сериализоваться должно быть в public
всё то, что не должно сериализоваться может быть в protected/private

это решает проблему о "внутреннем устройстве". Т.к. всегда можно проверить, все ли необходимые данные сериализуются.
Что ещё интереснее, написание класса для сериализации можно автоматизировать (т.е. тупо проходить по public секции класса)

#53
7:57, 19 янв. 2020

pahaa
Ты не шаришь, getDataProvider вообще не нужен. Но даже если в нем и будет какая-нибудь необходимость, json.Serialize под капотом и сам сможет вызвать его, something.getDataProvider() лишний. Кроме того, json.Serialize сам может выбирать те классы из иерархии Something, которые нужно сериализовать.
В связности, от которой ты так убегаешь, нет ничего плохого. Если обойтись без нее то получится еще одно промежуточное звено в виде getDataProvider. И вместо того чтобы JSON знал о тех Something которые ему интересны, Something должны будут уметь превращаться в этот самый getDataProvider. А в общем случае getDataProvider просто будет дублировать всю иерархию Something и мы получим бесполезное дублирование кода.
Но это все оффтоп. Суть примеры была в том, что виртуальные методы это всего лишь инструмент. И в некоторых случаях есть более удобные инструменты.
Но как ты заметил и это в определенной мере оффтоп. Поведение, которого я добивался заключалось в том, чтобы строго ограничить иерархию. Т.е. чтобы мы знали что у класса A есть определенное количество производных классов. И чтобы существовали гарантии что другие классы от него унаследовать уже не получиться. И подход A supers X, Y, Z предоставляет такую гарантию (по крайней мере если после этого мы уже не можем написать W extends A).

#54
11:48, 19 янв. 2020

Great V.
> Не совсем понимаю о чем ты и как это стыкуется с темой.

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

Модуль с типом Rectangle

struct Rectangle {
    left: f64,
    top: f64,
    right: f64,
    bottom: f64,
}

Модуль с типом Circle

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

Модуль с типом Triangle

struct Triangle {
    x1: f64,
    y1: f64,
    x2: f64,
    y2: f64,
    x3: f64,
    y3: f64,
}

Модуль с "суперклассом" Shape

enum Shape {
    Circle(Circle),
    Rectangle(Rectangle),
    Triangle(Triangle),
}

Модуль, ответственный за сериализацию

fn serialize_shape(f: &Shape) -> String {
    match f {
        Shape::Circle(c) => {
            return format!("S:C({},{},{})", c.x, c.y, c.radius)
        },
        Shape::Triangle(t) => {
            return format!("S:T({},{},{},{},{},{})", t.x1, t.y1, t.x2, t.y2, t.x3, t.y3)
        }
        Shape::Rectangle(r) => {
            return format!("S:R({},{},{},{})", r.left, r.top, r.right, r.bottom)
        }
    }
}

То есть, это вариант, чем-то аналогичный if(obj instance of ...), но безопасный, т.к. компилятор заставит тебя либо перечислить все возможные варианты, либо сделать дефолтную ветку, и при этом не даст тебе случайно перепутать конкретные "дочерние" типы. И при этом с локализацией кода, описывающего поведение, как ты и хотел - прямо в одной функции (в данном случае функцию можно было написать еще проще, но я для демонстрации мысли выделил фигурными скобками альтернативные блоки).

#55
(Правка: 12:49) 12:38, 19 янв. 2020

skalogryz
> чё-т... как-то... хм...
skalogryz
> вариант №3
> Класс предлагает, абстрактный источник данных, с которым умеет работать
> носитель данных
К чему этот комментарий вообще был?

Great V.
> Кроме того, json.Serialize сам может выбирать те классы из иерархии Something,
> которые нужно сериализовать.
Т.е. получится алгоритм, который и не алгоритм вовсе. Если захочется пошарить такой "алгоритм" между проектами, то придётся его перелопачивать.

Great V.
> будет дублировать всю иерархию Something
С иерархией этот класс будет делать ровно то же самое, что в твоём варианте будет делать метод serialize()
Более того, такой обход придётся писать у каждого "алгоритма"

P.S. Не так давно допиливал один такой сериализатор. Вся игра сериализовалась через 1 метод. И нужно было создать toJson() вместо toBinary(). А ещё там был метод toYaml(), который отличался от toBinary(), хотя вроде как не должен. Так вот, этой радостью пришлось страдать целую неделю. И потом ещё два дня писать валидатор, чтобы убедиться, что расхождений нет. А было бы гораздо быстрее и приятнее, если б можно было просто написать методы записи и скормить им уже готовый DataProvider.

Great V.
> И чтобы существовали гарантии что другие классы от него унаследовать уже не
> получиться.
Но ведь для этого и так есть механизмы. В плюсах, например, можно сделать приватный конструктор и указать допустимые подклассы в качестве френдов.

#56
16:52, 19 янв. 2020

pahaa
> С иерархией этот класс будет делать ровно то же самое, что в твоём варианте
> будет делать метод serialize()
Хотя в итоге работа будет выполнена та же, но в моем варианте алгоритм будет отделен от данных.
И это хорошо, ведь Something не обязательно должен отвечать за сериализацию в JSON.

Слышал когда-нибудь о шаблоне проектирования "посетитель"?

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

#57
17:32, 19 янв. 2020

Great V.
> но в моем варианте алгоритм будет отделен от данных
дык, наоборот же
прям внутри алгоритма придётся хардкодить структуру данных
а в случае с дата-провайдером можно при особом желании разнести данные, сериализатор и дата-провайдер не только по разным модулям, но даже по трём разным библиотекам, и зависимость будет всего одна: дата-провайдер зависит от объекта
тут единственный минус будет в объёме кода при малом количестве классов, зато это легко масштабируется и при разрастании иерархии такой подход обгонит хардкоженные сериализаторы даже по этому параметру
В общем, не убедил. Я, честно, надеялся на что-нибудь новенькое и интересное))

Great V.
> не следует что эти механизмы не костыли для конкретной задачи
Совершенно согласен. Но суперкласс не особо отличается в этом плане. Просто задача довольно странная. Концептуально потребность в этом не ясна. Ограничение допустимых вариантов объекта противоречит принципам наследования (впрочем, как и protected и private наследование). Она выглядит странно что в процедурном, что в объекто-ориентированном, что в функциональном, что в декларативном подходе. Другие парадигмы мне неизвестны. Я, конечно, понимаю, что такое может увеличить надёжность некоторых костылей "здесь и сейчас", но применять такое систематически - выглядит как головная боль и признак плохой архитектуры.

#58
18:30, 19 янв. 2020

pahaa
> К чему этот комментарий вообще был?
просто, ни разу такого подхода, как ты предлагаешь, не встречал.

#59
21:38, 19 янв. 2020

skalogryz
> просто, ни разу такого подхода, как ты предлагаешь, не встречал.
И поэтому под номером 3 написал дословно то же самое?

Страницы: 1 2 3 4 5 Следующая »
ФлеймФорумПрограммирование