Entry tags:
- dataflow,
- i/o,
- lisp,
- oop,
- programming
Ъ ООП I/O
Я тут подумал.
Вот как обычно делается обработка IO во всяком там ООП?
Вот у нас есть какой-то скажем TextReader, из него можно читать буквы.
Окей. Но вот нам надо сделать читалку XML-нодов из текста, XmlReader.
Так вот - почему обычно делается так, что TextReader биндится к объекту XmlReader в конструкторе последнего и остается там до окончания его, XmlReader'a, жизни? Т.е. почему вышележащие потоки обычно хранят используемые объекты внутри себя? Да, это может быть, неплохо ложится на C++ное RAII, но если подумать:
Возникают такие проблемы:
На самом деле, я еще про другие минусы этой модели недавно думал, но сейчас уже не вспомню - написал самые очевидные
А вот что вы думаете о противоположной модели, использующейся, в частности, в контексте "считывателя" Common Lisp - считыватель(штуковина, формирующая лисповые объекты из текста) и текстовые потоки друг с другом не связаны, и "высокоуровневый" поток, при вызове "read" принимает в аргументы низкоуровневый, как бы подключаясь к нему, когда нужно совершить операцию чтения. Это позволяет отделить состояние собственно считывателя(оно хранится в глобальных переменных - *read-base* и пр.) от состояния да и вообще, разновидности, используемого текстового потока, от которого нужен только интерфейс(он должен уметь считывать буквы).
Аналогия из реальной жизни - водоснабжение. Вот у нас есть труба, из которой по необходимости можно брать воду. Когда нам нужно, скажем, постирать что-либо, мы берем и подключаем к трубе стиральную машинку. Постирали - отключаем. Когда машинка, допустим, сломалась, и ее нужно поменять, нам не приходится ломать всю систему водоснабжения в доме, и строить ее заново - машинка не приварена к трубе, мы просто берем ее, выкидываем, или относим на ремонт, и приносим новую. Если в трубе вдруг пропала вода, мы подключаем машинку к другой трубе. Ну и так далее.
Конечно, в контексте программирования - если делать так, как это все делается в контексте лиспового reader'а, то возникает проблема разрастания количества аргументов функций по мере повышения количества слоев обработчиков. Но, тут можно пойти по тому пути, который используется, например, в обработчиках аудио и видео контента, например в виндовом DirectShow - там есть объекты фильтров, которые как-то перегоняют информацию из одной формы в другую. Фильтры создаются заранее, и потом, когда надо, "подключаются" друг к другу(у каждого фильтра есть т.н. разъемы, которые можно друг с другом соединять).
Вобщем, вот так вот. Что думаете?
upd. Вот пример того, о чем я говорю, в терминах интерфейсов на C#: http://pastebin.com/jvcb2F8g
(все-таки не совсем точно описал, что уже заметно по ответам в juick. Да, модель .NET предполагает что весь стек "читалок" стоит на фундаменте Stream, который основан на неуправляемых ресурсах. А я говорю про то, что архитектура должна быть слабосвязной, и "параллельной" (потоки должны связываться входами и выходами, динамически, как в графе фильтров DShow, а не скрывать друг друга или какой-то один "низкоуровневый" поток))
upd2. Короче, вот картинка идеи:

