Re[21]: Мэйнстрим vs. Самосовершенствование :)))
От: Павел Кузнецов  
Дата: 30.10.04 05:17
Оценка: 24 (6) +1 -1
VladD2:

> ПК> Прикол в том, что алгоритмы из Arrays нельзя применить к List или Deque,


> Да уж. Сортированная очередь — это прикол.


Может это, конечно, (для некоторых) и прикол, но сортированные очереди проходят в курсе изучения Computer Science, а некоторым — о ужас! — даже рассказывают о priority sorted queue

> ПК> Одним из фундаментальных принципов которого является Open-Closed Principle. А запихивание всех подряд функций в тот или иной "класс", у которого нет никаких основных атрибутов (поведение, состояние, инварианты), к ООП никакого отношения не имеет.


> Ну, а теперь представь, что кое-то на ООЯ пытается писать в ОО-стиле. Может они конечно и не удовлетворяют твоей концепции, но пользоваться плодами их трудов чертовски удобно.


Это не "мои" концепции, это один из классических критериев качества объектно-ориентированного дизайна.

> По теории ООП все методы объекта желательно помещать в его класс.


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

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

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

Например, представим, что у нас есть скелет простенькой иерархии "библиотечных" контейнеров (псевдокод):
interface Sequence<T>
{
   class Pos;

   uint size();
   bool is_empty();

   Pos begin();
   Pos end();
   Pos next(Pos);

   Pos insert(Pos, T);
   void remove(Pos);
   void clear();
};

interface RandomAccessibleSequence<T> : Sequence<T>
{
   T operator[](int index);
   Pos pos_by_index(int index);
};

class List<T> : Sequence<T>
{
};

class Array<T> : RandomAccessibleSequence<T>
{
};


Итак, мы хотим иметь возможность эти контейнеры сортировать. Допустим, мы решили добавить метод sort() в интерфейс Sequence<>.

Некоторое время мы живем счастливо, но оказывается, что для некоторых задач принципиальным оказывается вопрос: сохраняет ли используемый алгоритм порядок "одинаковых" с точки зрения упорядочивания элементов. Ну, что ж, следуя заведенному порядку, нам будет нужно добавить метод stable_sort().

Но как только мы подобным образом изменим интерфейс Sequence<>, все его наследники "сломаются", т.к. у них-то реализации stable_sort() не будет. Кроме того, есть ли уверенность, что для всех "последовательностей" существуют эффективные методы их сортировки с сохранением порядка "одинаковых" с точки зрения упорядочивания элементов? Ведь у пользователя могут быть свои наследники интерфейса Sequence<T>, о реализации которых мы ничего не знаем... Соответственно, этого делать нельзя, и придется поступать как-нибудь по-другому.

Хорошо, скажем, мы, понимая эту проблему, интерфейс Sequence<> менять не будем, а вместо этого добавим stable_sort(), скажем, в Array<>, где он более всего нужен, и будет иметь реализацию по-умолчанию, на случай, если кто-то от Array<> унаследовался.

Однако на этом наши приключения не заканчиваются: ведь у пользователя, в его наследнике Array<>, уже могла быть своя stable_sort(), обладающая несколько иной семантикой, и эта функция "перекроет" определенную в Array<>. В таком случае новые функции, принимающие Array<>, и предполагающие соответствие семантики stable_sort() той, что заявлена в Array<>, правильно работать не будут.

Далее, если представить себе развитие этой иерархии и возможную потребность добавления в будущем утилит типа find(), find_if(), binary_search() и т.п., то становится очевидно, что в классы этой иерархии эти функции также добавлять будет нельзя.

Есть и еще одна сторона этой проблемы: очевидно, что разработчики библиотеки не могут учесть всех нужд пользователей, и снабдить их всеми нужными им "утилитами". Что же делать пользователям? Следуя "попыткам писать в ОО-стиле", им надо будет унаследоваться от класса, который они захотят "расширить", и добавлять в своего наследника нужные им "утилиты". Но на этом пути их ждет масса трудностей.

Во-первых, вполне может оказаться, что некоторая "утилита" им нужна еще на уровне Sequence<>. Не имея возможности расширять этот интерфейс, пользователи будут вынуждены унаследовать от всех наследников Sequence<> только для добавления этой "утилиты".

Во-вторых, даже проделав всю эту работу, они будут становиться в тупик, получая готовые объекты наследников Sequence<> "извне": ведь эти объекты экземплярами их "расширенных" классов не будут. Их придется копировать, или работать с ними по-другому, чем с "последовательностями", созданными в коде приложения.

Какой же выход?

Выход простой: тем или иным образом вынести эти "утилиты" за пределы самих классов. В любом случае, никакой необходимости для них быть членами обсуждаемых классов нет, т.к. они прекрасно могут быть реализованы через "основной" интерфейс (а если не могут, то "основной" интерфейс не полон). А в качестве приза мы получим невероятную легкость расширения набора этих "утилит", совершенно не затрагивая клиентов классов рассматриваемой иерархии. Более того, пользователи с той же легкостью смогут пополнять набор "утилит" по такому же принципу.

Соответственно, по этому поводу разработчики библиотек Java поступили абсолютно правильно, вынеся sort и остальные утилиты для работы с массивами в совершенно отдельный "класс" java.util.Arrays. Единственное нарекание, что был использован именно класс, а не namespace, т.к. основных атрибутов "настоящих" классов у java.util.Arrays нет, но это уже претензии не к разработчикам библиотек, а к языку, т.к. там такой функциональности просто-напросто нет.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[22]: Мэйнстрим vs. Самосовершенствование :)))
От: Павел Кузнецов  
Дата: 30.10.04 05:29
Оценка: 1 (1) +1 :)
Павел Кузнецов:

> Выход простой: тем или иным образом вынести эти "утилиты" за пределы самих классов.


Кстати, забыл привести один яркий пример очень неудачного напихивания класса "утилитами", иллюстрирующий проблему еще с одной стороны: это шаблон класса стандартной библиотеки C++, std::basic_string<> (aka std::string, std::wstring).

