Qraizer
Advanced Member | Редактировать | Профиль | Сообщение | ICQ | Цитировать | Сообщить модератору
Цитата: Разумеется потому, что часто возникает необходимость унаследовать поведение двух сущностей. Полная же реализация множественного наследования как is a является излишней. | Наследуя интерфейсы, ты не наследуешь их реализации. Интерфейсы суть абстрактные сущности, они не имеют поведения, они его декларируют для своих реализаций. Чьи слова я перефразировал? Когда в списке базовых классов появляется интерфейс, понятно, что поизводный класс его реализует. Так или иначе. За одним-единственным исключением: если он сам не интерфейс, ибо в этом случае он просто его поглощает и взамен предоставляет другой, более обширный. Но даже в этом случае он его не отменяет, а явно утверждает, что является его расширением. В случае же именно что отмены базового интерфейса наследование должно быть обязательно непубличным. Когда ты наследуешь реализацию, понятно, что ты являешься в частности и базовым классом, т.е. тебя можно явно использовать везде, где требутся базовый класс. Если это не так, то опять же наследование обязано быть непубличныи, иначе любая функция, получив ссылку на базовый класс, получит в своё распоряжение не его, а нечто им не являющееся. ОО-архитекторы, я нигде не наврал? Подправьте, если что не так. Теперь ситуация. Я пишу компонент, который в одном флаконе и хранитель логов/истории, и ...эм-м, скажем, socket-ный приёмопередатчик, и несложненький блокнотик++. Что такое? Дикое сочетание, кому это только в голову пришло собрать воедино метры с секундами и залить сверху килограммами? А что, никто QIP не признал? Вы меня удивляете. Ну ладно, это так, лирика, вернёмся к нашим баранам. У меня есть интерфейсы IHistory, ISocketTransmitter и IRichEdit. Мне ничего не стоит создать свой класс, наследуя все три интерфейса, и реализовав их. Всё пучком? Я тоже проблем не вижу. Да вот незадача: я уже как-то писал их реализации, по две штуки первого и третьего и одну - второго, и у меня есть готовые THistoryFile, THistoryMemory, TTCPTransmitter, RichEditMemos и RichEdit_with_BlackJack_and_Bitchs. Как-то так: Код: class THistoryFile : public IHistory { /* ... */ } class THistoryMemory : public IHistory { /* ... */ } class TTCPTransmitter : public ISocketTransmitter { /* ... */ } class RichEditMemos : public IRichEdit { /* ... */ } class RichEdit_with_BlackJack_and_Bitchs : public IRichEdit { /* ... */ } | Создавались они в разное время для разных проектов. Что мне делать? Копипастить? Фу-у-ууу, скажут ОО-архитекторы и не только они. Что делает С++ист? Код: class TQIP: public THistoryFile, public TTCPTransmitter, public RichEditMemos { /* ... */ }; | и горя не знает. Замечу, что базовые интерфейсы IHistory, ISocketTransmitter и IRichEdit никуда не делись. Они по-прежнему в иерархии, так как их реализации THistoryFile, TTCPTransmitter и RichEditMemos явно от них порождены. ОО-архитекторы счастливы? Я тоже не вижу никаких проблем. Более того, я вообще не вижу даже потенциальных источников проблем. Да, класс объединяет в себе три разнородные сущности. И что? Где проблемы-то? А главное - где тут излишество-то? А самое главное - архитектура чётко показывает, кто чем является, кто что использует и как что от чего зависит. Что делает Дельфист? Код: TQIP = class(IHistory, ISocketTransmitter, IRichEdit) strict private mHistory : THistoryFile; mTransmitter: TTCPTransmitter; mEdit : RichEditMemos; protected property history : THistoryFile read mHistory implements IHistory; property transmitter: TTCPTransmitter read mTransmitter implements ISocketTransmitter; property edit : RichEditMemos read mEdit implements IRichEdit; public { ... } end; | ну и в общем-то тоже почти не знает горя. А почему почти, я уже говорил в прошлом посте. Во-первых. Есть класс-дебуггер, например, какая-нибудь реализация шаблона ...ну например Посетитель. (GoF никого не смущает?). Он должен ходить по всем IHistoryFile и собирать статистику для профилирования. Естественно ему и TQIP перепадает. Никаких проблем с его передачей не возникает, TQIP прекрасно кастится к IHistoryFile (ссылки на них, естественно), и Посетитель счастлив. Но вот мне вдруг понадобилось в Посетителе для TQIP-ов подсобрать чуток больше, для чего мне потребовался интерфейс, скажем, IRichEdit. Я, будучи граммотным ОО-архитектором, не стал "ломать" Посетителя для всего проекта, а решил проблему сугубо локально для себя - реализовал для Посетителя производный класс, в котором перекрыл нужный мне метод. Внимание, вопрос: как имея запасённый базовым классом IHistoryFile, от его по факту агрегированной реализации перейти к IRichEdit? Я могу ошибаться, но в своё время Дельфисты не удосужились мне ответить, мол, операция AS прекрасно справляется с прыжками между агрегатами одного класса-контейера, когда нам доступны только его базовые интефейсы (код только для демонстрации, так что просьба тапками не кидаться, если кто увидит некомпилябельность, её устранение сделает демонстрацию только хуже): Код: PROCEDURE TGuestMore.someMethod(IHistory hist); VAR edit: IRichEdit; BEGIN edit := hist AS IRichEdit; { ... } END; | Они просто промолчали, так что я заключил то, что заключил. В то же время C++ист просто напишет Код: void TGuestMore::someMethod(IHistory& hist) { IRichEdit& edit = dynamic_cast<IRichEdit&>(hist); /* ... */ } | и совершенно пофиг на конкретики реализаций. Плюсы прозрачнее, они не нагоняют тумана на иерархию, отношения и взаимозависимости. В них всё видно на уровне опубликованных интерфейсов. В Дельфи в общем случае без знания деталей реализации невозможно построить надёжную систему со сложными отношениями между подсистемами. Другими словами, как это ни парадоксально звучит, Плюсы своей прозрачностью позволяют делать черные ящики реально чёрными, а не с альфой под 10%. ОО-архитекторы уже смотрят косо. Во-вторых. TQIP реализует все три своих интерфейса. Это он так заявляет. На самом же деле он этого не делает. Он взял конкретные реализации этих трёх интерфейсов и их использует. Конечно, если я его автор, у меня проблем нет. Но я не автор, я пользователь этого TQIP, и без того, чтобы зарыться в изучение реализации этого TQIP, я об этом не узнаю, а значит если кому-то взбредёт в голову проапдейтить THistoryFile в нашем проекте, он вот так лёгким движением руки изменит поведение TQIP. Хотя казалось бы какая может быть связь между совершенно разными классами, однако одна из реализаций IHistoryFile оказалась зависимой от другой. ОО-архитекторы бьют тревогу, тут альфа нифига не 10%, все 90 наберётся. Да-да, с Плюсами будет та же картина. Но ведь её явно видно из объявления TQIP. Там явно сказано - TQIP является THistoryFile. Так что тут всё заранее известно и предсказуемо в отличие от. Цитата: Запутанность интерфейсной части при частичном перекрывании пространства имен. | Секундочку. У интерфейсов нет этой проблемы?? Нука-нука, вот есть у нас методы IHistoryFile::lifeTime() и ISocketTransmitter::lifeTime(), только первый отвечает за время хранения истории, второй - за время жизни пакета в сети. Интересно, если я вызову TQIP::lifeTime(), что вызовется? ИМХО очень интересный вопрос. Не языковой - архитектурный. Цитата: Проблема известная под названием алмаз никсона. Ромбовидное наследование. | Во-первых, это не проблема. Точнее, это не проблема множественного наследования реализаций. Во-вторых, как раз она ставит крест на множественном наследовании реализаций в Дельфях. Есть ситуации, когда неединственный базовый класс должен быть уникальным на иерархию, есть ситуации, когда он должен входить в неё столько раз, сколько указано. Тот же TQIP к примеру может иметь множество реализаций ISocketTransmitter, по одному на каждый потокол - ICQ, MailRu, GoogleTalk итп. И все эти ISocketTransmitter должны быть неединственными. Ты ж не собираешься предложить компилятору самому решить, сливать или не сливать? Так что это пробема опять же ОО-архитектуры, и решать её требуется в каждом случае явно ОО-архитектором, а не программистом. Так что проблемы алмаза в ООПрограммировании не существует, есть проблема алмаза в ООПроектировании. Цитата: Виртуальные конструкторы это фабричный метод в чистом виде, можно и без виртуальных конструкторов, но с ними лучше, намного лучше. | Надо будет потом вернуться к этой теме. Не забыть бы. Но вкратце-таки скажу. Виртуальных конструкторов не бывает. То, что бывает - инициализаторы. Borland обозвала их конструкторами объектов, её право, но ИМХО это был граммотный маркетинговый, но плохой дизайнерский ход. В Плюсах нет инициализаторов, и это в некоторой степени удручает, не могу не согласиться. То, что есть - это аналог Дельфийских конструкторов классов, но те и в Дельфи не могут быть виртуальными. Фабрика, реализованная прямо в языке - это удобно, когда нужно. Как показывает практика, нужно оно менее чем в ~10% случаев. Мягко говоря, постоянное её наличие во всех классах - это слишком высокая плата за потерю эффективности кода, сложность кастомизации фабрики, невозможность вменяемой реализации RAII, невменяемость безопасного к исключеняим кода, пестрящего TRY/FINALLY, невозможность разделить ответственность за инварианты между классами в иерархии да и отсутствие инвариантов как таковых. Сорри, тут только факты без аргументации. Просто не думаю, что будет продуктивно говорить подробно сразу обо всём. Написал, просто чтоб не потерялось.
---------- Одни с годами умнеют, другие становятся старше. |
|