Здравствуйте, Сергей Губанов, Вы писали:
СГ>Самый простой объект — это действительно структура (члены структуры, узлы графа, отдельные указатели и т.д.).
Самый простой объект — целостный, в котором не выделена внутренняя структура.
СГ>Самая простая динамическая структура связанных объектов — это список. СГ>Самый простой способ объединения объектов в один блок — массив на уровне языка программирования.
Разница между блоком и динамической структурой неясна. С моей т.з. самым простым методом объединения является неупорядоченная коллекция. Либо упорядоченная. Без завязки на то, как она будет храниться: массивом, списком, деревом...
СГ>Самый простой взгляд на взаимодейсвие между объектами — это их композиция, а уж никак не "наследование скрытого состояния".
Это, пожалуй, верно. Но, добавлю, что композиция объектов порождает третий объект, поведение которого требует доопределения. Композиция проста, но требует уточнения.
СГ>Так что "глубокий изъян" это, видимо, стремление к максимальной простоте.
Простота сама по себе является достоинством только, если с ней удобно работать. Для построения любого алгоритма достаточно двух конструкций: простого следования и условного перехода. Проще некуда. Нафиг-нафиг такую простоту!
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, eao197, Вы писали:
E>>Это я и так понимаю, запоминается writter куда? В атрибут. И, вероятно, не public-атрибут.
СГ>Естественно. Но тут нет никакого наследования от какого-либо самостоятельного класса объектов. Здесь речь идет о композиции объектов.
А я и не говорил про наследование в этом случае. Перечитай еще раз внимательно мои слова.
E>>Сергей, вы не понимаете суть вопроса. Пусть сначала Formatter был связан с writter-ом, который связан с файлом. Затем какому-то потомку захотелось перевязать этот же Formatter на writter, который связан с pipe. Можно ли такое допускать? E>>А почему нет? Пусть у меня есть Formatter, который строит XML и передает его в writter. Затем я делаю потомка для этого Formatter, который перед построением XML переводит все строки в UTF-8. Разве это запрещено? И нужно ли моему Utf8XmlFormatter-у знать, где XmlFormatter хранит ссылку на writter?
СГ>Нет никаких потомков! Класс Formatter не является потомком от класа Writer. Это разные классы.
Где я говорил о том, что Formatter является потомком Writter?
Я спросил про две вещи:
1. Можно ли делать свою иерархию классов для Formatter?
2. Если можно, то имеет ли право производный Formatter сменить ссылку на writter, которая была сохранена в базовом классе Formatter?
СГ>Самостоятельному объекту formatter ничего не известно о том куда самостоятельный объект writer осуществляет вывод. СГ>
СГ>Carrier, Reader, Writer, Scanner, Formatter, Client — это композиция из 6 разных объектов (классы которых не связаны друг с другом никаким отношением наследования). Стрелочки на рисунке обозначают поток информации от/в носител(я) Carrier, в роли которого может выступать кто угодно (File, Stream, Socket, ...) из/в Client.
СГ>Этот паттерн проектирования называется Carrier-Rider-Mapper. СГ>Reader и Writer — это Rider-ы СГ>Scanner и Formatter — это Mapper-ы
Мне кажется, что когда разговор уходит с простых вещей в область паттернов, то здравый смысл уходит куда-то далеко-далеко.
СГ>Каждый Carrier реализует свои собственные Reader и Writer (нет никакого наследования от других самостоятельных классов объектов).
Весело. Если в Unix-е чтение из файла, pipe и сокета ничем не отличается друг от друга, то зачем мне делать 3 разных Carrier (FileCarrier, PipeCarrier, SockerCarrier), а для них еще и 6 Reader/Writter (FileReader, FileWritter, PipeReader, PipeWritter, SocketReader, SocketWritter)?
Имхо, в этом случае ООП и не пахнет. А если и пахнет, то чем-то прогнившим.
СГ>Каждый Client реализует нужные только ему Scanner и Formatter (нет никакого наследования от других самостоятельных классов объектов).
Т.е. если клиенту MailAgent нужно читать из сокета SMTP трафик, то он должен создать свои Scanner и Formatter. И если я хочу создать свой SMSForwarder, который использует SMTP транспорт, то я так же должен создать собственные Scanner и Formatter? Даже не смотря на то, что их функциональность будет практически такой же как и для MailAgent?
СГ>Количество реализаций равно N + M, где N — количество разных Carrier-ов, а M — количество разных Client-ов. Если не использовать этот паттерн, а заставлять каждого клиента уметь работать непосредственно с каждым карриером, то количество реализаций взаимодействия из суммы N + M превратится в произведение N * M, что неэкономно.
Имхо, как раз то, что ты предлагаешь и приведет к N*M.
... << RSDN@Home 1.1.4 beta 4 rev. 303>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, tarkil, Вы писали:
T>Здравствуйте, Сергей Губанов, Вы писали:
СГ>>Самый простой объект — это действительно структура (члены структуры, узлы графа, отдельные указатели и т.д.).
T>Самый простой объект — целостный, в котором не выделена внутренняя структура.
Разумеется. Если переменные этой записи не экспортируются, то такой объект снаружи выглядит как раз как объект с не выделеной внутренней структурой. Вот так:
DEFINITION Module1
TYPE
Object = RECORD END;
END Module1.
Что находится внутри записи известно только самому модулю, для всех остальных тип Module1.Object — это вещь в себе.
СГ>>Самая простая динамическая структура связанных объектов — это список. СГ>>Самый простой способ объединения объектов в один блок — массив на уровне языка программирования.
T>Разница между блоком и динамической структурой неясна.
Динамическая структура динамически изменяется. Блок — фиксирован, его можно целиком создать и целиком о нем потом забыть.
СГ>>Самый простой взгляд на взаимодейсвие между объектами — это их композиция, а уж никак не "наследование скрытого состояния".
T>Это, пожалуй, верно. Но, добавлю, что композиция объектов порождает третий объект, поведение которого требует доопределения. Композиция проста, но требует уточнения.
Нет не порождает. Третий объект нужен в языках не имеющих сборки мусора только для того чтобы ввести между объектами отношение хозяин/раб и создавать/уничтожать первые два объекта синхронно с третьим — так проще за памятью следить.
СГ>>Так что "глубокий изъян" это, видимо, стремление к максимальной простоте.
T>Простота сама по себе является достоинством только, если с ней удобно работать. Для построения любого алгоритма достаточно двух конструкций: простого следования и условного перехода. Проще некуда. Нафиг-нафиг такую простоту!
В таком случа добавляют "...но не проще." Целиком: "Сделай так просто на сколько это возможно, но не проще".
Здравствуйте, eao197, Вы писали:
E>1. Можно ли делать свою иерархию классов для Formatter? E>2. Если можно, то имеет ли право производный Formatter сменить ссылку на writter, которая была сохранена в базовом классе Formatter?
Делайте на здоровье и то и другое.
E>...зачем мне делать 3 разных Carrier... E>...я так же должен создать собственные Scanner и Formatter?...
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, eao197, Вы писали:
E>>1. Можно ли делать свою иерархию классов для Formatter? E>>2. Если можно, то имеет ли право производный Formatter сменить ссылку на writter, которая была сохранена в базовом классе Formatter?
СГ>Делайте на здоровье и то и другое.
Тогда следующий вопрос: если базовый тип какого-то Formatter-а не допускает простой смены writter-а в процессе работы, то как он может запретить делать это производному классу? Например, Formatter шифрует сообщение каким-либо блочным шифром. Для этого сообщение разбивается на блоки кратные, скажем, 8 байтам. Но вот в очередном сообщении оказался хвостик из 5 байт. Если поступит следующее сообщение, то Formatter просто объеденит эти 5 байт с 3-мя первыми байтами очередного сообщения и продолжит свою работу. Если Formatter выполнит операцию flush, то он добавить к 5-ти оставшимся байтам 3 нулевых байта и зашифрует их.
Но в этом случае Formatter должен защищать свою ссылку на writter-а, чтобы какой-то из нерадивых потомков не сменил writter-а пока есть оставшиеся от предыдущего сообщения байты. Имхо, логично здесь хранить ссылку на writter-а в виде private атрибута. А доступ к нему предоставлять производным классам через protected методы.
E>>...зачем мне делать 3 разных Carrier... E>>...я так же должен создать собственные Scanner и Formatter?...
СГ>Не делайте на здоровье ни того и ни другого.
Что значит не делать? Как же тогда паттерн Carrier-Rider-Mapper? Или это Zonnon-specific штука?
... << RSDN@Home 1.1.4 beta 4 rev. 303>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Сергей Губанов, Вы писали:
К>>У меня складывается впечатление, что есть какой-то глубокий изъян в методологии изучения и практики программирования. В основном он присущ изучению структурно-ориентированных языков. Вот что это за изьян: К>>Мышление об объектах как о россыпи деталей (члены структуры, узлы графа, отдельные указатели и т.д.). К>>Отсюда — написание функций, работающих со списком через указатель на головной узел; динамические массивы на уровне языка, управление которыми (перевыделение памяти, например) доступно кому попало; вот ещё добавляется боязнь скрытого состояния базовых объектов.
СГ>Самый простой объект — это действительно структура (члены структуры, узлы графа, отдельные указатели и т.д.). СГ>Самая простая динамическая структура связанных объектов — это список. СГ>Самый простой способ объединения объектов в один блок — массив на уровне языка программирования. СГ>Самый простой взгляд на взаимодейсвие между объектами — это их композиция, а уж никак не "наследование скрытого состояния".
СГ>Так что "глубокий изъян" это, видимо, стремление к максимальной простоте.
Ты сам сказал "... но не проще".
Если я делаю декомпозицию задачи "мне нужен контейнер с последовательным двусторонним доступом, бла-бла-бла"
до "вот тебе объекты-узлы, собирай из них орграф-список, следи за целостностью и т.д."
то заказчик не порадуется. И меня не порадует.
В учебных целях (для понимания алгортимов реализации всей этой механики) — ради бога. Но в прикладных — первое, что нужно будет сделать — это найти наиболее абстрактный (в смысле — не привязанный к нюансам реализации) интерфейс.
Здравствуйте, eao197, Вы писали:
E> А доступ к нему предоставлять производным классам через protected методы.
Я вот что хочу сказать. Вы конечно можете наследоваться от классов как хотите, но я "проповедую" такой способ: Если уж наследоваться, то всегда это делать только от абстрактных классов. А от полноценных самостоятельных самодостаточных классов не наследоваться — вместо этого использовать композицию с уже готовыми объектами таких классов. Я считаю что так более правильно. У меня все.
E> Как же тогда паттерн Carrier-Rider-Mapper? Или это Zonnon-specific штука?
Zonnon тут не причем. Паттерн Carrier-Rider-Mapper был придуман Клеменсом Шиперски
Из документации к BlackBox:
The Carrier-Rider-Mapper separation goes back to a research project ["Insight ETHOS: On Object-Orientation in Operating Systems"; Clemens Szyperski; vdf, Zürich, 1992, ISBN 3 7281 1948 2] predating BlackBox. This project used several design patterns and design rules (e.g., avoidance of implementation inheritance) that are also described in Design Patterns. In the Design Patterns terminology, a Rider-Mapper combination (or Carrier-Mapper combination if there is no rider) forms a bridge pattern.
Здравствуйте, Кодт, Вы писали:
К>...первое, что нужно будет сделать — это найти наиболее абстрактный (в смысле — не привязанный к нюансам реализации) интерфейс.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, eao197, Вы писали:
E>> А доступ к нему предоставлять производным классам через protected методы.
СГ>Я вот что хочу сказать. Вы конечно можете наследоваться от классов как хотите, но я "проповедую" такой способ: Если уж наследоваться, то всегда это делать только от абстрактных классов. А от полноценных самостоятельных самодостаточных классов не наследоваться — вместо этого использовать композицию с уже готовыми объектами таких классов. Я считаю что так более правильно. У меня все.
Да вы, батенька, экстремист!
Похоже, что больше я вряд ли смогу чего добавить.
... << RSDN@Home 1.1.4 beta 4 rev. 303>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
C>То есть делегировать непереопределенные методы из интерфейса I1 в C>I1Impl. Реализуется такое до смешного просто, компилятору нужно C>сгенерировать стабы методов такого вида: C>
Здравствуйте, AndrewVK, Вы писали:
AVK>Самое смешное что в шарпе for действительно синтаксический сахар, на уровне IL превращающийся в while. Далеко не все декомпиляторы умеют использовать for.
while тоже нет. Есть конструкция проверки значения и конструкция перехода.
... << RSDN@Home 1.1.4 beta 4 rev. 359>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
VD>>Private-наследование уже выброшено. И, кстати, подилом. Дурь это.
E>Из C++ выброшено? E>А где про это прочитать можно?
На С++ свет клином не сошелся. Хотя согласен, говорить "выброшено" не корректно. Проще сказать, что кроме С++ его нигде толком то и нет.
... << RSDN@Home 1.1.4 beta 4 rev. 359>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VladD2 пишет:
> C>Получится примерно то же самое, что и трейты, но для этого не нужно > C>вводить никаких особых изменений в язык. > Зачем компилятору что-то делегировать, когда он может просто > скопировать код методов?
Копирование — это плохо. Вдобавок, мое решение более гибкое — я могу
менять реализацию делегата в run-time. Да и вообще, по-моему, гораздо
более простое решение.
Здравствуйте, Cyberax, Вы писали:
C>Копирование — это плохо. Вдобавок, мое решение более гибкое — я могу C>менять реализацию делегата в run-time. Да и вообще, по-моему, гораздо C>более простое решение.
C>То есть делегировать непереопределенные методы из интерфейса I1 в C>I1Impl. Реализуется такое до смешного просто, компилятору нужно C>сгенерировать стабы методов такого вида:
Смешно перестает быть тогда, когда происходит обратное приведение. Что вернет выражение (A)(I2)(new A())?
... << RSDN@Home 1.1.4 beta 4 rev. 347>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Смешно перестает быть тогда, когда происходит обратное приведение. Что вернет выражение (A)(I2)(new A())?
Вот поэтому в COM агрегация устроена с прибамбасами. Чтобы семейство агрегатов и главного объекта выдавало наружу интерфейсы, QueryInterface'абельные друг к другу. Для этого каждый агрегат знает Controlling IUnknown главного объекта и все запросы по AddRef, Release, QueryInterface перенаправляет к нему.
Здравствуйте, Кодт, Вы писали: К>Вот поэтому в COM агрегация устроена с прибамбасами. Чтобы семейство агрегатов и главного объекта выдавало наружу интерфейсы, QueryInterface'абельные друг к другу. Для этого каждый агрегат знает Controlling IUnknown главного объекта и все запросы по AddRef, Release, QueryInterface перенаправляет к нему.
Я в курсе. COM как раз явственно демонстрирует тот факт, что для корректного решения проблемы каждый класс, который может стать жертвой агрегации (класс-примесь) обязан реализовывать специальные интерфейсы. В принципе, их реализация ничего сверхъестественного не представляет и даже может быть сгенерирована компилятором. Однако, если мы говорим уже о .Net, то возникают некоторые занятные особенности. В частности, вместо явного вызова QueryInterface, который может быть делегирован аггрегирующему объекту путем подмены реализации, в коде будет использован специальный опкод каста. Т.е. для корректного примешивания нужно просматривать весь код примеси в поисках специфических операций, и корректно заменять его на что-то.
... << RSDN@Home 1.1.4 beta 4 rev. 347>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.