Кстати, на такую модель идеально ложится асинхронность, например.
Вот как обычно делается обработка IO во всяком там ООП?
Вот у нас есть какой-то скажем TextReader, из него можно читать буквы.
Окей. Но вот нам надо сделать читалку XML-нодов из текста, XmlReader.
Так вот - почему обычно делается так, что TextReader биндится к объекту XmlReader в конструкторе последнего и остается там до окончания его, XmlReader'a, жизни? Т.е. почему вышележащие потоки обычно хранят используемые объекты внутри себя? Да, это может быть, неплохо ложится на C++ное RAII, но если подумать:
Возникают такие проблемы:
- Фактически, с такой моделью, мы считаем низкоуровневый поток частью высокоуровневого, и таким образом, узурпируем его функциональность исключительно для реализации работы одного конкретного объекта высокоуровневого потока. Почему узурпируем? Потому что раз один объект - часть другого, то у нас появляется обязанность в деструкторе/финализаторе делать "освобождение" ресурсов первого. Соответственно, ни один другой объект н.у. поток хранить в себе не может, и значит, пользоваться им тоже не может.
Это ведет, в свою очередь к проблеме использования "многоцелевых" потоков, типа стандартного ввода(оно либо сильно усложняется либо становится вообще невозможным), во-вторых, повышает нагрузку на среду исполнения(для каждой отдельной комбинации потоков мы должны создавать их несколько каждого типа каждый раз), и в-третьих, усложняет комбинирование обработчиков(фактически, становится возможным только банальная линейная модель, цепь обработчиков ввода). И кстати, это, по моему мнению, довольно сильно противоречит идеологии "истинного" ООП, которая заключается в куче взаимодействующих объектов, между собой не связанных нигде, кроме точек взаимодействия. - Возникает проблема реюза состояния высокоуровневого потока. Потому что, как я уже выше написал, для каждой комбинации потоков мы должны создавать новые. Часто, для особенно сложных потоков данных, это создает невыносимо большие нагрузки на систему.
На самом деле, я еще про другие минусы этой модели недавно думал, но сейчас уже не вспомню - написал самые очевидные
А вот что вы думаете о противоположной модели, использующейся, в частности, в контексте "считывателя" Common Lisp - считыватель(штуковина, формирующая лисповые объекты из текста) и текстовые потоки друг с другом не связаны, и "высокоуровневый" поток, при вызове "read" принимает в аргументы низкоуровневый, как бы подключаясь к нему, когда нужно совершить операцию чтения. Это позволяет отделить состояние собственно считывателя(оно хранится в глобальных переменных - *read-base* и пр.) от состояния да и вообще, разновидности, используемого текстового потока, от которого нужен только интерфейс(он должен уметь считывать буквы).
Аналогия из реальной жизни - водоснабжение. Вот у нас есть труба, из которой по необходимости можно брать воду. Когда нам нужно, скажем, постирать что-либо, мы берем и подключаем к трубе стиральную машинку. Постирали - отключаем. Когда машинка, допустим, сломалась, и ее нужно поменять, нам не приходится ломать всю систему водоснабжения в доме, и строить ее заново - машинка не приварена к трубе, мы просто берем ее, выкидываем, или относим на ремонт, и приносим новую. Если в трубе вдруг пропала вода, мы подключаем машинку к другой трубе. Ну и так далее.
Конечно, в контексте программирования - если делать так, как это все делается в контексте лиспового reader'а, то возникает проблема разрастания количества аргументов функций по мере повышения количества слоев обработчиков. Но, тут можно пойти по тому пути, который используется, например, в обработчиках аудио и видео контента, например в виндовом DirectShow - там есть объекты фильтров, которые как-то перегоняют информацию из одной формы в другую. Фильтры создаются заранее, и потом, когда надо, "подключаются" друг к другу(у каждого фильтра есть т.н. разъемы, которые можно друг с другом соединять).
Вобщем, вот так вот. Что думаете?
upd. Вот пример того, о чем я говорю, в терминах интерфейсов на C#: http://pastebin.com/jvcb2F8g
(все-таки не совсем точно описал, что уже заметно по ответам в juick. Да, модель .NET предполагает что весь стек "читалок" стоит на фундаменте Stream, который основан на неуправляемых ресурсах. А я говорю про то, что архитектура должна быть слабосвязной, и "параллельной" (потоки должны связываться входами и выходами, динамически, как в графе фильтров DShow, а не скрывать друг друга или какой-то один "низкоуровневый" поток))
upd2. Короче, вот картинка идеи:

Кстати, на такую модель идеально ложится асинхронность, например.
no subject
Пример с DirectShow, кстати, очень показателен (многие системы устроены схожим образом: GStreamer, Jack; так же многие инструменты для machine learning).