Re: Трудные для ООП иерархии классов
От: Трурль  
Дата: 21.09.07 06:58
Оценка: 11 (2)
Здравствуйте, Кодёнок, Вы писали:

Кё>Я ищу примеры классов объектов, которые вызывают трудности при объектном моделировании в современных ЯП типа C++ или C#.


Мне кажется, трудности во многом вызываются использованием единого механизма наследования для выражения совершнно разных отношений:
  • тип — подтип;
  • структура — расширение cтруктуры;
  • интерфейс — реализация;
  • интерфейс — расширеный интерфейс;
  • используемый интерфейс — реализация;
  • реализация — точно такая же, но с перламутровыми пуговицами;
  • ...
  • Re: Трудные для ООП иерархии классов
    От: Sm0ke Россия ksi
    Дата: 21.09.07 07:17
    Оценка:
    Здравствуйте, Кодёнок, Вы писали:

    Кё>Я ищу примеры классов объектов, которые вызывают трудности при объектном моделировании в современных ЯП типа C++ или C#. В качестве примера, я знаю всего две классические задачи:


    Кё>1. Построить иерархию классов для прямоугольника и квадрата, с изменяющимися размерами. Трудность в том, что хотя квадрат всегда является прямоугольником (наследуется), он не захочет наследовать его реализацию (зачем ему раздельно хранить ширину и высоту?). Другая трудность в том, что прямоугольник иногда может являться квадратом, но классические отношения между классами, которые предлагают ЯП (наследование и implicit-conversion) не могут выразить такого отношения.


    Хотя квадрат и является частным случаем прямоугольника, это не значит, что выгодно его наследовать от прямоугльника.
    Скорее наоборот. Square : Figure { width }; Rectangle : Figure { width, height }; Figure { location, rotation, scale };
    Тут замечаем, что у фигур есть общие поля. Теперь мы можем:

    либо (1) завести для них общий базовый класс with_width { width };
    И тогда Square : Figure, with_width {}; Rectangle : Figure, with_width { height };

    либо (2) отнаследовать прямоугольник от квадрата на прямую Square : Figure { width }; Rectangle : Figure, Square { height };


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

    // параметры
    interface IParam
    {};
    
    struct text_param : IParam
    {};
    
    struct number_param : IParam
    {};
    
    // ПараметрЫ. указатель на список параметров ( на хэш )
    typedef map<string, IParam *> * Params;
    
    // фигура (не абстрактный класс!)
    struct Figure
    {
     Prototype * htype;
     Params hparams;
    };
    
    Figure::constructor(Prototype * hnew_type)
    {
      htype= hnew_type;
      hparams= htype->get_default_params();
    }
    
    Figure::draw()
    {
     htype->draw(this);
    }
    
    Figure::serialize(Archive & ar)
    {
      // сериализим имя типа фигуры
      // сериализим список параметров
    }
    
    // прототипы фигур
    struct Prototype abstract
    {
      virtual draw(Figure *) abstract;
      virtual Params get_default_params() abstract;
      virtual string get_name() abstract;
    };
    
    // прототип прямоугольника
    struct type_rect : Prototype
    {
      draw(Figure * hf)
      { /*рисуем прямоугольник*/ }
    
      // другие методы
    };
    
    // тут нужны другие типы фигур ...
    
    // картинка
    struct Picture
    {
      // тут список фигур
      // методы:
      draw(); // рисуем фигуры
      serialize(...); // сериализим фигуры
      add_figure(...);
      del_figure(...);
      // и т.д.
    };


    Квадрат имеет один параметр "сторона" ("side");
    У прямоугольника "width", "height";
    У круга "radius";

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

    Тут не фигура сама себя рисует. За действия над фигурами отвечают их прототипы.
    Таким образом типы фигур не hard-coded, а могут загружаться из библиотек типов.
    т.е. Тип фигуры можно тоже сериализовать.
    Re: Трудные для ООП иерархии классов
    От: Delight  
    Дата: 21.09.07 07:52
    Оценка:
    Здравствуйте, Кодёнок, Вы писали:

    Кё>1. Построить иерархию классов для прямоугольника и квадрата, с изменяющимися размерами. Трудность в том, что хотя квадрат всегда является прямоугольником (наследуется), он не захочет наследовать его реализацию (зачем ему раздельно хранить ширину и высоту?).


    Потому что у него есть и ширина, и высота. По мне так можно унаследовать квадрат от прямоугольника, переопределить сеттеры и при изменении одной стороны автоматически изменять другую.
    ... << RSDN@Home 1.2.0 alpha rev. 726>>
    Re[2]: Трудные для ООП иерархии классов
    От: konsoletyper Россия https://github.com/konsoletyper
    Дата: 21.09.07 07:53
    Оценка:
    Здравствуйте, Sm0ke, Вы писали:

    [skip]

    Изобретаем Self?
    ... << RSDN@Home 1.2.0 alpha rev. 710>>
    Re[2]: Трудные для ООП иерархии классов
    От: konsoletyper Россия https://github.com/konsoletyper
    Дата: 21.09.07 07:58
    Оценка:
    Здравствуйте, Delight, Вы писали:

    D>Потому что у него есть и ширина, и высота. По мне так можно унаследовать квадрат от прямоугольника, переопределить сеттеры и при изменении одной стороны автоматически изменять другую.


    Наследование класса — это сахар. Да и определение класса — тоже сахар; по сути происходит описание интерфейса и его же реализация. Вот если иммутабельные Rectangle и Square реализуют интерфейс IRectange, то проблем не возникает. А зачем нужны мутабельные Rectangle и Square?
    ... << RSDN@Home 1.2.0 alpha rev. 710>>
    Re: Трудные для ООП иерархии классов
    От: Кирилл Лебедев Россия http://askofen.blogspot.com/
    Дата: 21.09.07 08:04
    Оценка:
    Здравствуйте, Кодёнок, Вы писали:

    Кё>1. Построить иерархию классов для прямоугольника и квадрата, с изменяющимися размерами.

    Для решения какого класса задач нужна такая иерархия?

    Кё>2. Комплексное число и вещественное число.

    Опять-таки: для решения какого класса задач нужна такая иерархия?

    Кё>Также буду рад услышать названия языков, которые попытались реализовать более богатый набор отношений, чем даёт классическое понимание объектно-ориентированного проектирования.

    Предположим, такой язык нашелся. Ваши дальнейшие действия? Чем это Вам поможет при решении конкретных задач?
    С уважением,
    Кирилл Лебедев
    Software Design blog — http://askofen.blogspot.ru/
    Re[2]: Трудные для ООП иерархии классов
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 21.09.07 08:15
    Оценка: +5
    Здравствуйте, Delight, Вы писали:
    D>Потому что у него есть и ширина, и высота. По мне так можно унаследовать квадрат от прямоугольника, переопределить сеттеры и при изменении одной стороны автоматически изменять другую.
    Ребята, попользуйте поиск. Квадраты с прямоугольниками в ООП обсуждались на этом сайте досконально уже раза три. Все ответы, которые вы можете придумать, уже были даны.
    Правильный ответ — такой: при проектировании иерархии не надо думать о свойствах. Думайте о поведении. Если речь идет о CAD или векторном граф.редакторе, у вас 100% не будет классов ни для квадратов, ни для прямоугольников. Если речь идет о системе для решения геометрических задач, то никаких сеттеров ни у кого не будет вообще.
    ... << RSDN@Home 1.2.0 alpha rev. 677>>
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[3]: Трудные для ООП иерархии классов
    От: Delight  
    Дата: 21.09.07 08:25
    Оценка: -1
    Здравствуйте, konsoletyper, Вы писали:

    D>>Потому что у него есть и ширина, и высота. По мне так можно унаследовать квадрат от прямоугольника, переопределить сеттеры и при изменении одной стороны автоматически изменять другую.


    K>Наследование класса — это сахар. Да и определение класса — тоже сахар; по сути происходит описание интерфейса и его же реализация. Вот если иммутабельные Rectangle и Square реализуют интерфейс IRectange, то проблем не возникает. А зачем нужны мутабельные Rectangle и Square?


    Если нужны мутабельные, то нужно делать мутабельные. Впрочем у меня эта боян-проблема-в-себе вызывает довольно вялый интерес. На флейм не пойду.
    ... << RSDN@Home 1.2.0 alpha rev. 726>>
    Re: Трудные для ООП иерархии классов
    От: konsoletyper Россия https://github.com/konsoletyper
    Дата: 21.09.07 08:25
    Оценка: +1
    Здравствуйте, Кодёнок, Вы писали:

    Кё>1. Построить иерархию классов для прямоугольника и квадрата, с изменяющимися размерами. Трудность в том, что хотя квадрат всегда является прямоугольником (наследуется), он не захочет наследовать его реализацию (зачем ему раздельно хранить ширину и высоту?). Другая трудность в том, что прямоугольник иногда может являться квадратом, но классические отношения между классами, которые предлагают ЯП (наследование и implicit-conversion) не могут выразить такого отношения.


    Последнее отношение выполняется не всегда. Есть верное утверждение "квадрат? x => прямоугольник? x", т.е. любой квадрат можно представить как прямоугольник, но утверждение "прямоугольник? x => квадрат? x" в общем случае неверно, соответственно, никто по рукам бить не будет, если мы нарушим LSP. Так что можно обойтись явным приведением прямоугольника к квадрату, с выбросом исключения при необходимости.

    Кё>2. Комплексное число и вещественное число. Хотя вещественное число является комплексным (наследование), вы не станете наследовать его от комплексного по многим причинам: вам не нужна его реализация (два float в памяти вместо одного), вы вряд ли будете рады тому что sqrt(4) вместо 2.0 вернёт тупл из двух комплексных чисел, и т.п.


    А кто сказал, что нужно наследовать реализацию? С интерфейсами не возникает никаких проблем. В частности, sqrt для float и sqrt для complex — это, вообще-то, математически разные вещи, просто символ радикала является полиморфным (например, в той же математики делается различие между Ln и ln). Так что тут надо либо давать другое имя, либо делать explicit-реализацию метода IComplex.Sqrt, либо юзать язык с перегрузкой типов по возвращаемому значению.

    Зато есть другая вещь, которую вот так просто в терминах "классового" ООП не выразишь. Например, поле комплексных чисел является алгебраически замкнутым, а поле действительных — нет. Или ещё пример: Z является кольцом, а R — полем. И что тут делать? Хочу заметить, что подобные вещи вообще в терминах "классовых" ООЯ неудобно записывать. Гораздо лучше в терминах type classes из Haskell. Или на худой конец, в динамике. Если выражать в статике, то получим множественные runtime проверки типов, что по сути та же динамака.

    Кё>Также буду рад услышать названия языков, которые попытались реализовать более богатый набор отношений, чем даёт классическое понимание объектно-ориентированного проектирования.


    Smalltalk, Self вроде бы теоретически больше позволяют, чем "классовые" ООЯ, хотя у них есть свои проблемы.
    ... << RSDN@Home 1.2.0 alpha rev. 710>>
    Re[2]: Трудные для ООП иерархии классов
    От: Кодёнок  
    Дата: 21.09.07 08:26
    Оценка:
    Здравствуйте, mkizub, Вы писали:

    M>А какие ещё отношения ты знаешь?


    Например то, что в некоторых языках реализовано как трейты (классы типов):

    trait Addable[ThisType] { def +(ThisType other) }

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



    Или отношение, которое сейчас принято реализовать паттерном прокси. Например, если есть некий IString, и есть классы CString и std::string, которые очевидно могут являться IString, но тот же C++ не позволит тебе добавить еще один интерфейс к уже готовым классам. А в идеальном варианте я бы дописал что-то вроде
    implementation IString for std::string {
    char GetChatAt(int i) { return at(i); }
    }
    Re[3]: Трудные для ООП иерархии классов
    От: mkizub Литва http://symade.tigris.org
    Дата: 21.09.07 08:56
    Оценка:
    Здравствуйте, Кодёнок, Вы писали:

    M>>А какие ещё отношения ты знаешь?


    Кё>Например то, что в некоторых языках реализовано как трейты (классы типов):


    Кё>Или отношение, которое сейчас принято реализовать паттерном прокси.


    Угу. А ещё есть возможность динамически добавлять методы и поля (Python и прочие динамические языки), динамически менять супер-класс (наследование делегированием сообщений). Есть ещё возможность автоматического приведения типов (вроде view в Scala, или просто преобразования int->float в С).
    Но они никак не помогут решить проблему Квадрат-Прямоугольник или Complex-Float-Integer. Первая — просто неправильно построенное наследование, и Квадрат с Прямоугольником должны, скорее, унаследоваться от Фигура (как и Треугольник, Многоугольник, Круг и пр.) — так же как это сделано для byte, int, long, float, double в процедурных языках — они независимы, а не образуют иерархию. А с набором независимых типов есть проблема, заключающаяся в необходимости их знать все заранее. Если ты добавишь к числам complex — то отгребёшь по самое нехочу. А потом ещё можно добавить BigInteger/BigFloat, с неограниченным количеством бит. А если complex и BigFloat добавлены независимо, отдельно друг от друга? Ведь надо-бы иметь BigComplex, а иначе вся система рушится — ведь чему будет равен результат умножения BigFloat * complex?

    При фиксированной задаче мы можем знать набор требований, и можем выбрать иерархию Квадрат->Прямоугольник или Прямоугольник->Квадрат или Фигура->Прямоугольник|Квадрат — в зависимости от той модели, которую нам надо реализовать. Но эта модель будет всегда ограничена. А неограниченная (универсальная) модель автоматически приводит к проблеме несовместимости расширений (как complex*BigFloat).

    И это не проблема ООП. В функциональщине те-же проблемы. Так называемая Expression Problem. Вот в Scala её Мартин Одерски предлагает решать как здесь. По моему, очень коряво. Но альтернативные решения не лучше
    SOP & SymADE: http://symade.tigris.org , блог http://mkizub.livejournal.com
    Re[3]: Трудные для ООП иерархии классов
    От: lomeo Россия http://lomeo.livejournal.com/
    Дата: 21.09.07 09:45
    Оценка: +1
    Здравствуйте, Кодёнок, Вы писали:

    Кё>Это с какой стати? Вообще-то я моделирую предметную область с помощью средств языка программирования. С какого перепуга я должен забыть о предметной области?


    Насколько я понял, Igor Trofimov говорит о том, что квадрат из геометрии (реального мира) не является объектом предметной области. Объект предметной области — квадрат с изменяющейся стороной.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re: Трудные для ООП иерархии классов
    От: Кодт Россия  
    Дата: 21.09.07 10:03
    Оценка: 6 (1)
    Здравствуйте, Кодёнок, Вы писали:

    Кё>Я ищу примеры классов объектов, которые вызывают трудности при объектном моделировании в современных ЯП типа C++ или C#. В качестве примера, я знаю всего две классические задачи:


    Нужно определиться с тем, что именно означает иерархия структур в каждом случае: это специализация (субклассирование) или расширение (наследование).
    К специализации применим LSP, к расширению — очевидно, нет.

    Благодаря тому, что в обычных языках наследование как технический инструмент позволяет и специализировать, и расширять класс (перекрытие/перегрузка интерфейсных функций; надстройка layout'а и изменение семантики на корню), возникает прекрасная почва для путаницы в сознании.

    В Блоге Труъ Программиста в августе была серия статей на тему.

    Кё>Также буду рад услышать названия языков, которые попытались реализовать более богатый набор отношений, чем даёт классическое понимание объектно-ориентированного проектирования.


    Haskell и O'Haskell. В последнем — вопросам классовой борьбы уделяется много внимания.
    ... << RSDN@Home 1.2.0 alpha rev. 655>>
    Перекуём баги на фичи!
    Re[4]: Трудные для ООП иерархии классов
    От: _wqwa США  
    Дата: 21.09.07 11:08
    Оценка:
    Здравствуйте, elmal, Вы писали:

    E>Здравствуйте, Кодёнок, Вы писали:


    Кё>>То есть предлагаешь иерархию использовать только для классификации их всех как "чисел", а автоматическую совместимость между ними реализовать через implicit conversion?

    E>В общем да, а вот в деталях может оказаться что и нет . От деталей реализации все зависит.

    Кё>>А как решить проблему sqrt?

    E>А какая проблема с sqrt? sqrt(Number number) и должен возвращать набор чисел. sqrt(4) = 2 и -2 насколько я помню. И собственно когда мы его будем возвращать — эти числа должно быть наиболее адекватного типа. Ну а sqrt(-1) должен возвратить комплексное число, вроде оно наже одно (блин, уже школьную программу забываю — ужас).

    Угу, комплексные числа с мнимыми путаешь
    Кто здесь?!
    Re: Трудные для ООП иерархии классов
    От: igna Россия  
    Дата: 21.09.07 12:34
    Оценка:
    Здравствуйте, Кодёнок, Вы писали:

    Кё>... квадрат всегда является прямоугольником ...


    Изменяемый квадрат не всегда является изменяемым прямоугольником. А в программировании в отличии от математики квадраты и прямоугольники как правило изменяемы.
    Re[2]: Трудные для ООП иерархии классов
    От: COFF  
    Дата: 21.09.07 13:48
    Оценка: 3 (1)
    Кё>>... квадрат всегда является прямоугольником ...

    I>Изменяемый квадрат не всегда является изменяемым прямоугольником. А в программировании в отличии от математики квадраты и прямоугольники как правило изменяемы.


    Кстати, интересный факт из геометрии — квадрат, является не только прямоугольником, но и ромбом, параллелограммом и четырехугольником, а также возможно и трапецией (здесь я не уверен). Так что, судя по всему, объектная иерархия может существенно запутаться
    Re: Трудные для ООП иерархии классов
    От: mrozov  
    Дата: 21.09.07 13:56
    Оценка: 3 (1) +1
    Забавное обсуждение
    Автор привел пару примеров задач, решение которых в рамках традиционного ООП является затруднительным, и попросил подкинуть еще (т.е. добавить убедительности, по сути).
    А ему в ответ все дружно начали объяснять, как эти примеры нужно переписать, чтобы затруднений не было (т.е. решать прямо обратную задачу).

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

    Но все это ни в малейшей степени не означает, что приведенные примеры не являются затруднительными для решения. Идеальный язык не будет навязывать свою парадигму мышления, он будет логично встраиваться в систему взглядов эксперта предметной области. Я так понимаю, что речь именно об этом.
    Re[2]: Трудные для ООП иерархии классов
    От: COFF  
    Дата: 21.09.07 14:05
    Оценка:
    M>Забавное обсуждение
    M>Автор привел пару примеров задач, решение которых в рамках традиционного ООП является затруднительным, и попросил подкинуть еще (т.е. добавить убедительности, по сути).

    M>Но все это ни в малейшей степени не означает, что приведенные примеры не являются затруднительными для решения. Идеальный язык не будет навязывать свою парадигму мышления, он будет логично встраиваться в систему взглядов эксперта предметной области. Я так понимаю, что речь именно об этом.


    Так ведь суть в том, что это не задачи, а вопросы из серии — что было раньше, курица или яйцо, или с какого конца надо разбивать яйцо? А ведь эти проблемы легкими для решения тоже не назовешь. Другой вопрос, стоит ли их решать?
    Re: Трудные для ООП иерархии классов
    От: MatFiz Россия  
    Дата: 21.09.07 19:49
    Оценка:
    Здравствуйте, Кодёнок, Вы писали:

    Кё>1. Построить иерархию классов для прямоугольника и квадрата, с изменяющимися размерами. Трудность в том, что хотя квадрат всегда является прямоугольником (наследуется), он не захочет наследовать его реализацию (зачем ему раздельно хранить ширину и высоту?). Другая трудность в том, что прямоугольник иногда может являться квадратом, но классические отношения между классами, которые предлагают ЯП (наследование и implicit-conversion) не могут выразить такого отношения.


    Одинаковый интерфейс, разные реализации (данные представлены по-разному).
    Тут все просто.


    public interface IRectangle
    {
        double Width { get; }
        double Height { get; }
    }
    
    public class Rectangle : IRectangle
    {
        readonly double _width;
        readonly double _height;
    
        public double Width { get { return _width; } }
        public double Height { get { return _width; } }
    }
    
    public class Square : IRectangle
    {
        readonly double _size;
    
        public double Size { get { return _size; } }
        public double Width { get { return Size; } }
        public double Height { get { return Size; } }
    }
    How are YOU doin'?
    Re[2]: Трудные для ООП иерархии классов
    От: Left2 Украина  
    Дата: 22.09.07 10:01
    Оценка:
    M>Идеальный язык не будет навязывать свою парадигму мышления, он будет логично встраиваться в систему взглядов эксперта предметной области.
    Ох как красиво сказал! Утопично, но как звучит!
    ... << RSDN@Home 1.2.0 alpha rev. 717>>
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.