Куча "утилит", находящихся в этом шаблоне (find_first, find_last_of и т.п.), которые, очевидно, могли быть вынесены как "свободные" функции, плоха по, как минимум двум соображениям:
  • когда по тем или иным причинам приходится делать свой класс для замены std::basic_string, также приходится заново реализовывать в своем классе все эти "утилиты";
  • эти "утилиты" невозможно использовать для "строк" char*, а копировать полученную char* в std::string не всегда хорошо.
    Будь они "свободными" функциями, оперирующими с диапазонами итераторов, жизнь была бы намного легче.
    Posted via RSDN NNTP Server 1.9 gamma
  • Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
    Re[10]: Oberon???????????????????????????????????
    От: AndrewVK Россия http://blogs.rsdn.org/avk
    Дата: 30.10.04 09:18
    Оценка: +1
    Здравствуйте, VladD2, Вы писали:

    AVK>>Увы, некоторые фишки щарпа встроены в язык намертво и используются в стандартной библиотеке. Можно конечно про них умалчивать, но задолбаешься потом флажки развешивать, куда не ступать.


    VD>Например?


    Так примеры я уже неоднократно приводил — эвенты, боксинг, двойственная сущность енумов и т.п.

    AVK>>Ничего там не уйдет. Там вобще как таковой отдельной концепции нет.


    VD>Агащасблин. На них и визуальные контролы завязаны и ЁЖиБи.


    Интересно что у Java Beans и EJB общего кроме слова бобы? Опять же, Java Beans это не часть языка, это обычная библиотека.

    AVK>> Просто в некоторых библиотеках появляются listeners, причем как ими пользоваться понятно сразу же при просмотре первого примера.


    VD>Ну, как поьзоваться событиями дотнета становится понятно значитель быстрее.


    Пользоваться безусловно, я об этом и говорил. А вот разобраться как оно работает намного сложнее.

    AVK>> Что же такое эвенты, по моим наблюдениям, точно не знают весьма немалый процент профессиональных программистов. Посмотри с какой регулярностью в форуме возникает вопрос — "чем эвенты отличаются от делегатов".


    VD>Дык с тем же успехом взникают вопросы "чем переменные отличаются от свойств".


    Ни разу не видел.

    VD>В принципе, при наличии делегатов можно было бы обойтись без событий. Просто для CLI это более общая идеома. В Васике, например, события совсем иначе выглядят.


    Все оно так, но к сожалению эта фишка встроена в CLR и язык, и игнорировать ее при обучении будет очень сложно.

    AVK>>Я уже сказал — здесь джава действительно требует больше усилий, нежели шарп.


    VD>Ну, и выходит даш на даш.


    Да нет, в шарпе заморочек заметно больше. По собственному опыту скажу — при изучении джавы в самом языке меня что то перестало удивлять уже через неделю, а вот в шарпе я встречал фишки о которых не знал еще несколько месяцев. Это при том что на момент изучения шарпа я уже хорошо знал джаву, следовательно базовые вещи в языке, вроде управляющих конструкций, общего синтаксиса классов, неймспейсов мне изучать было не нужно.

    AVK>> Но в целом шарп по неявностям и нетривиальностям джаву опережает с большим запасом. Поверь человеку, который неплохо знаком с обоими языками.


    VD>Да я как бы Яву на уровне языка знаю не плохо. Там скорее дыр больше, чем неявностей. Все неявности Шарпа — это залатывания дыр Явы. Да Шарп, от части, и родился как предлжение по модификации Явы.


    Не дыр, а неудобства использования. Вот только при обучении удобство написания реальных систем отходит на второй план.

    VD>Вот только этот прием теперь во всех книжках по Яве идет как базовый способ реализации событий.


    Синтаксис короче. Вполне допустимо после того как человек научится реализовать интерфейсы подписки явно. К сожалению, тут я с тобой согласен, в большинстве книжек сразу начинают использовать анонимные классы, в свое время меня это сильно озадачило.
    Впрочем шарп 2.0 в этом отношении ничуть не лучше — анонимные методы фактически та же фигня, особенно когда внутри ты начинаешь использовать локальные переменные.

    VD> Еще бы (?!) он же в половине библиотек испльзвется.


    Это пофигу. Анонимные классы снаружи не видны, так что что там внутри библиотек используется никого не колышет.

    AVK>>Значительно проще, поскольку вся механика явная.


    VD>Не батенька. Когда явной механики вагон и нехилая тележка, то объяснять задолбаешся.


    Не задолбаешься. Объяснять нужно только то, что требуется для объяснения какой либо концепции. Если же студент потом где нибудь наткнется на класс с событиями, то безо всякой документации сможет догадаться как их использовать. А вот если наткнется на эвент, то без помощи документации уже не обойтись.

    VD> Иначе бы С++ был бы самым простым языком в мире.


    Без шаблонов и множественного наследования вобщем то это достаточно простой язык.

    VD>Здорово. Значит часть возможностей языка не объясняем.


    Это не возможности языка, это паттерн.

    VD>В Яве уже появилась куча возможностей из Шарпа. Те же атрибуты, например. Их тоже прикажешь не учить? Тогда можно вообще к Яве 1.0 обратиться. Уж там учить еще меньше.


    Возможно ты и прав. Хотя как язык джава практически не менялась вплоть до версии 5.0. Так что 1.4 будет самое оно.

    AVK>>Смотря какого. Для ремесленника важный, для инженера не очень.


    VD>Реального. Или мы учим детей играться в программирование. Или учим программировать.


    Да не учит никто студентов программировать, по крайней мере в российских вузах. Учат созданию программных систем, а это далеко не одно и то же, это скорее ближе к проектированию. И конкретный язык всего лишь средство обучения, а не цель.

    AVK>>Нет, это правильный подход к проектированию, когда лень не мешает это делать. Сколько раз встречаются ref и out в библиотеке фреймворка?


    VD>Много.


    Где? Примеры приведи. Желательно в часто используемых классах, а не в каком нибудь интеропе.

    VD> Но это к делу не относится.


    ИМХО очень даже относится . Показывает реальную применимость ref и out.

    VD>А, ну, зашибись! Тогда учить нужно явно на плюсах. А то и на С. Там как раз минимум неявностей.


    Зато там много аппаратуры, что не есть гуд. Надо искать золотую середину, а не кидаться в крайности.
    ... << RSDN@Home 1.1.4 beta 3 rev. 216>>
    AVK Blog
    Re[21]: Обновление (73 KB)
    От: Schade Россия  
    Дата: 30.10.04 10:14
    Оценка: :)
    Здравствуйте, VladD2, Вы писали:

    VD>Кстати, сдества отладки там тоже потрясающие. И это при бесплатном Эклпипсе и VS Express.


    Ну что ты! Это же еще одна гениальная идея Вирта! Отладчик — это еще более страшное зло, чем "=="!
    ... << RSDN@Home 1.1.4 @@subversion >>
    Re[22]: Мэйнстрим vs. Самосовершенствование :)))
    От: Undying Россия  
    Дата: 30.10.04 10:28
    Оценка: +1
    Здравствуйте, Павел Кузнецов, Вы писали:

    ПК>"Попытки писать в ОО-стиле", если подразумевать под этим напихивание в классы всевозможных "утилит", могут "работать" (*), только если весь код, использующий эти классы, находится под прямым контролем разработчиков обсуждаемых классов. Плюс, разработчики готовы платить за поддержание всех наследников этих классов, что в больших проектах может обходиться достаточно дорого.


    Поэтому за наследование от неабстрактных классов в общем случае нужно пинать ногами.

    ПК>Некоторое время мы живем счастливо, но оказывается, что для некоторых задач принципиальным оказывается вопрос: сохраняет ли используемый алгоритм порядок "одинаковых" с точки зрения упорядочивания элементов. Ну, что ж, следуя заведенному порядку, нам будет нужно добавить метод stable_sort().


    А если возникла задача определять вид сортировки по внутреннему состоянию класса, то что будешь делать? Если Sort это метод класса, то все просто добавляем Sorter sorter = SorterFactory.Create(this, params) и в Array.Sort вызываем sorter.Sort(), а что он там дальше делает не наша проблема. А в случае внешних функций?

    ПК>Однако на этом наши приключения не заканчиваются: ведь у пользователя, в его наследнике Array<>, уже могла быть своя stable_sort(), обладающая несколько иной семантикой, и эта функция "перекроет" определенную в Array<>. В таком случае новые функции, принимающие Array<>, и предполагающие соответствие семантики stable_sort() той, что заявлена в Array<>, правильно работать не будут.


    А вместо наследования от Array делегировать его религия не позволяет?

    ПК>Далее, если представить себе развитие этой иерархии и возможную потребность добавления в будущем утилит типа find(), find_if(), binary_search() и т.п., то становится очевидно, что в классы этой иерархии эти функции также добавлять будет нельзя.


    Можно для них ввести дополнительный интерфейс и реализовывать его будут только те классы, которым это действительно надо.

    ПК>Есть и еще одна сторона этой проблемы: очевидно, что разработчики библиотеки не могут учесть всех нужд пользователей, и снабдить их всеми нужными им "утилитами". Что же делать пользователям? Следуя "попыткам писать в ОО-стиле", им надо будет унаследоваться от класса, который они захотят "расширить", и добавлять в своего наследника нужные им "утилиты". Но на этом пути их ждет масса трудностей.


    Тебя чем не устраивает связка статичные функции + инкапсулирование вызовов этих функций методами класса?

    ПК>Выход простой: тем или иным образом вынести эти "утилиты" за пределы самих классов. В любом случае, никакой необходимости для них быть членами обсуждаемых классов нет, т.к. они прекрасно могут быть реализованы через "основной" интерфейс (а если не могут, то "основной" интерфейс не полон). А в качестве приза мы получим невероятную легкость расширения набора этих "утилит", совершенно не затрагивая клиентов классов рассматриваемой иерархии. Более того, пользователи с той же легкостью смогут пополнять набор "утилит" по такому же принципу.


    Конечно, часть проблем это решает, но часть и создает, так что на серебрянную пулю не тянет.
    ... << RSDN@Home 1.1.2 stable >>
    Re[23]: Мэйнстрим vs. Самосовершенствование :)))
    От: Павел Кузнецов  
    Дата: 30.10.04 12:49
    Оценка:
    Undying,

    по большей части я с тобой согласен. Я и не предлагал вынесение функций из класса в качестве универсального решения всех проблем, в каковые у меня веры нет. Речь шла только о том, что ответ на вопрос о том, принадлежит ли некоторая функция некоторому интерфейсу, не так очевиден, как может показаться с первого взгляда.

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

    ПК>>"Попытки писать в ОО-стиле", если подразумевать под этим напихивание в классы всевозможных "утилит", могут "работать" (*), только если весь код, использующий эти классы, находится под прямым контролем разработчиков обсуждаемых классов. Плюс, разработчики готовы платить за поддержание всех наследников этих классов, что в больших проектах может обходиться достаточно дорого.


    U>Поэтому за наследование от неабстрактных классов в общем случае нужно пинать ногами.


    Еще больше в "классическом ООП" принято "пинать" за работу непосредственно с классом, а не через какой-либо интерфейс. Соответственно, если поступать по канонам, то в приведенном примере иерархию нужно будет чуть-чуть усложнить:
    interface Sequence<T>;
    interface RandomAccessSequence<T>;
    interface List<T> : Sequence<T>;
    interface Array<T> : RandomAccessSequence<T>;
    class ListImpl<T> : List<T>;
    class ArrayImpl<T> : Array<T>;

    соответственно, т.к. клиентам классы ListImpl<T> и ArrayImpl<T> не видны, добавить что-либо типа sort() мы можем только начиная с уровня List<T> или Array<T>, которые конкретными классами не являются, и добавление чего-либо в эти интерфейсы приводит к тем же проблемам, что были рассмотрены при модификации Sequence<T>.

    ПК>> для некоторых задач принципиальным оказывается вопрос: сохраняет ли используемый алгоритм порядок "одинаковых" с точки зрения упорядочивания элементов. Ну, что ж, следуя заведенному порядку, нам будет нужно добавить метод stable_sort().


    U>А если возникла задача определять вид сортировки по внутреннему состоянию класса, то что будешь делать?


    Дык, это уже речь пошла о каких-то более специализированных классах, не столь универсальных, как стандартные Array, List etc. В этих специализированных классах, для которых сортировка является не "утилитой", а основным поведением, сам байт велел делать функцию sort() членом.

    Однако если речь идет об "универсальных" коллекциях, то тут так просто отбиться не получится, т.к. кроме сортировки есть еще очень большое количество вспомогательных алгоритмов, которые хочется иметь возможность для этих коллекций использовать. Более того, множество нужных алгоритмов очень сильно зависит от конкретного применения той или иной коллекции, и, соответственно, заранее предсказать все нужные "утилиты" на уровне, скажем, стандартной библиотеки языка и близко не получится.

    ПК>>Однако на этом наши приключения не заканчиваются: ведь у пользователя, в его наследнике Array<> <...>


    U>А вместо наследования от Array делегировать его религия не позволяет?


    Мне? Более чем Даже еще хуже: моя религия это не только позволяет, но и предписывает. Это я просто позволил себе, быть может, не очень ясно, проехаться по практике наследования для "расширения" функциональности.

    ПК>>Далее, если представить себе развитие этой иерархии и возможную потребность добавления в будущем утилит типа find(), find_if(), binary_search() и т.п., то становится очевидно, что в классы этой иерархии эти функции также добавлять будет нельзя.


    U>Можно для них ввести дополнительный интерфейс и реализовывать его будут только те классы, которым это действительно надо.


    А что мы будем делать, когда нам понадобится новый "универсальный" алгоритм (скажем, partial_sort), которого в стандартном Array нет? Все равно, рано или поздно, мы придем к тому, что нам будут нужны "внешние" по отношению к классу "утилиты".

    ПК>>Есть и еще одна сторона этой проблемы: очевидно, что разработчики библиотеки не могут учесть всех нужд пользователей, и снабдить их всеми нужными им "утилитами". Что же делать пользователям? Следуя "попыткам писать в ОО-стиле", им надо будет унаследоваться от класса, который они захотят "расширить", и добавлять в своего наследника нужные им "утилиты". Но на этом пути их ждет масса трудностей.


    U>Тебя чем не устраивает связка статичные функции + инкапсулирование вызовов этих функций методами класса?


    Методами какого класса, унаследованного от "библиотечного"?

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

    ПК>>Выход простой: тем или иным образом вынести эти "утилиты" за пределы самих классов. <...>


    U>Конечно, часть проблем это решает, но часть и создает, так что на серебрянную пулю не тянет.


    Безусловно. Поэтому надо смотреть в каждом конкретном случае исходя из назначения класса, а не безусловно пихать все подряд нужные "утилиты" в виде членов класса, наравне с "основными" функциями класса. Тем более, выбор того, делать ли некоторую функцию членом класса, не должен базироваться на том, как на это дело смотрит Intellisence ("любой дурак нажав точку в IDE получит их список")
    Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
    Re[24]: Мэйнстрим vs. Самосовершенствование :)))
    От: Undying Россия  
    Дата: 30.10.04 14:27
    Оценка: 18 (1)
    Здравствуйте, Павел Кузнецов, Вы писали:

    ПК>Еще больше в "классическом ООП" принято "пинать" за работу непосредственно с классом, а не через какой-либо интерфейс.


    Согласен.

    ПК> Соответственно, если поступать по канонам, то в приведенном примере иерархию нужно будет чуть-чуть усложнить:

    ПК>
    ПК>interface Sequence<T>;
    ПК>interface RandomAccessSequence<T>;
    ПК>interface List<T> : Sequence<T>;
    ПК>interface Array<T> : RandomAccessSequence<T>;
    ПК>class ListImpl<T> : List<T>;
    ПК>class ArrayImpl<T> : Array<T>;
    ПК>

    ПК>соответственно, т.к. клиентам классы ListImpl<T> и ArrayImpl<T> не видны, добавить что-либо типа sort() мы можем только начиная с уровня List<T> или Array<T>, которые конкретными классами не являются, и добавление чего-либо в эти интерфейсы приводит к тем же проблемам, что были рассмотрены при модификации Sequence<T>.

    Никто не мешает добавить интерфейс ISort и использовать множественное наследование интерфейсов. Но тут понятно есть другая проблема, что в общем виде на каждую комбинацию наследуемых интерфейсов нам нужно иметь свой обобщающий интерфейс, т.е. всего лишь при 4 базовых интерфейсах нам может потребоваться аж 11 обобщающих. В этом пожалуй главный недостаток такого подхода. Его бы можно было нивелировать, если разрешить такое задание типов: IList&IEnumerator&ICollection, но почему-то пока такие конструкции в языки не вводят.

    ПК>Дык, это уже речь пошла о каких-то более специализированных классах, не столь универсальных, как стандартные Array, List etc. В этих специализированных классах, для которых сортировка является не "утилитой", а основным поведением, сам байт велел делать функцию sort() членом.


    Говорим-то мы о общих подходах к проектированию. А при проектировании сплошь и рядом встречаются ситуации, когда мы не знаем будет ли данная функция основным поведением класса или нет, но, если мы эту функцию сделали внутренней, то в случае изменения ее поведения в большинстве случаев достаточно изменить внутренний код класса, код же использующий эту функцию никаких изменений не потребует. Если же использовалась внешняя функция, то при появлении дополнительных требований очень вероятно, что потребуется изменение кода во всех местах, где эта функция вызывалась. Понятно, что исправить код в одном месте гораздо проще, чем во многих местах.

    ПК>Однако если речь идет об "универсальных" коллекциях, то тут так просто отбиться не получится, т.к. кроме сортировки есть еще очень большое количество вспомогательных алгоритмов, которые хочется иметь возможность для этих коллекций использовать. Более того, множество нужных алгоритмов очень сильно зависит от конкретного применения той или иной коллекции, и, соответственно, заранее предсказать все нужные "утилиты" на уровне, скажем, стандартной библиотеки языка и близко не получится.


    Я могу с тобой согласиться, что решение с добавлением Sort'а в коллекции является не совсем концептуально чистым (прежде всего из-за использования реализаций классов, а не минимально нужного набора интерфейсов — в статичном Array.Sort в частности), но пользоваться этими коллекциями очень удобно, так как концептуальная нечистость мешает только в том случае, если мы собираемся использовать свои альтернативные реализации коллекций, а на практике это практически не встречается.

    ПК>Мне? Более чем Даже еще хуже: моя религия это не только позволяет, но и предписывает. Это я просто позволил себе, быть может, не очень ясно, проехаться по практике наследования для "расширения" функциональности.


    Тогда поддерживаю, по сей практике проехаться дело святое.

    U>>Можно для них ввести дополнительный интерфейс и реализовывать его будут только те классы, которым это действительно надо.


    ПК>А что мы будем делать, когда нам понадобится новый "универсальный" алгоритм (скажем, partial_sort), которого в стандартном Array нет? Все равно, рано или поздно, мы придем к тому, что нам будут нужны "внешние" по отношению к классу "утилиты".


    Здесь зависит от отношения количества универсальных алгоритмов к частоте их использования. Если алгоритмов много, но применяются они редко, то внешние методы скорей всего оптимальное решение, если же они применяются сплошь и рядом, то лучше всего будет сделать обертку над тем же ArrayList'ом, которая в том числе будет реализовать и эти дополнительные алгоритмы через какой-нибудь интерфейс.

    U>>Тебя чем не устраивает связка статичные функции + инкапсулирование вызовов этих функций методами класса?


    ПК>Методами какого класса, унаследованного от "библиотечного"?


    Методами класса реализующего в том числе интерфейс библиотечного.

    ПК>"Внешние"/статические функции меня устраивают. Я только не вижу оснований делать к ним переходники в виде членов класса, пока это поведение не становится ключевым для рассматриваемых объектов. В частности, сортировка или поиск "универсальных" массивов и списков, на мой взгляд, к таковым не относятся.


    Здесь не согласен, переходниками пользоваться гораздо удобнее. И если проводить аналогию с реальным миром, то можно заметить, что все действия в нем направлены именно непосредственно на объект, скажем физические законы можно считать внешними функциями, а шарик объектом. Мы же не говорим, эй физические законы толкните шарик с силой F, а говорим просто толкнем шарик с силой F, а там уже шарик сам разбирается каким физическим законам он сейчас подчиняется и как он должен на это отреагировать.

    ПК>Безусловно. Поэтому надо смотреть в каждом конкретном случае исходя из назначения класса, а не безусловно пихать все подряд нужные "утилиты" в виде членов класса, наравне с "основными" функциями класса. Тем более, выбор того, делать ли некоторую функцию членом класса, не должен базироваться на том, как на это дело смотрит Intellisence ("любой дурак нажав точку в IDE получит их список")


    Но фактор Intellisence при этом не нужно сбрасывать со счетов при принятии решения.
    ... << RSDN@Home 1.1.2 stable >>
    Re[25]: Мэйнстрим vs. Самосовершенствование :)))
    От: Павел Кузнецов  
    Дата: 30.10.04 16:22
    Оценка: +2
    Undying:

    > ПК> Дык, это уже речь пошла о каких-то более специализированных классах, не столь универсальных, как стандартные Array, List etc. В этих специализированных классах, для которых сортировка является не "утилитой", а основным поведением, сам байт велел делать функцию sort() членом.


    > Говорим-то мы о общих подходах к проектированию.


    +1. Я только хотел подчеркнуть, что, с моей точки зрения, общим подходом должно быть принятие подобных решений в частном порядке. И иногда, учитывая все за и против, прибегать к более сложным паттернам, а не просто ограничиваться наследованием и "ужирнением" интерфейсов (interface bloat), "пытаясь писать в ОО-стиле".

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


    В качестве одного из предусловий я упомянул, что код, использующий классы, разработчиками напрямую не контролируется. Соответственно, к изменению семантики некоторой функции в библиотечном классе я отношусь крайне настороженно, представляя последствия замены использования в методе Array.sort() функции stable_sort() (сохраняющей порядок "одинаковых" элементов) на более быструю (в некоторых случаях) quick_sort()... В этих условиях, имхо, изменение семантики как раз совершенно недопустимо.

    Да и в других случаях, когда весь код в нашем распоряжении, мы далеко не всегда готовы проводить анализ всех использований, необходимый для подобных "молчаливых" изменений семантики базовых компонент. Я бы скорее согласился, если бы были изменены места, где используется данный класс. Естественно, если все было спроектировано на совесть, дублирования аналогичных вызовов Array.sort быть не должно, т.к. доступ к некоторому Array должен контролироваться одним классом, и, соответственно, в данном классе, представляющем абстракцию уровня кода приложения, изменять семантику подобных методов вполне допустимо. Воздействие таких изменений, в отличие от правки общих компонент, ограничены и предсказуемы, т.к. соответствующая сортировка привязана к контексту, и ожидания от нее хорошо известны.

    > Я могу с тобой согласиться, что решение с добавлением Sort'а в коллекции является не совсем концептуально чистым (прежде всего из-за использования реализаций классов, а не минимально нужного набора интерфейсов — в статичном Array.Sort в частности), но пользоваться этими коллекциями очень удобно <...>


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

    > если же они применяются сплошь и рядом, то лучше всего будет сделать обертку над тем же ArrayList'ом, которая в том числе будет реализовать и эти дополнительные алгоритмы через какой-нибудь интерфейс.


    Ага. А теперь представляем ситуацию, что часть ArrayList'ов мы получаем в "готовом" виде, из библиотечных функций, а часть, своих оберток, создаем сами... Боюсь, в результате, работать по-разному с по-сути одинаковыми контейнерами будет не очень удобно. Впрочем, здесь каждый сам для себя выбирает...

    > если проводить аналогию с реальным миром, то можно заметить, что все действия в нем направлены именно непосредственно на объект, скажем физические законы можно считать внешними функциями, а шарик объектом. Мы же не говорим, эй физические законы толкните шарик с силой F, а говорим просто толкнем шарик с силой F, а там уже шарик сам разбирается каким физическим законам он сейчас подчиняется и как он должен на это отреагировать.


    Я придерживаюсь концепции, рекомендующей по умолчанию помещать методы в класс, являющий субъектом, а не объектом действия, предоставляя в объекте достаточный для реализации намерений субъекта интерфейс. В частности, возвращаясь к твоему примеру, мы говорим: "(Мы) толкнем шарик с силой F" (скорее, даже: "Передать шарику импульс P") Но это уже софистика, т.к. в том, что у шарика неизбежно будет какой-то метод, принимающий воздействие, ты, естественно, прав.
    Posted via RSDN NNTP Server 1.9 gamma
    Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
    Re[26]: Мэйнстрим vs. Самосовершенствование :)))
    От: Павел Кузнецов  
    Дата: 30.10.04 16:54
    Оценка:
    > Я придерживаюсь концепции, рекомендующей по умолчанию помещать методы в класс, являющий субъектом, а не объектом действия, предоставляя в объекте достаточный для реализации намерений субъекта интерфейс. В частности, возвращаясь к твоему примеру, мы говорим: "(Мы) толкнем шарик с силой F" (скорее, даже: "Передать шарику импульс P") Но это уже софистика, т.к. в том, что у шарика неизбежно будет какой-то метод, принимающий воздействие, ты, естественно, прав.

    P.S. Кстати, хорошую аналогию ты подобрал. Представим, что нам часто приходится катать шарик по кругу. С моей точки зрения функция go_round интерфейсу шарика не принадлежит, являясь наглядным примером вспомогательного алгоритма, который стоит реализовать через "основной" интерфейс "шарика" (accept_impulse etc.).
    Posted via RSDN NNTP Server 1.9 gamma
    Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
    Re[13]: Обновление
    От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
    Дата: 31.10.04 05:23
    Оценка: +1
    Здравствуйте, Сергей Губанов, Вы писали:

    VD>>Существенный недостаток есть. Язык не применим на практике. Он мертв. И учить на нем детей — значит обрекать их на дополнительное самообучение с переламыванием себя.

    СГ>Я уже не раз указывал Вам на Вашу некомпетентность в этом вопросе. Но Вы продолжаете повторяться вновь и вновь.

    Извини, дорогой. Если ты компетентен, то не грех указать на конкретные ошибки собеседника. А так, доверие к тебе ещё сильнее падает. Знаешь, орать "сам дурак" мы все умеем.
    ... << RSDN@Home 1.1.3 stable >>
    Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
    P.S.: Винодельческие провинции — это есть рулез!
    Re[12]: Обновление
    От: Геннадий Васильев Россия http://www.livejournal.com/users/gesha_x
    Дата: 31.10.04 05:23
    Оценка:
    Здравствуйте, Сергей Губанов, Вы писали:

    ПК>>Никаких очевидных существенных преимуществ Оберона & co. в качестве учебного языка перед многими другими не заметно


    СГ>Это Вы их в упор не видите. Существенные преимущества неоднократно демонстрировались, главными из которых является то, что Оберон — простой язык (минимальный и достаточный) и высоко-дисциплинирующий программиста.


    Ну да, "дисциплинирующий"! Со встроенным GC-то... От уж дисциплина так дисциплина.

    На самом деле, существенные преимущества языка програмирования с точки зрения обучения можно продемонстрировать, только если сопоставить успехи программистов, обучавшихся на Oberon с программистами, обучавшимися на C#, Python, C++, Java, Pascal. Только так и не иначе. Поверь, подпись "Никлаус Вирт" — это просто подпись "Никлаус Вирт". Ничего более. Нету никакой ложки.

    СГ> На счет дисциплины — лично мной было указано, на то как высококвалифицированные программисты, но воспитанные на Си-подобных языках, пишут элементарный код циклов используя либо несколько continue либо вспомогательные переменные, хотя и то и другое излишне, так как можно написать тоже самое более просто (дисциплинированно).


    Разворачивание последовательности IF-THEN-ELSE так, что она с трудом помещается в экран, это что? Метод решения задачи, по-твоему?

    СГ> То есть даже высококвалифицированные специалисты способны блуждать в трех соснах, что было бы не возможно будь они воспитаны на высокодисциплинирующих языках каковыми являются обероны.


    Это "блуждание в трёх соснах" — всего лишь игра иллюстрации. И нельзя делать из неё каких-то выводы космического масштаба и космической же... э... ну ты понял. (c) профессор Преображенский.
    ... << RSDN@Home 1.1.3 stable >>
    Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
    P.S.: Винодельческие провинции — это есть рулез!
    Re[20]: Читайте хелп
    От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
    Дата: 31.10.04 07:12
    Оценка:
    Здравствуйте, Mamut, Вы писали:

    M>...невозможно работать.


    Из приведенного рисунка видно следующее:

    1) Вы зачем-то засунули в Log текст программы и пару контролов... Log — предназаначен для вывода сообщений.
    2) Для того чтобы написать свою программу нужно создать новый документ.
    3) Контролы, вообще-то, принято помещать на формы.

    Вывод:
    Почитайте хелп.
    Re[18]: Обновление
    От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
    Дата: 31.10.04 07:22
    Оценка: :))) :))
    Здравствуйте, Кодт, Вы писали:

    К>Значит, ты мало трахался с системами контроля версий.


    Я неоднократно указывал на то что обероны, еще со времен Модулы, являются модульными языками. Но тут, видимо, никто не понимает что это такое. Вот и Вы тоже туда же. Не понимаете в чем состоит преимущество модульности... С модулями не возникает проблем контроля версий. Новому модулю дается новое имя (так же как с COM-интерфейсами). В конце концов, модуль — маленький. 1-модуль : 1-программист. Несколько программистов в один и тот же модуль код никогда не пишут.
    Re[16]: Обновление
    От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
    Дата: 31.10.04 07:25
    Оценка: :))) :)
    Здравствуйте, VladD2, Вы писали:

    VD>Ага. Вместо того чтобы сделать подсветку синтаксиса.



    Нужна — сделай сам. Все в твоих руках. Система открытая.
    Полярная лиса...
    От: Зверёк Харьковский  
    Дата: 31.10.04 07:33
    Оценка: +6 :))) :)
    Здравствуйте, Сергей Губанов, Вы писали:

    К>>Значит, ты мало трахался с системами контроля версий.


    СГ>Я неоднократно указывал на то что обероны, еще со времен Модулы, являются модульными языками. Но тут, видимо, никто не понимает что это такое. Вот и Вы тоже туда же. Не понимаете в чем состоит преимущество модульности... С модулями не возникает проблем контроля версий. Новому модулю дается новое имя (так же как с COM-интерфейсами). В конце концов, модуль — маленький. 1-модуль : 1-программист. Несколько программистов в один и тот же модуль код никогда не пишут.


    Не, ну я так больше не могу!
    Отладчик — порочно; контроль версий — порочно; подсветку синтаксиса — сделай сам...

    И эти люди...
    Я фигею, дорогая редакция...
    сам слушаю и вам рекомендую: Разные Люди — Она не вышла замуж
    FAQ — це мiй ай-кью!
    Re[14]: Обновление
    От: Сергей Губанов Россия http://sergey-gubanov.livejournal.com/
    Дата: 31.10.04 07:35
    Оценка:
    Здравствуйте, Геннадий Васильев, Вы писали:

    ГВ>Здравствуйте, Сергей Губанов, Вы писали:


    VD>>>Существенный недостаток есть. Язык не применим на практике. Он мертв. И учить на нем детей — значит обрекать их на дополнительное самообучение с переламыванием себя.

    СГ>>Я уже не раз указывал Вам на Вашу некомпетентность в этом вопросе. Но Вы продолжаете повторяться вновь и вновь.

    ГВ>Извини, дорогой. Если ты компетентен, то не грех указать на конкретные ошибки собеседника. А так, доверие к тебе ещё сильнее падает. Знаешь, орать "сам дурак" мы все умеем.


    Простите великодушно, но я так много раз уже приводил ссылки, что на этот раз решил их уже не приводить.
    Но если Вы настаиваете, пожалуйста, вот ссылочка, из которой можно почерпнуть что обероны не мертвы

    http://cern.ch/oberon.day

    Свежие аэрокосмические новости: http://www.prweb.com/releases/2004/9/prweb160825.php (Excelsior — (бывший XDS) разработчик компиляторов модулы и оберона).

    Кстати, сам не давно узнал — на Обероне-2 написана ОС реального времени XO/2, управляющая роботами контроля дорожного движения, расставляемыми в данный период по всей Швейцарии.

    Да Вы и сами можете покопаться в интернете по ключевым словам Modula, Oberon...
    Re[22]: Обновление (73 KB)
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 31.10.04 08:19
    Оценка: :)
    Здравствуйте, Schade, Вы писали:

    S>Ну что ты! Это же еще одна гениальная идея Вирта! Отладчик — это еще более страшное зло, чем "=="!


    Незнал. Жаль, что он не слыашал о функциональных языках. Глядишь все его бредовые идеи обрели бы осмысленность. В них какраз реализуются все за чем он гонется. Вот только понимать их для многих сложновато.
    ... << RSDN@Home 1.1.4 beta 3 rev. 207>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[22]: Мэйнстрим vs. Самосовершенствование :)))
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 31.10.04 08:19
    Оценка:
    Здравствуйте, Павел Кузнецов, Вы писали:

    ПК>Может это, конечно, (для некоторых) и прикол, но сортированные очереди проходят в курсе изучения Computer Science, а некоторым — о ужас! — даже рассказывают о priority sorted queue


    Приоритеты и процесс сортировки разные веши. Не находишь? А метод сортировки для очереди — это бред. Даже если в очереди данные хранятся упорядоченно, для этого используются механизмы отличные от сортировки.

    Кстати, по версии гугля "priority sorted queue" встречается 15 раз. В то время как "priority queue" примерно 59800 раз. Так что это это просто некорерктное использование терминов. И попытка выдать это дело за факты.

    >> Ну, а теперь представь, что кое-то на ООЯ пытается писать в ОО-стиле. Может они конечно и не удовлетворяют твоей концепции, но пользоваться плодами их трудов чертовски удобно.


    ПК>Это не "мои" концепции, это один из классических критериев качества объектно-ориентированного дизайна.


    Ты ее навязываешь, значит она твоя. К ОО-дизайну она в общем-то отношения не имеет. Более того. В твоем видение она даже становится вредной, так как отрицает базовые концепции вроде инкапсуляции.

    >> По теории ООП все методы объекта желательно помещать в его класс.


    ПК>Это в некотором роде тавтология,


    Для тех кто не понял объясняю — это сарказм.


    ПК> т.к. методы объекта по определению есть функции-члены соответствующего класса. Вопрос как раз в том, в какой степени та или иная функция принадлежит интерфейсу класса.


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

    ПК>Если же ты утверждаешь, что все действия, которые можно производить с объектом, должны быть представлены функциями-членами соответствующего класса, то теория ООП подобных постулатов не содержит. Напротив, при обсуждении качества ОО дизайна, в конечном итоге, так или иначе речь заходит о минимизации интерфейсов. И тому есть веские причины.


    Это не теории ООП. Это теория, которую ты пытаешься неверно применить. Включить все действия над объектом в его класс просто физически тяжело. К тому же некоторые действия могут затрагивать и другие объекты. Однако, требования инкапсуляции говорят о том, что желательно включать методы внутрь объекта.

    Простенький примерчик (хотя странно, что тебе его нужно приводить). Конечно, многие вычисления можно произвести и на универсальных объектах. Так для хранения времени прекрасно подходят целые или числа с плавающей точной. Все вычисления можно реализовать отдельными функциями, обосновав это применением метода "отрытый-клозет" , ну, например:
    int AddMinutes(int time, int minutes)
    {
        return time + minutes * 60;
    }

    и это будет работать... до той поры пока какой-нибудь неосторожный отоваришь, не залепит в программе, например, такой код:
    int time = InitTime(1, 2, 0);
    time = AddKilometres(2);


    Я, конечно, утрирую, но все же... ООП и был придуман для решения подобных проблем. Ты, конечно, можешь сказать, что тут проблема в том, что я не использовал выделенного типа. Но я отвечу, что основная проблема тут в нарушении правил инкапсуляции.

    Внешние функции той же сортировки — это как раз такое нарушение. Ведь я потенциально могу применить функцию сортировки к объекту, который не должен поддерживать данного действия (метода). Так моя очередь или стэк может хранить данные в определенном порядке (смешно было бы, если это было бы не так!). Интерфейс этих классов походит для применения внешнего метода сортировки. Значит где-то в глучинах кода кто-то может совершенно случайно применить ее к этим объектам. И получается та же самая ошибка, что и в приведенном выше примитивном примере. А почему? Да потому-что кто-то с очень умным выражением лица обосновал то что даная операция не должна быть методом класса, а является универсальной опрации. Другими словами схватил превую попавшуюся концепцию и применил ее случайным образом. Другими словами он поступил с концепцией, так же как я с приведенной выше функций — использовал ее не по назначению.

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


    Попытки прилепить ярлык бедумного распихивания не крассят аппонента. Давай как исходить из того, что аппоненты тоже не дети и немного понимают в том, что они делают. А бездумные дейсвия могут привести к нужному результату только случайно. Тут я спорить не буду.

    ПК> Плюс, разработчики готовы платить за поддержание всех наследников этих классов, что в больших проектах может обходиться достаточно дорого.


    "Может" — вряд ли должно являться критерием доказательства. С тем же успехом и безапелляционностью могу заявить, что грамотное проектирование иерархии наследования может существенно сократить затраты на поддержку больших проектов. Собственно в отличии от твоего заявления мое подтверждается теорией и практикой ООП. Для того ООП и создавался.

    ПК> В противном случае, рано или поздно, новые "утилиты" в сами классы добавлять становится невозможным.


    Опять таки заменим ярлык "утилиты", но подобающее название методы — и станет очевидно, что твои слова не более чем профанация идей ООП.

    ПК>Например, представим, что у нас есть скелет простенькой иерархии "библиотечных" контейнеров (псевдокод):

    ПК>
    ПК>interface Sequence<T>
    ПК>{
    ПК>   class Pos;
    
    ПК>   uint size();
    ПК>   bool is_empty();
    
    ПК>   Pos begin();
    ПК>   Pos end();
    ПК>   Pos next(Pos);
    
    ПК>   Pos insert(Pos, T);
    ПК>   void remove(Pos);
    ПК>   void clear();
    ПК>};
    
    ПК>interface RandomAccessibleSequence<T> : Sequence<T>
    ПК>{
    ПК>   T operator[](int index);
    ПК>   Pos pos_by_index(int index);
    ПК>};
    
    ПК>class List<T> : Sequence<T>
    ПК>{
    ПК>};
    
    ПК>class Array<T> : RandomAccessibleSequence<T>
    ПК>{
    ПК>};
    ПК>


    ПК>Итак, мы хотим иметь возможность эти контейнеры сортировать. Допустим, мы решили добавить метод sort() в интерфейс Sequence<>.


    Не ожидал от тебя столько детский ошибок в таком простом варианте.
    В описанной выше иерархии имеется класс RandomAccessibleSequence — это подразумевает, что ее предок не должен иметь средств прямого доступа к последовательности, а значит методы:
    Pos next(Pos);
    Pos insert(Pos, T);
    void remove(Pos);

    не имеют права находиться в этом классе.

    Более того. Под большим вопросом находится целесообразность размещения в последовательности методов:
    uint size();
    bool is_empty();
    Pos begin();
    Pos end();

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

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

    Кстати, в .NET-е иерархия хотя и не идеальна, но значительно чище твоей:
    public interface IEnumerator<T> : IDisposable
    {
        bool MoveNext();
        T Current { get; }
    }
    
    public interface IEnumerable<T>
    {
        IEnumerator<T> GetEnumerator();
    }
    
    public interface ICollection<T> : IEnumerable<T>
    {
        // Methods
        void Add(T item);
        void Clear();
        bool Contains(T item);
        void CopyTo(T[] array, int arrayIndex);
        bool Remove(T item);
        
        // Properties
        int Count { get; }
        bool IsReadOnly { get; }
    }
    
    public interface IList<T> : ICollection<T>, IEnumerable<T>
    {
        // Methods
        int IndexOf(T item);
        void Insert(int index, T item);
        void RemoveAt(int index);
        
        // Properties
        T this[int index] { get; set; }
    }
    
    // Внимание! Реализация! :)
    public class List<T> : IList<T>, IList
    {
        ...
        public T Find(Predicate<T> match);
        public List<T> FindAll(Predicate<T> match);
        public int FindIndex(Predicate<T> match);
        public int FindIndex(int startIndex, Predicate<T> match);
        public int FindIndex(int startIndex, int count, Predicate<T> match);
        public T FindLast(Predicate<T> match);
        ...
        public void Sort();
        public void Sort(IComparer<T> comparer);
        public void Sort(Comparison<T> comparison);
        public void Sort(int index, int count, IComparer<T> comparer);
        ...
    }


    Будь моя воля я бы выделил понятие модифицируемой коллекции в отдельную иерархию. Но ребята делающие Framework решили, что достаточно будет runtime-проверок.

    Однако даже в таком виде это шедевр ОО-дизайна по сравнению с СТЛ которую ты тут защищаешь.

    ПК>Некоторое время мы живем счастливо, но оказывается, что для некоторых задач принципиальным оказывается вопрос: сохраняет ли используемый алгоритм порядок "одинаковых" с точки зрения упорядочивания элементов. Ну, что ж, следуя заведенному порядку, нам будет нужно добавить метод stable_sort().


    ПК>Но как только мы подобным образом изменим интерфейс Sequence<>, все его наследники "сломаются", т.к. у них-то реализации stable_sort() не будет.


    Гы. Видимо это от безграмотного проектирования. Собственно об этом сказано выше.

    ПК> Кроме того, есть ли уверенность, что для всех "последовательностей" существуют эффективные методы их сортировки с сохранением порядка "одинаковых" с точки зрения упорядочивания элементов?


    [i]Паша, наделав таких детских ошибок в проектировании иерархии классов, ты задаешься столь не детскими вопросами. Может они не спроста сделаны? [/q]
    Конечно, нет! Последовательность обеспечивающая прямой (другими словами эффективный) доступ к своим элементам, а так же обеспечивающая модификацию своих элементов без создании копии (а это ведь еще не факт!) позволяет реализовать сортировку значительно эффективнее. Более того еще большой вопрос нужно ли вводить метод сортировки в последовательность не обеспечивающую прямой доступ. И еще более того... совсем не ясно зачем вводить метод сортировки в базовый интерфейс?

    ПК> Ведь у пользователя могут быть свои наследники интерфейса Sequence<T>, о реализации которых мы ничего не знаем... Соответственно, этого делать нельзя, и придется поступать как-нибудь по-другому.


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

    Так вот то, что их не стоит вводить в интерфейсы еще не означает, что их не стоит вводить в классы. Класс, реализующий список на базе массива, очень даже может иметь подобный метод. И так как этот метод полностью реализован, то проблем у наследников не будет. Только нужно подумать о том, можно ли делать наследником массива, например, стек. Ведь для стека операция сортировки полный маразм! Стало быть, если сек реализуется на базе массива, то логичнее было бы включить массив в качестве приватного члена, а не делать стек наследником массива.

    ПК>Хорошо, скажем, мы, понимая эту проблему, интерфейс Sequence<> менять не будем, а вместо этого добавим stable_sort(), скажем, в Array<>, где он более всего нужен, и будет иметь реализацию по-умолчанию, на случай, если кто-то от Array<> унаследовался.


    Ну, слава буогу.

    ПК>Однако на этом наши приключения не заканчиваются: ведь у пользователя, в его наследнике Array<>, уже могла быть своя stable_sort(), обладающая несколько иной семантикой, и эта функция "перекроет" определенную в Array<>. В таком случае новые функции, принимающие Array<>, и предполагающие соответствие семантики stable_sort() той, что заявлена в Array<>, правильно работать не будут.


    Хочу тебя расстроить. Этот случай никакого отношения к "утилитам", как ты изволил выразиться, не имеет. Введение любого нового метода чревато подобными проблемами. Если ты забудешь ввести в в свою последовательность, например, метод Add, то уверяю тебя ты нарвешся на те же самые проблемы. К тому же — это не такая большая проблема. Нормальные языки программирования поддерживают перекрытие и максимум что мы получим — это варнинг (в Шарпе и ничего в C++), который успешно уберем за пару минут.
    Реализовав подобные методы как статические ты так же можешь нарваться на перекрытие. Это неизбежно. Мир не идеален.

    ПК>Далее, если представить себе развитие этой иерархии и возможную потребность добавления в будущем утилит типа find(), find_if(), binary_search() и т.п., то становится очевидно, что в классы этой иерархии эти функции также добавлять будет нельзя.


    Возможно Find, BinarySearch и т.е. можно назвать утилитами, так как они не модифицируют состояние. Но это такие же методы. Ты прав, что их не стоит засовывать в базовые абстрактные интерфейсы. Но то, что они должны быть методами классов-реализаций лично у меня даже не возникает сомнений. Если есть большое сомнение, что данные методы целесообразно делать экземплярными, то хотя бы можно сделать их статическими. Это точно никому не повредит.

    ПК>Есть и еще одна сторона этой проблемы: очевидно, что разработчики библиотеки не могут учесть всех нужд пользователей, и снабдить их всеми нужными им "утилитами". Что же делать пользователям? Следуя "попыткам писать в ОО-стиле", им надо будет унаследоваться от класса, который они захотят "расширить", и добавлять в своего наследника нужные им "утилиты". Но на этом пути их ждет масса трудностей.


    Зачем им наследоваться? Наследование это инструмент который нужно применять по месту. Они могу просто добавить нужные методы. Если нужно соблюсти неизменность интерфейса, то нет проблем ввести расширенный интерфейс и реализовать его.

    [В общем, надоело отвечать на выводы сделанные на базе неверных предпосылок и трактовок принципов ООП, так что остальное поскипано.]

    ПК>Выход простой: тем или иным образом вынести эти "утилиты" за пределы самих классов. В любом случае, никакой необходимости для них быть членами обсуждаемых классов нет, т.к. они прекрасно могут быть реализованы через "основной" интерфейс (а если не могут, то "основной" интерфейс не полон). А в качестве приза мы получим невероятную легкость расширения набора этих "утилит", совершенно не затрагивая клиентов классов рассматриваемой иерархии. Более того, пользователи с той же легкостью смогут пополнять набор "утилит" по такому же принципу.


    Вывод неверный. Думаю, мои комментарии это хорошо продемонстрировали. При создании методов нужно соблюдать принципы: инкапсуляции, полиморфизма и, конечно, целесообразности. Последний принцип конечно важен, но он не должен нарушать первые два. Иначе получится сортируемые стеки и тому подобная фигня.

    Теперь что касается необходимости. Бесспорно, методы, не изменяющие состояние объекта можно выносить куда угодно. Более того, метода, не относящегося к самым базовым, вообще можно не делать. Вот только классы делаются для того чтобы потом ими пользовались люди. А стало быть, довольно неразумно выносить их черти куда. Это приводит к тому, что писать код становится значительно сложнее. И к тому, что приходится хранить в краткосрочной памяти слишком много деталей. А ведь все парадигмы программирования как раз и создаются для того чтобы уменьшить количество информации которых человек должен хранить у себя в голове. Попробую проиллюстрировать это на простом примере. Когда я пишу программу на C#, то все вокруг меня является объектом! Это не громкие слова. Это большое достижение в области поднятия уровня языка. Для того чтобы найти нужные средства работы с некоторыми объектами (функции для работы с переменными в терминологии С/С++) у меня есть выбор нажать точку после имени объекта, или полезть в документацию. Да и в документации одно дело искать методы некоторого класса, а другое функции применимые к этому классу в принципе. Даже если метод реализован как статический, я обязан всего лишь убедится, что нужного метода нет в интерфейсе используемого мной объекта, после чего попробовать поискать его в соответствующем классе. Например, для С++ перевод строки в целое будет таким же убогим как и для замшелого С:
    string str("123");
    int val = atoi(str.c_str);

    А на более высокоуровневых языках это будет выглядеть немного иначе.
    C#:
    string str = "123";
    int val = int.Parse(str);

    Ruby (и еже с ним):
    str = "123";
    val = str.to_i;

    казалось бы, какая разница? Я и сам написал все примеры по памяти. Но, черт побери, зачем мне знать все эти мелочи, если среда способна мне подсказать всю необходимую информацию?! Мы же, в конце концов, живем в 21 веке! А ведь ситуации могут быть куда сложнее. Так что подобная помощь была бы полезна не только новичкам.

    В обещем, ООП — это концепция не только утилитарная. Это конецпция цель которой поднять уроветь разработки ПО. И исходя из этой концепции смотреть на вещи только из соображений необходимости ("никакой необходимости!") подразумевая под этим "можно не применять значит нужно не применять" является большой ошибкой! Необходимость хотя бы в том, что это упрощает написание кода и его восприятие. Зачастую класс поясняет значение метода. Зачастую сочетание типа и члена дают куда больше информации чем имя отдельной фукции.

    Корче, мой вывод таков: Методами нужно делать наиболее часто используемые, а значит нужные наибольшему количеству народа, функции. Конечно, при этом нужно думать что творишь. Наличие возможности еще не означает, что ее обязательно нужно использовать. Да и требований дизайна оно не отменяет. Однако засовывать методы черти куда и оправдывать это за ухо притянутыми принципами проектирования тоже не дело.


    PS

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

    Тот же метод сортировки в интрефейсе такой обшщей абстракции как последовательность действительно не совсем. Однако в реализации вроде динамического массива он вполне уместен. Может быть дело в этом? Классы к которым ты привык, да и вообще очень многии примеры C++-дизайна не очень то жалуют программистов подобным разделением. Его можно увидить пожалуй что в ATL.
    ... << RSDN@Home 1.1.4 beta 3 rev. 207>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[24]: Мэйнстрим vs. Самосовершенствование :)))
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 31.10.04 08:42
    Оценка: -1
    Здравствуйте, Павел Кузнецов, Вы писали:

    ПК>по большей части я с тобой согласен. Я и не предлагал вынесение функций из класса в качестве универсального решения всех проблем,


    Нет, Паша, именно это ты и предлагаешь. И именно это звучит очень смешно. В попытках обосновать дизайн СТЛ ты готов весь ООП вверх ногами перевернуть.

    ПК>Речь шла только о том, что ответ на вопрос о том, принадлежит ли некоторая функция некоторому интерфейсу, не так очевиден, как может показаться с первого взгляда.


    Приятно, что в твоих словах уже появились термины интерфейс и исчезает безапелляционность. Глядишь, мы тебя потихоньку убедим, что наличие методов в классах — это хорошо. А независимые функции — это прискорбная плата за простоту и универсальность.

    ПК>Безусловно. Поэтому надо смотреть в каждом конкретном случае исходя из назначения класса, а не безусловно пихать все подряд нужные "утилиты"


    О! Ну, почти прогресс.
    ... << RSDN@Home 1.1.4 beta 3 rev. 207>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[26]: Мэйнстрим vs. Самосовершенствование :)))
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 31.10.04 08:42
    Оценка:
    Здравствуйте, Павел Кузнецов, Вы писали:

    Ну, что же... Маладец Андайинг. Если прочесть это
    Автор: Павел Кузнецов
    Дата: 30.10.04
    и это
    Автор: Павел Кузнецов
    Дата: 30.10.04
    , разница ощущается очень хорошо. Ндо бы товарищу Андаингу еще немного над тобой поработать, и глядишь ты начнешь тих недолюбливать STL и по ночам программировать на C# (для души).
    ... << RSDN@Home 1.1.4 beta 3 rev. 207>>
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.