Мой вопрос может показаться довольно странным, абстрактным и/или нелепым, но я хотел бы услышать мнения.
В мейнстримовых языках программирования разрешены произвольные взаимодействия между классами и интерфейсами — класс А может иметь поле типа "класс Б", и одновременно в классе Б может быть поле типа "класс А". То же самое с интерфейсами. То есть — все может зависеть от всего, везде разрешены циклы. Это, насколько мне известно, не считается чем-то предосудительным и часто используется.
Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?
С двумя важными дополнениями/исключениями:
1) Класс/интерфейс может использовать сам себя (то есть можно создать связанный список или дерево).
2) Запрет на циклы — только на уровне объявлений. То есть физически цикл создать можно, передав объект через, скажем, генерик-параметр. Но сами объявления циклическими быть не могут. (В расте все наоборот — объявления можно делать циклическими, но физически циклы без unsafe запрещены.)
Насколько плоха такая организация классов/интерфейсов в виде дерева, без циклов?
Здравствуйте, AlexRK, Вы писали:
ARK>Приветствую.
ARK>Мой вопрос может показаться довольно странным, абстрактным и/или нелепым, но я хотел бы услышать мнения.
ARK>В мейнстримовых языках программирования разрешены произвольные взаимодействия между классами и интерфейсами — класс А может иметь поле типа "класс Б", и одновременно в классе Б может быть поле типа "класс А". То же самое с интерфейсами. То есть — все может зависеть от всего, везде разрешены циклы. Это, насколько мне известно, не считается чем-то предосудительным и часто используется.
Циклическая зависимость означает, что мы не можем использовать эти типы раздельно друг от друга, может им тогда лучше в месте быть(объединить в один тип) или выделить что-то общее в отдельный тип.
ARK>Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?
Он не нужен.
ARK>С двумя важными дополнениями/исключениями: ARK>1) Класс/интерфейс может использовать сам себя (то есть можно создать связанный список или дерево). ARK>2) Запрет на циклы — только на уровне объявлений. То есть физически цикл создать можно, передав объект через, скажем, генерик-параметр. Но сами объявления циклическими быть не могут. (В расте все наоборот — объявления можно делать циклическими, но физически циклы без unsafe запрещены.)
ARK>Насколько плоха такая организация классов/интерфейсов в виде дерева, без циклов?
Здравствуйте, Qulac, Вы писали:
Q>Он не нужен. Q>По мне это не нужно.
Мне как раз интересно — что плохого будет от такого запрета. Что важное нельзя будет написать? Сразу приходит на ум паттерн "родитель и его потомки" со ссылками друг на друга. Не самый лучший паттерн, на мой взгляд, так что и пофиг на него. Но наверняка есть что-то еще.
Здравствуйте, AlexRK, Вы писали:
ARK>Насколько плоха такая организация классов/интерфейсов в виде дерева, без циклов?
Только это не дерево, а DAG (directed acyclic graph).
ARK>Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?
Подобные ограничения возникают при разбиении на сборки (при разбиении солюшена на проекты). Когда-то слышал, что технически можно дабиться циклических ссылок между сборками/нетмодулями (это не точно); но стандартный инструментарий/IDE это запрещает.
Здравствуйте, Qbit86, Вы писали:
Q>Только это не дерево, а DAG (directed acyclic graph).
Да, верно, я ошибся.
ARK>>Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?Q>Подобные ограничения возникают при разбиении на сборки (при разбиении солюшена на проекты). Когда-то слышал, что технически можно дабиться циклических ссылок между сборками/нетмодулями (это не точно); но стандартный инструментарий/IDE это запрещает.
Но все же в рамках проекта хоть какие-то циклы возможны. А что если их нет совсем (кроме двух указанных мной ограничений)? Можно это терпеть или нет?
Здравствуйте, AlexRK, Вы писали:
ARK> Q>Он не нужен. ARK> Q>По мне это не нужно.
ARK> Мне как раз интересно — что плохого будет от такого запрета. Что важное нельзя будет написать? Сразу приходит на ум паттерн "родитель и его потомки" со ссылками друг на друга. Не самый лучший паттерн, на мой взгляд, так что и пофиг на него. Но наверняка есть что-то еще.
Что первое в голову пришло. Типы Node и NodeList. У Node есть имя, т.п. и список детей NodeList, а так же какой-нибудь query может возвращать тоже NodeList который не Node.
Здравствуйте, ·, Вы писали:
ARK>> Мне как раз интересно — что плохого будет от такого запрета. Что важное нельзя будет написать? Сразу приходит на ум паттерн "родитель и его потомки" со ссылками друг на друга. Не самый лучший паттерн, на мой взгляд, так что и пофиг на него. Но наверняка есть что-то еще. ·>Что первое в голову пришло. Типы Node и NodeList. У Node есть имя, т.п. и список детей NodeList, а так же какой-нибудь query может возвращать тоже NodeList который не Node.
Здравствуйте, AlexRK, Вы писали:
ARK>>> Мне как раз интересно — что плохого будет от такого запрета. Что важное нельзя будет написать? Сразу приходит на ум паттерн "родитель и его потомки" со ссылками друг на друга. Не самый лучший паттерн, на мой взгляд, так что и пофиг на него. Но наверняка есть что-то еще. ARK>·>Что первое в голову пришло. Типы Node и NodeList. У Node есть имя, т.п. и список детей NodeList, а так же какой-нибудь query может возвращать тоже NodeList который не Node. ARK>По идее это ведь может быть один Node, разве нет?
class Node
{
string name;
int data;
...
Node parent;//ссылка на себя
NodeList children;
NodeList ancestors; //вот даже так
}
class NodeList
{
int size;
Node get(int index);
...
}
class Document
{
Node root;
NodeList findNodesByName(...);// вот тут список нодов, но не нода, т.к. имени и прочего нет.
}
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
AlexRK:
ARK>В мейнстримовых языках программирования разрешены произвольные взаимодействия между классами и интерфейсами — класс А может иметь поле типа "класс Б", и одновременно в классе Б может быть поле типа "класс А". То же самое с интерфейсами.
Интерфейс не может иметь полей.
Если подразумевается просто использование в сигнатуре методов, то не вижу ничего предосудительного.
Модератор-националист Kerk преследует оппонентов по политическим мотивам.
·>class Node
·>{
·> string name;
·> int data;
·> ...
·> Node parent;//ссылка на себя
·> NodeList children;
·> NodeList ancestors; //вот даже так
·>}
·>class NodeList
·>{
·> int size;
·> Node get(int index);
·>...
·>}
·>class Document
·>{
·> Node root;
·> NodeList findNodesByName(...);// вот тут список нодов, но не нода, т.к. имени и прочего нет.
·>}
·>
А нельзя так?
class Node
{
string name;
int data;
...
Node parent;
List<Node> children;
List<Node> ancestors;
}
class Document
{
Node root;
List<Node> findNodesByName(...);
}
Здравствуйте, Bill Baklushi, Вы писали:
ARK>>В мейнстримовых языках программирования разрешены произвольные взаимодействия между классами и интерфейсами — класс А может иметь поле типа "класс Б", и одновременно в классе Б может быть поле типа "класс А". То же самое с интерфейсами.
BB>Интерфейс не может иметь полей.
Во-первых, это неважно в контексте текущего вопроса. Во-вторых, есть языки, в которых интерфейсы могут иметь поля (Java, Eiffel).
BB>Если подразумевается просто использование в сигнатуре методов, то не вижу ничего предосудительного.
Мой вопрос не в том, предосудительно это или нет. Вопрос в том, насколько критичен отказ от циклов в объявлениях типов. Есть ли какие-то очевидные шоу-стопперы. Или есть полтора сценария, которые нужны в очень специфических случаях.
Ну мне циклы редко нужны. Если есть возможность обойти ограничение через что-то вроде void*, не думаю, что такой дизайн меня сильно бы ограничил. Правда непонятно из чего вытекает такое ограничение.
Вообще если у нас есть класс A и класс B с методами a() и b(), которые, для простоты, рекурсивно вызывают друг друга, т.е. код вида:
class A {
B b;
void a() {
b.b();
}
}
class B {
A a;
void b() {
a.a();
}
}
То это разруливается через промежуточный интерфейс и становится нерекурсивным:
interface IA {
void a();
}
interface IB {
void b();
}
class A implements IA {
IB b;
void a() {
b.b();
}
}
class B implements IB {
IA a;
void b();
a.a();
}
}
При этом этот случай достаточно общий, чтобы такого подхода хватило на разруливание рекурсивной структуры типов любой степени сложности, насколько я могу представить. Иногда это даже может приводить к более чистому дизайну.
Здравствуйте, AlexRK, Вы писали:
ARK>Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?
Считаю такой запрет сильным и даже вредным, если будем говорить о доменной модели под конкретное непростое множество окружающего мира.
Например, это важно для модели, которая одновременно отражает как физическое взаимоотношение (контейнер-содержимое), так и логическое (взаимосвязи между разными вложенными не обязательно в один и тот же контейнер объектами разных типов).
В последний раз, что мне доводилось реализовывать подобную модель, я нашёл очень удобным подход с retriever'ом, предоставляющим find-методы по индексам сущностей разных типов, которые выдавали объекты с lazy-разрешаемыми переходами по графу физической или логической навигации (реализация на java, переходы через Memoize Supplier, гарантированно невидимые для потребляющей стороны через методы того же retriever'а). В частности, это не только позволило избежать конструкторов с лишь частичной проверкой инвариантов, но и не всегда дешёвой загрузкой связанных данных в ситуациях, когда это не было просто не нужно. Более того, это позволило избежать императивной нагрузки на реализациях разных бизнес-операций, где было достаточно вызвать один find-метод и далее использовать навигацию по модели без возврата к другим finder'ам. На всякий случай добавлю, что речь была совсем не про ORM/реляционную БД и модель с 200+ сущностей.
Здравствуйте, AlexRK, Вы писали:
ARK>·> NodeList children; ARK>А нельзя так? ARK> List<Node> children;
Можно-то можно, но это неэквивалентно. NodeList это может быть с одной стороны расширенный относительно List<Node> класс с дополнительными свойствами и методами, а с другой стороны — инкапсулировать способ хранения нод, он может быть совсем не List<T>, или сейчас List, а потом захочется поменять на другой способ, и не хотелось бы отлавливать все вхождения по всему приложению (его применений может быть куда больше чем только в классе Node).
Здравствуйте, fmiracle, Вы писали:
ARK>>·> NodeList children; ARK>>А нельзя так? ARK>> List<Node> children;
F>Можно-то можно, но это неэквивалентно. NodeList это может быть с одной стороны расширенный относительно List<Node> класс с дополнительными свойствами и методами, а с другой стороны — инкапсулировать способ хранения нод, он может быть совсем не List<T>, или сейчас List, а потом захочется поменять на другой способ, и не хотелось бы отлавливать все вхождения по всему приложению (его применений может быть куда больше чем только в классе Node).
Ну, так можно далеко зайти. Вдруг потом захочется и name чтобы был не string, и size чтоб не int. И поля другие, и методы, и списки аргументов.
Абстрагироваться от всего на всякий случай — это порочная практика, я считаю.
Впрочем, в данном случае вроде бы можно передать тип контейнера через генерик-параметр:
Не, похоже в жабе нельзя. Нужны higher-kinded types.
Здравствуйте, AlexRK, Вы писали:
ARK>Ну, так можно далеко зайти. Вдруг потом захочется и name чтобы был не string, и size чтоб не int.
И в некоторых случаях это вполне себе может оказаться оправдано.
А абстрагирование коллекции — еще чаще.
ARK>Абстрагироваться от всего на всякий случай — это порочная практика, я считаю.
От всего на всякий случай — да. Но это не повод вообще убирать возможность подобного абстрагирования из языка.
ARK>А нельзя так? ARK> List<Node> children;
Во-первых, шаблон List<Node> это таки отдельный тип, аналогичный NodeList с т.з. сабжа.
Во-вторых, впрочем иногда таки нельзя, если понадобится расширить List специфичными для NodeList методами.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, AlexRK, Вы писали:
ARK>Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?
Мне больше всего нравится подход Go, когда есть запрет на циклические зависимости между модулями, что крайне хорошо сказывается на дизайне, и всё. Тот же запрет в Rust не от хорошей жизни, а от модели памяти.
Здравствуйте, fmiracle, Вы писали:
ARK>>Ну, так можно далеко зайти. Вдруг потом захочется и name чтобы был не string, и size чтоб не int.
F>И в некоторых случаях это вполне себе может оказаться оправдано. F>А абстрагирование коллекции — еще чаще.
Не уверен за жабу, но абстрагироваться от коллекции можно чем-то типа алиаса: "NodeList = List;"
Главное, чтобы эта коллекция не была завязана на определение Node. Я хочу сказать, что не вижу в данном примере необходимости того, чтобы коллекция знала про тип своих элементов.
Здравствуйте, kaa.python, Вы писали:
ARK>>Вопрос у меня таков: насколько сильным вы считаете запрет на любые циклы в графе классов и интерфейсов?
KP>Мне больше всего нравится подход Go, когда есть запрет на циклические зависимости между модулями, что крайне хорошо сказывается на дизайне, и всё.