Чем больше я проникаюсь идеями ООП, тем больше возникает ощущение, что я это уже где-то видел
Например, вот развитие понятия интерфейса в ООП. Начиналось все с того, что некоторые данные из structа научились скрывать. Потом началось повальное скрытие данных — инкапсуляция . И уже в то время оформились обратные идеи — вместо того, чтобы скрывать некоторые данные из всех доступных, надо скрыть все и выдать нагора тщательно отмеренную чуточку — интерфейс. Просто дать обязательство, что помимо всего прочего, я мол являюсь по совместительству и этим-то.
С появлением Паттернов от Банды Четырех эта идея окончательно оформилась — вместо того, чтобы скрыть побольше, надо стремиться показать поменьше. Теперь у нас есть интерфейс, и мы можем с ним работать, ничего не зная о том, что за сущность за ним скрывается — и нам собственно все равно, главное, что она реализовывает нужный нам интерфейс.
А теперь вспомним, как развивались определения в математике. Сначала просто тыкали пальцем — вот это то, а вот это — это. А потом появились аксиоматические определения — например, расстояние — это любая штука такого-то рода, удовлетворяющая таким-то и таким-то аксиомам. И нам все равно, что это за расстояние — евклидова метрика на плоскости, или метрика Хаусдорфа на пространстве компактов. Ну как здесь не сказать, что например, норма реализует интерфейс "расстояние"?
Или вот еще — иерархия классов и множество теорем в математике. Если разрешить множественное наследование, то каждый новый класс наследует какое-то количество уже созданных, а каждая теорема опирается в доказательстве на какое-то подмножество уже доказанных. И при этом верхушки иерархий — интерфейсы или аксиоматические определения.
У теоремы есть утверждение и доказательство — интерфейсная часть и реализация.
Многие теоремы напоминают паттерн Адаптер — берут результат одной теоремы (утверждение которой тяжело прямо использовать в рассматриваемой модели), мучают-мучают преобразованиями — и в удобоваримом виде применяют результат в рассматриваемой теории.
Пишите, что думаете — интересны ваши мысли и наблюдения по этому поводу
Re: Математика и ООП: аналогии
От:
Аноним
Дата:
31.10.03 21:13
Оценка:
Интересная мысль...
Я тебе более скажу, когда после изучения одного раздла математики
я притупал к другому, то я тоже ловил себя на мысле "Где-то я это уже видел..."
В общем, по настоящему новых идей мало.
Но зато есть множество интерпретаций (отражений) одной идеи.
В общем если еще сделать один шаг, то мы перейдем от философии программирования
к просто философии
А>В общем, по настоящему новых идей мало. А>Но зато есть множество интерпретаций (отражений) одной идеи.
во-во! Как у Платона — мир идей
Может, наверное, это и бред, но мне мерещится общая идея в следующих парах:
Множества и отображения
Объекты и действия
Данные-члены и функции-члены класса
Масса и гравитация
Материя и взаимодействия
Подлежащее и сказуемое (аналогия между членами предложения и ООП продолжается и дальше)
Здравствуйте, Кирилл Осенков, Вы писали:
КО>Чем больше я проникаюсь идеями ООП, тем больше возникает ощущение, что я это уже где-то видел
Копайте в сторону алгебры (теория групп). Очень рекомендую посмотреть стандарт языка SDL, в коем была реализована алгебраическая система типов.
Тип определяется множеством литералов, операциями и отношениями эквивалентности (скажем операция умноженя коммутативна, т.е a*b эквивалентно b*a)
Если оставить типы в стороне и сосредоточиться на объектах, то мы спокойно можем использовать аппарат конечных автоматов для моделирования функционирования и взаимодействия объектов.
А читали книгу Кауфмана "Языки программирования"?
Он там как раз проводит аналогии между математическими понятиями и конструкциями языков программирования.
Чтиво интересное, но не для программистов, хотя и называется "Языки программирования"
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
В целом наблюдение верное, позволю себе добавить несколько уточнений: КО>Например, вот развитие понятия интерфейса в ООП. Начиналось все с того, что некоторые данные из structа научились скрывать. Потом началось повальное скрытие данных — инкапсуляция . И уже в то время оформились обратные идеи — вместо того, чтобы скрывать некоторые данные из всех доступных, надо скрыть все и выдать нагора тщательно отмеренную чуточку — интерфейс. Просто дать обязательство, что помимо всего прочего, я мол являюсь по совместительству и этим-то.
Началось все достаточно давно с появления концепции "Абстрактный Тип Данных" (то, что сейчас в ООП называется "инкапсуляцией"). Определение таково: тип данных, определяемый набором операций над ним. Концепция совершенно математическая, и вообще говоря самостоятельная. Известны языки, поддерживающие только инкапсуляцию, без наследования и полиморфизма. Уже в конце 70-х курс обучения программированию в Массачусетском технологическом институте был полностью основан на концепции ADT. Желающие могут ознакомиться с этим курсом и сейчас в замечательной книге "Использование абстракций и спецификаций при разработке программ" (Лисков Б., Гатэг Дж. 1989). Кстати, если кто не читал, рекомендую , получите массу удовольствия.
КО>С появлением Паттернов от Банды Четырех эта идея окончательно оформилась — вместо того, чтобы скрыть побольше, надо стремиться показать поменьше. Теперь у нас есть интерфейс, и мы можем с ним работать, ничего не зная о том, что за сущность за ним скрывается — и нам собственно все равно, главное, что она реализовывает нужный нам интерфейс.
Идея, соответственно, оформилась еще до вхождения в моду ООП. Единственное, что Gang of Four сделали действительно полезного — они дали паттернам имена, и теперь разработчики могут лучше понимать друг друга, когда обсуждают дизайн. Ну, кроме того, что они написали хорошую и интересную книгу
КО>Или вот еще — иерархия классов и множество теорем в математике. Если разрешить множественное наследование, то каждый новый класс наследует какое-то количество уже созданных, а каждая теорема опирается в доказательстве на какое-то подмножество уже доказанных. И при этом верхушки иерархий — интерфейсы или аксиоматические определения.
Отношение "следствие" в математике ИМХО не очень хорошая аналогия отношения тип-подтип в ООП. Наследование, это почти что отношение "подмножество". Почему "почти"? Вот пара задачек, как раз на эту тему:
1) Связанны-ли отношением "подкласс" действительные и комплексные числа. Если да, то как. Требуется аргументированный ответ.
2) То же для круга и эллипса.
Великолепные задачки, имхо. Позволяют на практике проверить, как именно человек понимает ООП.
AVK>Слышал про науку под названием синергетика? Поинтересуйся, тебе понравится.
Для меня идеи просто духозахватывающие, никакая фантастика не катит. Только вот... попробовал почитать Пригожина и Изабеллу-Не-Помню-Фамилии-Кажется-Сэндерс... и ничего не понял Не по зубам еще наверное
Я еще не гуглил, когда будет минутка гляну, может наткнусь на что-нибудь попроще и попопулярней
Здравствуйте, Gaperton, Вы писали:
GG>Началось все достаточно давно с появления концепции "Абстрактный Тип Данных" (то, что сейчас в ООП называется "инкапсуляцией"). Определение таково: тип данных, определяемый набором операций над ним. Концепция совершенно математическая, и вообще говоря самостоятельная. Известны языки, поддерживающие только инкапсуляцию, без наследования и полиморфизма. Уже в конце 70-х курс обучения программированию в Массачусетском технологическом институте был полностью основан на концепции ADT. Желающие могут ознакомиться с этим курсом и сейчас в замечательной книге "Использование абстракций и спецификаций при разработке программ" (Лисков Б., Гатэг Дж. 1989). Кстати, если кто не читал, рекомендую , получите массу удовольствия.
Согласен! Классное чтиво!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Кирилл Осенков, Вы писали:
AVK>Слышал про науку под названием синергетика? Поинтересуйся, тебе понравится.
А еще теория катастроф и теория хаоса.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
DG>Эллипс — базовый класс, круг наследуется от эллипса.
Лично я не стал бы связывать эллипс и окружность отношением "подтип". Они оба реализуют интерфейс "эллипс" — это да. Объект типа эллипс может в очень частном случае оказаться окружностью, более того, у него может быть конструктор, инициализируемый объектом типа "окружность". Но способ задания разный.
DG>Все операции для эллипса доступны и для круга.
Ну не знаю. А операция "сжать в n раз к большей оси? А как насчет провести прямую через фокусы?
DG>Также у круга есть дополнительные операции.
Круг — подтип эллипса — навязывает задание фокусами и эксцентрисситетом (или полуосями, или еще сотней способов — но не центром и радиусом). Если мы хотим эффективно обращаться с окружностями, их просто необходимо задавать центром и радиусом, иначе это будет очень неестественно.
DG>Я бы даже сказал, что круг поддерживает интерфейс эллипса, а не наследуется от эллипса, т.к. хранить те же два фокуса уже не надо.
Во. Это да.
Здравствуйте, LaptevVV, Вы писали:
AVK>>Слышал про науку под названием синергетика? Поинтересуйся, тебе понравится. LVV>А еще теория катастроф и теория хаоса.
А это оно и есть. По крайней мере второе совершенно точно.
Здравствуйте, Кирилл Осенков, Вы писали:
DG>>Все операции для эллипса доступны и для круга. КО>Ну не знаю. А операция "сжать в n раз к большей оси? А как насчет провести прямую через фокусы?
Считаем, что для круга ось, например, всегда вертикальна
class Ellipse
{
virtual Vector BigAxis {get {return (Focus1 - Focus2).Normalize();}}
}
class Circle:
Ellipse
{
override Vector BigAxis {get {return new Vector(0, 1);}}
}
тогда все эти операции продолжают работать.
DG>>Также у круга есть дополнительные операции. КО>Круг — подтип эллипса — навязывает задание фокусами и эксцентрисситетом (или полуосями, или еще сотней способов — но не центром и радиусом). Если мы хотим эффективно обращаться с окружностями, их просто необходимо задавать центром и радиусом, иначе это будет очень неестественно.
DG>>Я бы даже сказал, что круг поддерживает интерфейс эллипса, а не наследуется от эллипса, т.к. хранить те же два фокуса уже не надо. КО>Во. Это да.
Здравствуйте, Кирилл Осенков, Вы писали:
DG>>Также у круга есть дополнительные операции. КО>Круг — подтип эллипса — навязывает задание фокусами и эксцентрисситетом (или полуосями, или еще сотней способов — но не центром и радиусом). Если мы хотим эффективно обращаться с окружностями, их просто необходимо задавать центром и радиусом, иначе это будет очень неестественно.
Особого противоречия — здесь нет:
class Ellipse
{
public Ellipse(Point focus1, Point focus2, double excentriset){..}
}
class Circle:
Ellipse
{
public Circle(Point center, double radius):
base(center, center, CalcExcentriset(center, radius)){}
}
Конечно, если мы говорит о наиболее эффективной реализации, тогда да. Есть interface IEllipse, который эллипс и круг реализуют немного по разному.
Здравствуйте, DarkGray, Вы писали:
DG>Здравствуйте, Кирилл Осенков, Вы писали:
DG>>>Все операции для эллипса доступны и для круга. КО>>Ну не знаю. А операция "сжать в n раз к большей оси? А как насчет провести прямую через фокусы?
DG>Считаем, что для круга ось, например, всегда вертикальна DG>
Грабли номер 1: в императивных языках с поддержкой ООП есть ограничение на операции. Они должны быть замкнуты, т. е. не выводить экземпляр класса из этого класса. Для функциональных языков это не так, но это случай отдельный и экзотический.
Применение stretch ("сжать в n раз к большей оси") в твоем примере выведет экземпляр Circle из класса Circle, и он станет эллипсом (так как ты все-таки согласился ввести ось для круга). Если не согласен, то просто попробуй написать метод stretch.
Да, про то, что круг перестает быть кругом не подумал. ступил.
G>Грабли номер 1: в императивных языках с поддержкой ООП есть ограничение на операции. Они должны быть замкнуты, т. е. не выводить экземпляр класса из этого класса. Для функциональных языков это не так, но это случай отдельный и экзотический.
Императивные языки — это какие?
Если имеются ввиду — строго типизированные, то в них такие операции (которые меняют тип объекта) плохо реализуются.
G>Применение stretch ("сжать в n раз к большей оси") в твоем примере выведет экземпляр Circle из класса Circle, и он станет эллипсом (так как ты все-таки согласился ввести ось для круга). Если не согласен, то просто попробуй написать метод stretch.
Здравствуйте, Кирилл Осенков, Вы писали:
DG>>Эллипс — базовый класс, круг наследуется от эллипса. КО>Лично я не стал бы связывать эллипс и окружность отношением "подтип". Они оба реализуют интерфейс "эллипс" — это да. Объект типа эллипс может в очень частном случае оказаться окружностью, более того, у него может быть конструктор, инициализируемый объектом типа "окружность". Но способ задания разный.
КО>Круг — подтип эллипса — навязывает задание фокусами и эксцентрисситетом (или полуосями, или еще сотней способов — но не центром и радиусом). Если мы хотим эффективно обращаться с окружностями, их просто необходимо задавать центром и радиусом, иначе это будет очень неестественно.
На способ задания как важный критерий здесь ориентироваться нельзя. Это надо, конечно, принимать во внимание при разработке реальной системы, но это не должно являться аргументом первой значимости при разработке объектной модели. Речь идет об абстрактных типах данных, которые определяются через поведение, а не структурой. Что такое табуретка? Сидение + четыре ножки (видим экземпляр табуретки)? Сидение с массивом ножек от одной до четырех (вспомнили про табуретку для рояля и другие странные табуретки)? Или то, на чем можно сидеть без опоры для спины?
Посмотрите задачку о действительных/комплексных числах, там способ задания является одной из основных ловушек. Если ему следовать (что является распространенным среди программистов представлением об эффективности), получится "самое неправильное" решение, — наследование будет применено не по назначению.
DG>>Все операции для эллипса доступны и для круга. КО>Ну не знаю. А операция "сжать в n раз к большей оси? А как насчет провести прямую через фокусы?
А вот это очень правильные вопросы
Здравствуйте, DarkGray, Вы писали:
DG>Императивные языки — это какие?
В которых ты явно прописываешь последовательность операций, "как решать задачу". В непререкаемом, повелительном (imperative) тоне . Проще говоря, процедурные языки. Прошу прощения за заумную лексику.
В функциональном языке ты пишешь, "что" ты хочешь получить, а не "как". Ты даешь определения, а не пишешь алгоритмы. Почему функциональные языки особый случай: благодоря свойству "прозрачности по ссылкам". На практике это означает, что все методы должны быть константными (аналогия с С++), т. е. методы не могут изменять состояния объекта. Объект вместо изменения пересоздается заново. А посему, нет необходимости требовать замкнутость операций. Наверное. Хотя чОрт его знает.
Здравствуйте, Gaperton, Вы писали:
DG>>Императивные языки — это какие? G>В которых ты явно прописываешь последовательность операций, "как решать задачу". В непререкаемом, повелительном (imperative) тоне . Проще говоря, процедурные языки. Прошу прощения за заумную лексику.
Smalltalk и JavaScript — тоже императивные языки, но так как они не строго типизированные, то мы можем менять тип на ходу. Т.е. если мы в какой-то момент поняли, что мы круг, то добавили себе операций, если опять эллипс, то "круговые" операции убрали.
В строготипизированные можно сделать почти тоже самое, но надо больше извращатся.
Здравствуйте, DarkGray, Вы писали:
DG>Здравствуйте, Gaperton, Вы писали:
DG>>>Императивные языки — это какие? G>>В которых ты явно прописываешь последовательность операций, "как решать задачу". В непререкаемом, повелительном (imperative) тоне . Проще говоря, процедурные языки. Прошу прощения за заумную лексику.
DG>Smalltalk и JavaScript — тоже императивные языки, но так как они не строго типизированные, то мы можем менять тип на ходу. Т.е. если мы в какой-то момент поняли, что мы круг, то добавили себе операций, если опять эллипс, то "круговые" операции убрали.
Ну со смаллтоком-то все что угодно может быть, это довольно странный язык. А вот с JavaScript — интересно. Не мог бы ты привести пример? Как это выглядит?
DG>В строготипизированные можно сделать почти тоже самое, но надо больше извращатся.
Это будет круто! Если можно, приведи пример для С++. Ребята заторчат.
Здравствуйте, Gaperton, Вы писали:
G>Применение stretch ("сжать в n раз к большей оси") в твоем примере выведет экземпляр Circle из класса Circle, и он станет эллипсом (так как ты все-таки согласился ввести ось для круга). Если не согласен, то просто попробуй написать метод stretch.
Ну, формально говоря, все в порядке. Если мы поймем, что методов изменения состояния у нас нет, то никаких проблем не будет:
class Ellipse
{
Ellipse Stretch(double Factor)
{
return new Ellipse(Focus1, Focus2, Excentricitet/Factor);
}
}
т.е. даже если метод Stretch будет перегружен в Circle, то он все равно вернет Ellipse. В данном случае, похоже, перегрузка не понадобиться, т.к. Circle обладает всеми необходимыми свойствами для создания по ней нового эллипса. В случае других операций, например Scale, лучше будет выполнить перегрузку, чтобы возвращать именно Circle, а не Ellipse.
Но это, вообще говоря, отходит от концепций ООП, т.к. определенные таким образом объекты идентифицируются своими "значениями", а не идентичностью. Т.е. окружности с одинаковым центром и радиусами считаются тождественными. Именно это позволяет нам не напрягаться по поводу того, что возвращать из Stretch(1) — вернется другой объект, но полностью тождественный исходному, и выражение (Ellipse1.Stretch(1)==Ellipse1) будет истинным, как мы и ожидаем (в том случае, если среда позволяет правильно интерпретировать оператор == как сравнение по значению).
... << RSDN@Home 1.1 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
G>>Применение stretch ("сжать в n раз к большей оси") в твоем примере выведет экземпляр Circle из класса Circle, и он станет эллипсом (так как ты все-таки согласился ввести ось для круга). Если не согласен, то просто попробуй написать метод stretch. S>Ну, формально говоря, все в порядке. Если мы поймем, что методов изменения состояния у нас нет, то никаких проблем не будет:
Здесь проблема в другом, должно так же выполнятся следующее преобразование:
Circle circle = new Circle(new Point(0, 0), 10);
Circle circle2 = circle.Stretch(0.5).Stretch(2);
Assert.IsEqual (circle, circle2);
Есть небольшая проблема в том, что из Ellipse в некоторых случаях должен получатся Circle
Здравствуйте, DarkGray, Вы писали:
DG>Здесь проблема в другом, должно так же выполнятся следующее преобразование: DG>
DG>Circle circle = new Circle(new Point(0, 0), 10);
DG>Circle circle2 = circle.Stretch(0.5).Stretch(2);
DG>Assert.IsEqual (circle, circle2);
DG>
DG>Есть небольшая проблема в том, что из Ellipse в некоторых случаях должен получатся Circle.
Нет, так нельзя . Сигнатура Stretch запрещает нам присваивать его результат в Circle. Если мы позволим определять неявные преобразования (как в С++), то должен быть определен соответствующий оператор Ellipse -> Circle. Но надо быть готовым к тому, что он бросит исключение.
Надо отметить, что мы уже выкинули из рассмотрения всяческий аналог dynamic_cast, по тем же причинам, что и модифицирующий Stretch.
Кстати, никакой проблемы может не быть, если мы не требуем от результата нашего стретча быть именно окружностью:
Circle circle = new Circle(new Point(0, 0), 10);
Ellipse circle2 = circle.Stretch(0.5).Stretch(2); //ok
Assert.IsEqual (circle, circle2); // no problem - equality will work properly if defined on Ellipse class.
... << RSDN@Home 1.1 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, DarkGray, Вы писали:
DG>Конечно, если мы говорит о наиболее эффективной реализации, тогда да. Есть interface IEllipse, который эллипс и круг реализуют немного по разному.
Здравствуйте, SG, Вы писали:
DG>>Конечно, если мы говорит о наиболее эффективной реализации, тогда да. Есть interface IEllipse, который эллипс и круг реализуют немного по разному.
SG> Все равно неудачно. См. вот здесь:
SG>http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6
Есть большая разница между ООП и строготипизированным ООП.
Да, в строготипизированном ПО, для круга операции вида setRadiuses(double x, double y), stretch не удобно реализуется, потому что эти операции требует смены типа — от типа круг объект должен перейти к типу ellipse.
Но если мы рассмотриваем ООП без ограничения на строгую типизацию, то Circle может полностью реализовать интерфейс Ellipse-а
Здравствуйте, DarkGray, Вы писали:
DG>Здравствуйте, SG, Вы писали:
DG>>>Конечно, если мы говорит о наиболее эффективной реализации, тогда да. Есть interface IEllipse, который эллипс и круг реализуют немного по разному.
SG>> Все равно неудачно. См. вот здесь:
SG>>http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6
DG>Есть большая разница между ООП и строготипизированным ООП.
DG>Да, в строготипизированном ПО, для круга операции вида setRadiuses(double x, double y), stretch не удобно реализуется, потому что эти операции требует смены типа — от типа круг объект должен перейти к типу ellipse.
В языке С++, например, такая операция не то что "неудобно", — она не реализуется вообще. Это я вам как врач говорю. Как и в других языках со строгой типизацией. В чем проблема: допустим обявлен указатель на Circle, указывающий на наш объект. При вызове setRadiuses экземпляр класса Circle должен стать экземпляром только Ellipse. Во первых, в С++ это не возможно. Во вторых, даже если бы это было возможно, объясните что делать с указателем на Circle. И это уже безотносительно к языку.
DG>Но если мы рассмотриваем ООП без ограничения на строгую типизацию, то Circle может полностью реализовать интерфейс Ellipse-а
Любопытно. Я очень прошу Вас привести все-таки хоть один пример для JavaScript. Чертовски интересно. Потому что есть проблема. "По честному" это сделать все равно не выйдет, так как вообще говоря такая операция требует пересоздания экземпляра класса (и у вас опять "уплывут" указатели, если они не устроены как-то страшно внутри). В противном случае, пример не будет иметь отношения к ООП. Вот мне и любопытно, что-же такого может быть хитрого в казалось-бы простом языке JavaScript?
Как я уже писал, функциональные языки лишены этого недостатка (желающим посмотреть на функциональный объектно-ориентированный язык рекомендуется Haskell). Вот ТАМ — можно работать с такими объектами без проблем.
Re[7]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, Sinclair, Вы писали:
S>Ну, формально говоря, все в порядке. Если мы поймем, что методов изменения состояния у нас нет, то никаких проблем не будет: S>
S>т.е. даже если метод Stretch будет перегружен в Circle, то он все равно вернет Ellipse. В данном случае, похоже, перегрузка не понадобиться, т.к. Circle обладает всеми необходимыми свойствами для создания по ней нового эллипса. В случае других операций, например Scale, лучше будет выполнить перегрузку, чтобы возвращать именно Circle, а не Ellipse.
Ага. Все правильно, я ждал что кто-то это придумает . Но иногда такой трюк будет выглядеть нескольно странно. Как насчет метода Ellipse::SetRadiuses( double i_x, double i_y ) из примера уважаемого SG?
S>Но это, вообще говоря, отходит от концепций ООП, т.к. определенные таким образом объекты идентифицируются своими "значениями", а не идентичностью. Т.е. окружности с одинаковым центром и радиусами считаются тождественными. Именно это позволяет нам не напрягаться по поводу того, что возвращать из Stretch(1) — вернется другой объект, но полностью тождественный исходному, и выражение (Ellipse1.Stretch(1)==Ellipse1) будет истинным, как мы и ожидаем (в том случае, если среда позволяет правильно интерпретировать оператор == как сравнение по значению).
Я бы не сказал, что это отход от концепции ООП, тем более в общем случае. В функциональных ОО языках это является нормой, так как все модифицирующие операции там запрещены в принципе (требование "прозрачности по ссылкам"). Что и приводит к тому, что объекты идентифицируются своими "значениями", а не идентичностью. Это является нормой для функциональных, и ненормальным для процедурных языков.
Здесь проблема именно в том, что мы используем процедурные языки, в которых возможность изменения состояния объектов является не просто нормой, а ключевой парадигмой, определяющей стиль разработки. Если мы не хотим проблем с нашим дизайном, необходимо отказаться от любого наследования между Circle и Ellipse, потому что:
1) существует большой класс модифицирующих операций определенных для Ellipse, которые не замкнуты на Circle (выводят Circle из класса Circle). Переводить эти операции в немодифицирующие — прием для процедурных языков исскуственный, и широкое его применение чревато серьезным усложнением системы.
2) Распространенное объяснение с другого конца (например, в ссылке приведенной SG): нарушается "принцип подстановки Лисков". Он состоит в том, что экземпляр подкласса должен быть пригоден к использованию во всех контекстах, где может быть использован базовый класс. К кругу нельзя сделать операцию SetFocuses. На самом деле здесь тоже неявно предполагается, что операция обязана быть замкнутой. Просто акценты стоят по другому.
G>Любопытно. Я очень прошу Вас привести все-таки хоть один пример для JavaScript. Чертовски интересно. Потому что есть проблема. "По честному" это сделать все равно не выйдет, так как вообще говоря такая операция требует пересоздания экземпляра класса (и у вас опять "уплывут" указатели, если они не устроены как-то страшно внутри). В противном случае, пример не будет иметь отношения к ООП. Вот мне и любопытно, что-же такого может быть хитрого в казалось-бы простом языке JavaScript?
В простом языке JavaScript мы можем к объекту добавлять, удалять функции на лету, в отличии от строго типизированных языков:
Будет что-то такое:
function CreateEllipse(focus1, focus2, diameter)
{
var ellipse = new object();
ellipse.UpdateFigure = UpdateFigure;
ellipse.Stretch = Stretch;
ellipse.focus1 = focus1;
ellipse.focus2 = focus2;
ellipse.diameter = diameter;
return ellipse;
}
function CreateCircle(center, radius)
{
var circle = CreateEllipse(center, center, radius * 2);
circle.UpdateFigure();
return circle;
}
function Stretch(int k)
{
this.ChangeFocusesAndDiameter(k);
this.UpdateFigure();
}
function SetCenter(center)
{
this.focus1 = center;
this.focus2 = center;
}
function GetCenter()
{
return this.focus1;
}
function UpdateFigure()
{
if (this.IsCircle())
Ellipse2Circle(this);
else
Circle2Ellipse(this);
}
function IsCircle()
{
return this.focus1 == this.focus2;
}
function Ellipse2Circle(ellipse)
{
ellipse.SetCenter = SetCenter;
ellipse.GetCenter = GetCenter;
}
function Circle2Ellipse(ellipse)
{
ellipse.SetCenter = null;
ellipse.GetCenter = null;
}
Re[8]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, Gaperton, Вы писали:
G>Ага. Все правильно, я ждал что кто-то это придумает . Но иногда такой трюк будет выглядеть нескольно странно. Как насчет метода Ellipse::SetRadiuses( double i_x, double i_y ) из примера уважаемого SG?
Что-нибудь такое
using System;
using System.Drawing;
namespace ConsoleApplication1
{
public interface IEllipse
{
void SetFocuses(PointF focus1, PointF focus2);
PointF Focus1{get;set;}
PointF Focus2{get;set;}
void StretchByBigAxis(double k);
}
public interface ICircle:
IEllipse
{
PointF Center {get;set;}
}
public abstract class EllipseWrapper:
IEllipse
{
protected abstract IEllipse InnerEllipse {get;}
#region IEllipse Members
public void SetFocuses(PointF focus1, PointF focus2)
{
InnerEllipse.SetFocuses(focus1, focus2);
}
public void StretchByBigAxis(double k)
{
InnerEllipse.Stretch(k);
}
public PointF Focus1
{
get {return InnerEllipse.Focus1;}
set {InnerEllipse.Focus1 = value;}
}
public PointF Focus2
{
get {return InnerEllipse.Focus2;}
set {InnerEllipse.Focus2 = value;}
}
#endregion
}
public abstract class CircleWrapper:
EllipseWrapper,
ICircle
{
protected abstract ICircle InnerCircle {get;}
protected override IEllipse InnerEllipse
{
get
{
return InnerCircle;
}
}
#region ICircle Members
public PointF Center
{
get
{
return InnerCircle.Center;
}
set
{
InnerCircle.Center = value;
}
}
#endregion
}
public class CircleImpl:
EllipseWrapper,
ICircle
{
public CircleImpl(IEllipse ellipse)
{
this._InnerEllipse = ellipse;
}
IEllipse _InnerEllipse;
protected override IEllipse InnerEllipse
{
get
{
return _InnerEllipse;
}
}
#region ICircle Members
public PointF Center
{
get
{
return InnerEllipse.Focus1;
}
set
{
InnerEllipse.Focus1 = InnerEllipse.Focus2 = value;
}
}
#endregion
}
public class Ellipse:
IEllipse
{
public Ellipse(PointF focus1, PointF focus2)
{
this._Focus1 = focus1;
this._Focus2 = focus2;
}
#region IEllipse Members
PointF _Focus1, _Focus2;
public void SetFocuses(PointF focus1, PointF focus2)
{
this.Focus1 = focus1;
this.Focus2 = focus2;
}
public PointF Focus1
{
get
{
return _Focus1;
}
set
{
_Focus1 = value;
}
}
public PointF Focus2
{
get
{
return _Focus2;
}
set
{
_Focus2 = value;
}
}
public void StretchByBigAxis(double k)
{
}
#endregion
}
public class Circle:
CircleWrapper
{
public Circle(IEllipse ellipse)
{
_InnerCircle = new CircleImpl(ellipse);
}
ICircle _InnerCircle;
protected override ICircle InnerCircle
{
get
{
if (!IsCircle(_InnerCircle))
throw new Exception("Данная фигура не круг");
return _InnerCircle;
}
}
public static ICircle AsCircle(IEllipse ellipse)
{
if (!IsCircle(ellipse))
throw new Exception("Данная фигура не круг");
if (ellipse is ICircle)
return (ICircle)ellipse;
return new Circle(ellipse);
}
public static bool IsCircle(IEllipse ellipse)
{
return ellipse.Focus1 == ellipse.Focus2;
}
}
/// <summary>
/// Summary description for Class1.
/// </summary>class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
IEllipse ellipse = new Ellipse(new PointF(0, 1), new PointF(0, -1));
ellipse.SetFocuses(new PointF(0, 0), new PointF(0, 0));
ICircle circle = Circle.AsCircle(ellipse);
circle.StretchByBigAxis(1.5);
}
}
}
Здравствуйте, DarkGray, Вы писали:
DG>В простом языке JavaScript мы можем к объекту добавлять, удалять функции на лету, в отличии от строго типизированных языков:
Спасибо, все понял!
1) Язык полиморфный.
2) Закрыть данные нельзя, верно? Т. е. инкапсуляция языком не поддерживается. Все так?
3) Типы конструируются динамически и определяются неявно. Применяется позднее связывание. Поэтому нет необходимости в наследовании. Вернее, поведение аналогичное наследованию организовать можно, но при этом в понятии "наследование" нет необходимости. По выразительной силе похоже на шаблоны C++, но еще мощнее, так как работает во время исполнения.
В общем, согласен, в JavaScript такой проблемы нет. Одна проблема
Язычок прикольный, но вроде как по любому не объектно-ориентированный. Формально из 3 признаков ОО языка в наличии 1. Неформально — концепция JavaScript превосходит по гибкости ООП.
Re[9]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>Здравствуйте, Gaperton, Вы писали:
G>>Ага. Все правильно, я ждал что кто-то это придумает . Но иногда такой трюк будет выглядеть нескольно странно. Как насчет метода Ellipse::SetRadiuses( double i_x, double i_y ) из примера уважаемого SG?
DG>Что-нибудь такое
Я в шоке, дорогая редакция Добро пожаловать в мир занимательного си-шарпа
2 интерфейса, 5 классов (не считая точки) — 7 разных типов данных.
Самый интересный вопрос, которым задасца читатель с пытливым умом, это что-же все-таки на самом деле случится с Circle в вашем примере после применения операции StretchByBigAxis? Это сложно понять, и поэтому, браццы, видимо придецца верить мне на слово : Он как был на самом деле эллипсом, так им и остался . Не верицца — почитайте КОД.
Можно было еще проще:
class Ellipse : ICircle, IEllipse;
И фсе, собственно. Тот же известный анатомический орган, только вид сбоку. Высеченная в коде как на граните констатация простого факта, что круг это частный случай эллипса.
Вса-таки, позвольте поинтересоваться: что именно хотели вы приллюстрировать этим примером? К сожалению, у меня не хватает моральных сил до конца разобраться в КОДЕ, так как он почему-то выводит меня из душевного равновесия, мягко выражаясь . Ничего личного, это мои проблемы.
Re[10]: Задачка про Circle/Ellipse: "официальный" ответ.
G>>>Ага. Все правильно, я ждал что кто-то это придумает . Но иногда такой трюк будет выглядеть нескольно странно. Как насчет метода Ellipse::SetRadiuses( double i_x, double i_y ) из примера уважаемого SG?
DG>>Что-нибудь такое G>Я в шоке, дорогая редакция Добро пожаловать в мир занимательного си-шарпа G>2 интерфейса, 5 классов (не считая точки) — 7 разных типов данных.
Типов четыре — два интерфейса и две реализации. Все остальное — это издержки C#, т.к. C# — низкоуровневый язык. Фактически остальные три класса — это смарт-поинтеры.
— Теперь о ситуации в общем. —
Наследование бывает разным: бывает наследование расширающее, бывает наследование сужающее, в первом случае — мы к предку добавляем новые возможности, во втором случае — мы к предку добавляет новые ограничения.
Пример:
рассмотрим интервальные типы,
в частности — тип, значения которого определены на интервале 1..1000.
Данный тип — наследует всю функциональность целого типа с учетом ограничений. К данному типу применимы большинство операций, свойств, теорем целого типа за небольшим исключением.
Но в то же время операция: верни число со знаком минус является бессмысленной, и можно даже сказать неприменимой к данному типу.
Другой пример:
рассмотрим шизофреника (человека с раздвоением личности)
С одной стороны, доступны все те же операции, что и над обычным человек, с другой стороны, такая тривиальная операция, как "Скажи свое имя" шизофреника загонит в ступор, т.к. сразу появляется вопросы: Какое из имен?, Первой личности? Второй? Любое? А кто спрашивает?
Получается, что и для случая расширающего наследования, и для сужающего — мы не можем напрямую поддерживать все операции, который были доступны для предка.
Проблема в том, что языки со статической типизацией (C++, C#, Java) не только не решают данную проблему, но даже не дают ни одной возможности решить эту проблему в своих рамках.
Re[11]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>Наследование бывает разным: бывает наследование расширающее, бывает наследование сужающее, в первом случае — мы к предку добавляем новые возможности, во втором случае — мы к предку добавляет новые ограничения.
Наследование это не отношение "подмножество". Говоря об типах в ООП мы обязаны принять во внимание, что [абстрактный] тип данных (класс) определяется только операциями над ним. При правильном применении наследования в любых языках должен соблюдаться "принцип подстановки Лисков", который состоит в том, что "подкласс должен быть пригоден к использованию во всех контекстах, что и базовый класс", что означает, что операции в подклассе должны работать и сохранять семантику.
Другими словами, "сужающее" наследование — совершенно некорректная штука.
Re[12]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Наследование бывает разным: бывает наследование расширающее, бывает наследование сужающее, в первом случае — мы к предку добавляем новые возможности, во втором случае — мы к предку добавляет новые ограничения.
G>Наследование это не отношение "подмножество". Говоря об типах в ООП мы обязаны принять во внимание, что [абстрактный] тип данных (класс) определяется только операциями над ним. При правильном применении наследования в любых языках должен соблюдаться "принцип подстановки Лисков", который состоит в том, что "подкласс должен быть пригоден к использованию во всех контекстах, что и базовый класс", что означает, что операции в подклассе должны работать и сохранять семантику. G>Другими словами, "сужающее" наследование — совершенно некорректная штука.
В каких отношениях тогда находится тип целое числа и тип натуральное число?
Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего?
Т.е. получается, что мы должны отдельно завести тип целое число, определить для него операции, потом отдельно завести тип натуральное число опять определив все операции?
Потом также отдельно завести типы [0..10), [-4..1000], [0..255], [0..65535] и т.д.?
ps
Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
Re[13]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>>>Наследование бывает разным: бывает наследование расширающее, бывает наследование сужающее, в первом случае — мы к предку добавляем новые возможности, во втором случае — мы к предку добавляет новые ограничения.
G>>Наследование это не отношение "подмножество". Говоря об типах в ООП мы обязаны принять во внимание, что [абстрактный] тип данных (класс) определяется только операциями над ним. При правильном применении наследования в любых языках должен соблюдаться "принцип подстановки Лисков", который состоит в том, что "подкласс должен быть пригоден к использованию во всех контекстах, что и базовый класс", что означает, что операции в подклассе должны работать и сохранять семантику. G>>Другими словами, "сужающее" наследование — совершенно некорректная штука.
Кстати, конструкторы являются частью интерфейса?
DG>В каких отношениях тогда находится тип целое числа и тип натуральное число?
Если все методы константны — то вполне могут быть Лисковыми классом и подклассом. Различаются только конструкторы.
DG>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего?
Скорее так: между наследованием и типами нет ничего общего. Ну, или почти ничего. Наследование — не единственный способ связи.
DG>ps DG>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
Просто он, видимо, не имел в виду С++...
С С++ можно работать по искову, но для этого надо полностью использовать множественное наследование и идентификаторы доступа (private etc.)
Кстати, интерфейс нельзя поддерживать частично. Либо поддерживаешь, либо нет.
Re[13]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали: DG>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего?
Смешно. Ни то ни другое. Наследованием не исчерпываются взаимоотношения между типами.
Можно было бы ввести метаинтерфейс, который содержит все нужные нам мат.операции. Мета — потому, что мы не можем фиксировать тип параметров. Можем только потребовать, чтобы operator + (type, type) возвращал снова type, а не хрен собачий.
В итоге мы бы получили два типа, которые реализуют эти операции. Вот только целые числа умели бы еще немножко больше. И у нас был бы оператор преобразования, который позволяет получать по любому натуральному числу его целый аналог. Обратного оператора быть не должно.
Я бы хотел подчеркнуть, что в такой модели есть два разных объекта "2" — целая двойка и натуральная двойка. У натуральной двойки нет оператора унарного минуса. Поэтому вычитание из 4 2 выглядит примерно так: Integer(Natural(4))+Negation(Integer(Natural(2))).
DG>Т.е. получается, что мы должны отдельно завести тип целое число, определить для него операции, потом отдельно завести тип натуральное число опять определив все операции? DG>Потом также отдельно завести типы [0..10), [-4..1000], [0..255], [0..65535] и т.д.?
DG>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
Барбара вообще ничего не говорила про числа. Ребята, математика — хреновое поле приложения ООП. Она базируется на теории множеств. В отличие от объектов, элементы множества суть вечные и неизменные э-э... элементы, и поведением (которое суть неотъемлемая часть парадигмы ООП) не обладают. Поэтому для них стоит придумать отдельную теорию. Хотя некоторые аспекты их функциональности и можно с различным успехом смоделировать в ООП.
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: Задачка про Circle/Ellipse: "официальный" ответ.
INT>Кстати, конструкторы являются частью интерфейса?
Если рассматривать ООП, в общем виде, то — да, является, но в нынешних языках — формально мы это специфировать не можем.
DG>>В каких отношениях тогда находится тип целое числа и тип натуральное число? INT>Если все методы константны — то вполне могут быть Лисковыми классом и подклассом. Различаются только конструкторы.
В данном случае, подкласс в смысле наследник? или в каком смысле "под"?
DG>>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего? INT>Скорее так: между наследованием и типами нет ничего общего. Ну, или почти ничего. Наследование — не единственный способ связи.
Какое отношение в данном случае есть?
DG>>ps DG>>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма. INT>Просто он, видимо, не имел в виду С++... INT>С С++ можно работать по искову, но для этого надо полностью использовать множественное наследование и идентификаторы доступа (private etc.)
INT>Кстати, интерфейс нельзя поддерживать частично. Либо поддерживаешь, либо нет.
Ха! Открой спецификацию любого сложный интерфейс. Почти всегда большая часть методов является опциональными, т.е. получается, что на уровне метаданных метод есть, но реально его нет. Реально он не поддерживается.
Т.е. даже на уровне спецификаций многие интерфейсы можно поддерживать частично. Что же тогда творится на самом деле.
ps
Взять те же компиляторы C++. Есть строго специфицированный стандарт C++, фактически это интерфейс взаимодействия между текстом и результатом в виде программы. И насколько много компиляторов, которые поддерживают полностью данный интерфейс(стандарт)?
Re[14]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего? S>Смешно. Ни то ни другое. Наследованием не исчерпываются взаимоотношения между типами.
Для начала хочется увидеть определение отношения "наследование".
В моем понимании "наследование" — это "перенос" признаков, аттрибутов, методов, теорем и т.д. от одного объекта к другому, причем данный перенос может быть, как сужающим, так и расширающим.
Пример:
Немой человек — это человек, который не может говорить. В данном случае, понятие "немой человек" наследует все свойства, аттрибуты, методы и т.д. понятие человека. Причем понятие "немой человек" может использоваться во всех тех же контекстах, что и понятие "человек".
Замечу, что понятие "немой человек" почти совсем не отличается от понятия "человек" с установленым свойством "голос сорван" в true.
Лыжник — это человек с лыжами. В данном случае, понятие "лыжник" тоже наследует все свойства, аттрибуты, методы и т.д. от понятия "человек". И лыжник тоже может быть использован в тех же контекстах, что и человек.
S>Можно было бы ввести метаинтерфейс, который содержит все нужные нам мат.операции. Мета — потому, что мы не можем фиксировать тип параметров. Можем только потребовать, чтобы operator + (type, type) возвращал снова type, а не хрен собачий.
S>В итоге мы бы получили два типа, которые реализуют эти операции. Вот только целые числа умели бы еще немножко больше. И у нас был бы оператор преобразования, который позволяет получать по любому натуральному числу его целый аналог. Обратного оператора быть не должно.
Почему? Почему мы по целому числу не можем получить натуральное? Из числа с плавающей точкой мы же можем получить целое... И из строки можем получить число...
S>Я бы хотел подчеркнуть, что в такой модели есть два разных объекта "2" — целая двойка и натуральная двойка. У натуральной двойки нет оператора унарного минуса. Поэтому вычитание из 4 2 выглядит примерно так: Integer(Natural(4))+Negation(Integer(Natural(2))).
Что полезного (кроме громоздкости) нам дает такая модель?
Имхо, более удобной является следующая модель: есть один объект "2". При этом этот объект является и натуральным числом, и целым числом, и дробным числом, и даже комплексным числом. Может даже являться строкой "2" или "10", если у нас заспецифицировано, что используется сооветствующая система счисления.
И если мы при делении 7.20 на 3.60 получили объект "2", то этот объект мы можем легко передать кладовщику, чтобы он отгрузил данное (натуральное) кол-во объектов(вещей).
Re[15]: Задачка про Circle/Ellipse: "официальный" ответ.
INT>>Если все методы константны — то вполне могут быть Лисковыми классом и подклассом. Различаются только конструкторы.
DG>В данном случае, подкласс в смысле наследник? или в каком смысле "под"?
В смысле, натуральные числа -наследник действительных.
DG>>>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего? INT>>Скорее так: между наследованием и типами нет ничего общего. Ну, или почти ничего. Наследование — не единственный способ связи. DG>Какое отношение в данном случае есть?
Отношение вида sibling, т.е. общие предки/интерфейсы. Еще бывают связи типа "использует" и "включает".
INT>>Кстати, интерфейс нельзя поддерживать частично. Либо поддерживаешь, либо нет. DG>Ха! Открой спецификацию любого сложный интерфейс. Почти всегда большая часть методов является опциональными, т.е. получается, что на уровне метаданных метод есть, но реально его нет. Реально он не поддерживается. DG>Т.е. даже на уровне спецификаций многие интерфейсы можно поддерживать частично. Что же тогда творится на самом деле.
Ну, в спецификация интерфейсом можно назвать что угодно. Я думал ты имеешь в виду интерфейсы в Com/Corba/Java смысле.
DG>Взять те же компиляторы C++. Есть строго специфицированный стандарт C++, фактически это интерфейс взаимодействия между текстом и результатом в виде программы. И насколько много компиляторов, которые поддерживают полностью данный интерфейс(стандарт)?
Если они не поддерживают стандарт полностью — значит, это не компиляторы языка С++, только и всего. А с вопросом о том, почему стандарт языка не позволяет написать соответствующий ему компилятор — к товарищу Страуструпу.
Re[16]: Задачка про Circle/Ellipse: "официальный" ответ.
INT>>>Если все методы константны — то вполне могут быть Лисковыми классом и подклассом. Различаются только конструкторы.
DG>>В данном случае, подкласс в смысле наследник? или в каком смысле "под"? INT>В смысле, натуральные числа -наследник действительных.
Как в таком случае быть с операцией "отрицание"?
INT>>>Кстати, интерфейс нельзя поддерживать частично. Либо поддерживаешь, либо нет. DG>>Ха! Открой спецификацию любого сложный интерфейс. Почти всегда большая часть методов является опциональными, т.е. получается, что на уровне метаданных метод есть, но реально его нет. Реально он не поддерживается. DG>>Т.е. даже на уровне спецификаций многие интерфейсы можно поддерживать частично. Что же тогда творится на самом деле.
INT>Ну, в спецификация интерфейсом можно назвать что угодно. Я думал ты имеешь в виду интерфейсы в Com/Corba/Java смысле.
И в этом смысле тоже. Так многие Com-овские интерфейсы по спецификации могут быть реализованы частично.
An OLE component can implement an interface without implementing all the semantics of every method in the interface, instead returning E_NOTIMPL or S_OK as appropriate. The following table describes those methods that an ActiveX Control container is not required to implement (i.e. the control container can return E_NOTIMPL).
The table below describes optional methods; note that the method must still exist, but can simply return E_NOTIMPL instead of implementing real semantics. Note that any method from a mandatory interface that is not listed below must be considered mandatory and may not return E_NOTIMPL.
DG>>Взять те же компиляторы C++. Есть строго специфицированный стандарт C++, фактически это интерфейс взаимодействия между текстом и результатом в виде программы. И насколько много компиляторов, которые поддерживают полностью данный интерфейс(стандарт)?
INT>Если они не поддерживают стандарт полностью — значит, это не компиляторы языка С++, только и всего.
Но ведь ими же пользуются... И пользуются успешно.
Ты ведь не можешь сказать работадателю/клиенту — я не буду писать программу, потому что Ваш/данный компилятор не поддерживает полностью спецификацию C++, а будешь подстраиваться под то, что дали.
Re[17]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>>>В данном случае, подкласс в смысле наследник? или в каком смысле "под"? INT>>В смысле, натуральные числа -наследник действительных.
DG>Как в таком случае быть с операцией "отрицание"?
Операция "отрицание" от натурального числа возвращает целое, как и в операция "отрицание" от целого.
INT>>Ну, в спецификация интерфейсом можно назвать что угодно. Я думал ты имеешь в виду интерфейсы в Com/Corba/Java смысле.
DG>И в этом смысле тоже. Так многие Com-овские интерфейсы по спецификации могут быть реализованы частично.
ОК, согласен. Но все равно это уже "как бы" интерфейсы и "как бы" наследование. Они имеют смысл в спецификации и для определения протокола обмена, но такая "реализация" не позволяет сказать ничего определенного об объекте "реализующем" этот "интерфейс".
Кроме того, даже возвращение "NOT_IMPLEMENTED" — это тоже выполнение обязательств интерфейса, хотя бы на уровне общения. Т.е. фактически мы имеем не оди интерфейс, а интерфейс общения и по одному функциональному "интерфейсику" на каждое сообщение.
INT>>Если они не поддерживают стандарт полностью — значит, это не компиляторы языка С++, только и всего. DG>Но ведь ими же пользуются... И пользуются успешно. DG>Ты ведь не можешь сказать работадателю/клиенту — я не буду писать программу, потому что Ваш/данный компилятор не поддерживает полностью спецификацию C++, а будешь подстраиваться под то, что дали.
Разумеется, но буду помнить, что то, на чем я пишу является не стандартным С++, а языком "по мотивам".
Извини, если я что-то не в тему говорю — болею. Вообще общая мысль такая — понятия "язык", "интерфейс", "наследование", "класс" и т.д. слишком размыты и многозначны. Тем удивительнее, что их можно использовать в первоначальном "строгом" значении и писать в "строгом" стиле, позволяющем, в частности, формально доказывать свойства программ. Еще больше меня удивляет то, что это можно делать и в С++, причем средств этого языка достаточно для этого практически точь-в-точь, т.е. все, что нужно, и ничего лишнего. Но повторюсь, для "формально корректных" программ нужны возможности С++ целиком, включая множественное наследование, декларацию выкидываемых функцией исключений, частично определенный шаблоны и т.д. Т.е. писать на чем-то, соответствующем стандарту.
Re[15]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>Для начала хочется увидеть определение отношения "наследование". DG>В моем понимании "наследование" — это "перенос" признаков, аттрибутов, методов, теорем и т.д. от одного объекта к другому, причем данный перенос может быть, как сужающим, так и расширающим.
Это-то конечно. А в моем понимании негодяй — это человек, который проработал не один год
Просто в ООП традиционно под наследованием понимают операцию, обратную обобщению. А поскольку основой ООП является поведение объектов, то фраза "класс А является обобщением классов A1 и A2" (== "классы A1 и A2 унаследованы от A") может трактоваться только как "объекты классов A1 и A2 обладают некоторым общим поведением, которое унаследовано ими от класса A". И все — мы не можем лишить объекты класса A1 какой-то части поведения объектов класса A. Можем только добавлять.
Используя альтернативные определения наследования можно получать альтернативные результаты. DG>Почему? Почему мы по целому числу не можем получить натуральное? Из числа с плавающей точкой мы же можем получить целое... И из строки можем получить число...
Ну и какое же натуральное число ты собрался получить из целого числа "-2"? А из строки "бармаглот"? DG>Что полезного (кроме громоздкости) нам дает такая модель?
А то, что другого варианта корректной эмуляции поведения этих двух классов чисел в рамках ООП я не вижу. DG>Имхо, более удобной является следующая модель: есть один объект "2". При этом этот объект является и натуральным числом, и целым числом, и дробным числом, и даже комплексным числом. Может даже являться строкой "2" или "10", если у нас заспецифицировано, что используется сооветствующая система счисления.
Ну, смотря для чего эта модель является удобной. Видишь ли, если это целая двойка, то из нее нельзя извлечь квадратный корень — эта операция не определена для целых чисел. Как, впрочем, и для рациональных чисел ты не сможешь статически определить такую операцию. Более того, результат операции 5/2 радикальным образом зависит от того, какие 2 и 5 мы рассматриваем — целые либо рациональные. В твоей более удобной модели нет никакого способа понять, какой из этих результатов будет возвращен! Ведь у тебя ровно одна двойка и одна пятерка! Еще рекомендую подумать над тем, что должен вернуть корень четвертой степени из единицы. DG>И если мы при делении 7.20 на 3.60 получили объект "2", то этот объект мы можем легко передать кладовщику, чтобы он отгрузил данное (натуральное) кол-во объектов(вещей).
А если не получили? Я не опротестовываю твою модель. Вот только типизация у нее получается сугубо динамическая. Боюсь, к ООП она не сможет иметь никакого отношения.
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[17]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>И в этом смысле тоже. Так многие Com-овские интерфейсы по спецификации могут быть реализованы частично.
Это не больший косяк, чем отказ реализовывать семантику. Я вот сталкивался с тем, что негодяи возвращают S_OK, но при этом не выполняют требований спецификации. Очень, кстати, раздражает.
Фактически, такие вещи — следствие кривого дизайна. Вы посмотрите на объектную модель Екселя! Там с десяток объектов всего. Зато каждый реализует под пару сотен методов. Ага, это они просто не хотели мешать жить тем, кто программирует процедурно. Не хотелось им раскладывать функционал на те самые сто-двести интерфейсов.
Второй вариант — это отсутствие рефакторинга и требования поддержать обратную совместимость. Начиная с аутлука XP, попытка принудительно вызвать доставку почты через MAPI игнорируется. Правильно — это дыра, через которую лазили черви. Увы, то что написано в реестре вырубить топором нельзя. Даже если опубликовать новый интерфейс, то что делать с теми клиентами, которые опираются на старый?
Но это хоть как-то можно понять. А вот опциональные методы в интерфейсах COM — это вообще преступление против теории кристалла. Вместо этого надо было развалить интерфейс на соответствующее количество частей, и не парить мозг разработчикам. Так бы все было просто — допросил объект об интерфейсах — и работай себе. А то для каждого долбаного вызова надо проверять, уж не вернул ли он E_NOTIMPL.
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[13]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>>>Наследование бывает разным: бывает наследование расширающее, бывает наследование сужающее, в первом случае — мы к предку добавляем новые возможности, во втором случае — мы к предку добавляет новые ограничения.
G>>Наследование это не отношение "подмножество". Говоря об типах в ООП мы обязаны принять во внимание, что [абстрактный] тип данных (класс) определяется только операциями над ним. При правильном применении наследования в любых языках должен соблюдаться "принцип подстановки Лисков", который состоит в том, что "подкласс должен быть пригоден к использованию во всех контекстах, что и базовый класс", что означает, что операции в подклассе должны работать и сохранять семантику. G>>Другими словами, "сужающее" наследование — совершенно некорректная штука.
DG>В каких отношениях тогда находится тип целое числа и тип натуральное число? DG>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего?
Если операции не имеют деструктивной семантики, то все ништяк. Натуральное число — подкласс целого.
DG>Т.е. получается, что мы должны отдельно завести тип целое число, определить для него операции, потом отдельно завести тип натуральное число опять определив все операции? DG>Потом также отдельно завести типы [0..10), [-4..1000], [0..255], [0..65535] и т.д.?
Да. Именно так. В противном случае наследование будет использовано не по назначению.
DG>ps DG>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
Найди в гугле материал по "Liskov Substitution Principle", там на примерах объясняется, чем в реальной жизни чревато нарушение этого принципа. А чревато оно настолько, что надо вырывать руки нах программерам, которые им пренебрегают.
Ну вот, самый простой пример. Представь, что у тебя есть написанная и отлаженная подсистема, работающая с абстрактным типом А. Пусть А — это эллипс. Подсистема делает последовательность преобразований
1) Растянуть А по оси ох в два раза.
2) Повернуть А на 90 градусов по часовой стрелке.
3) Растянуть А по оси ох в два раза.
Легко видеть, что это преобразование инвариантно для эллипса, который является кругом. Попробуй написать подкласс А для круга, если перечисленные операции обладают деструктивной семантикой (т. е. они изменяют объект, а не создают новый — что нормально для императивных языков).
Кстати, Лисков — это женщина , и очень умная женщина. Она пока вроде еще жива и преподает в MIT. Живой классик.
Re[16]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, DarkGray, Вы писали:
S>Более того, результат операции 5/2 радикальным образом зависит от того, какие 2 и 5 мы рассматриваем — целые либо рациональные. В твоей более удобной модели нет никакого способа понять, какой из этих результатов будет возвращен! Ведь у тебя ровно одна двойка и одна пятерка! Еще рекомендую подумать над тем, что должен вернуть корень четвертой степени из единицы.
Это все глюки sintactic sugar. Целочисленное деление и целое деление — это две большие разницы. И приведение типов — это тоже операция, даже если она не требует упоминания в тексте.
DG>>И если мы при делении 7.20 на 3.60 получили объект "2", то этот объект мы можем легко передать кладовщику, чтобы он отгрузил данное (натуральное) кол-во объектов(вещей). S>А если не получили? Я не опротестовываю твою модель. Вот только типизация у нее получается сугубо динамическая. Боюсь, к ООП она не сможет иметь никакого отношения.
Типизация у ООП тоже не имеет отношения. Типизация — это свойство контроля правильности и sintactic sugar для операций (чтобы можно было разные / отличать по типам операндов, а не указывать каждый раз).
Кстати, чем динамическая типизация отличается от статической кроме того, что в ней иногда можно опускать упоминания операции приведения типа?
Re[16]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Для начала хочется увидеть определение отношения "наследование". DG>>В моем понимании "наследование" — это "перенос" признаков, аттрибутов, методов, теорем и т.д. от одного объекта к другому, причем данный перенос может быть, как сужающим, так и расширающим. S>Просто в ООП традиционно под наследованием понимают операцию, обратную обобщению. А поскольку основой ООП является поведение объектов, то фраза "класс А является обобщением классов A1 и A2" (== "классы A1 и A2 унаследованы от A") может трактоваться только как "объекты классов A1 и A2 обладают некоторым общим поведением, которое унаследовано ими от класса A".
согласен.
S> И все — мы не можем лишить объекты класса A1 какой-то части поведения объектов класса A. Можем только добавлять.
Не согласен.
Обобщение — есть обобщение. Где сказано, что обобщение должно давать пересечение (а не сумму) операций, доступных для объектов A1 и A2.
Пример обобщения:
Над фигурой возможны следующие операции: передвинуть, повернуть, изменить размер.
Но для фигур "точки", "окружности", "всей плоскости" часть из этих операций бессмыслена.
S>Используя альтернативные определения наследования можно получать альтернативные результаты. DG>>Почему? Почему мы по целому числу не можем получить натуральное? Из числа с плавающей точкой мы же можем получить целое... И из строки можем получить число... S>Ну и какое же натуральное число ты собрался получить из целого числа "-2"?
Либо 0, либо ошибку, либо 2, взависимости от контекста.
S> А из строки "бармаглот"?
либо ошибку, либо 0, либо некое число, если это слово на каком-то языке обозначает число.
DG>>Что полезного (кроме громоздкости) нам дает такая модель? S>А то, что другого варианта корректной эмуляции поведения этих двух классов чисел в рамках ООП я не вижу.
Человек оперируется окружающим миром в рамках ООП или нет?
Если в рамках ООП, то почему человек может, а машина — нет?
DG>>Имхо, более удобной является следующая модель: есть один объект "2". При этом этот объект является и натуральным числом, и целым числом, и дробным числом, и даже комплексным числом. Может даже являться строкой "2" или "10", если у нас заспецифицировано, что используется сооветствующая система счисления. S>Ну, смотря для чего эта модель является удобной. Видишь ли, если это целая двойка, то из нее нельзя извлечь квадратный корень — эта операция не определена для целых чисел.
Где не опредена? В языке C?
Кто сказал, что результат операции должен принадлежать тому же типу, что и операнды?
S> Как, впрочем, и для рациональных чисел ты не сможешь статически определить такую операцию. S> Более того, результат операции 5/2 радикальным образом зависит от того, какие 2 и 5 мы рассматриваем — целые либо рациональные. В твоей более удобной модели нет никакого способа понять, какой из этих результатов будет возвращен! S> Ведь у тебя ровно одна двойка и одна пятерка!
Возвращается всегда объект "2.5", но если мы работаем в поле целых чисел, то данных объект преобразуется к 2.
S> Еще рекомендую подумать над тем, что должен вернуть корень четвертой степени из единицы.
Возвращается множество {-1; 1; i}, если мы работаем в поле натуральных чисел, то результат будет 1, если действительных, то {-1; 1}, если в поле комплексных, то все множество.
DG>>И если мы при делении 7.20 на 3.60 получили объект "2", то этот объект мы можем легко передать кладовщику, чтобы он отгрузил данное (натуральное) кол-во объектов(вещей). S>А если не получили? Я не опротестовываю твою модель. Вот только типизация у нее получается сугубо динамическая. Боюсь, к ООП она не сможет иметь никакого отношения.
Кто сказал, что ООП должна быть статической?
Статическая типизация нам только дает только одно преимущество, по сравнению с динамической. Основным преимуществом статической типизации является то, что мы заранее (на этапе компиляции) можем проверить правильность выражений.
Всё. Больше преимуществ нет.
Re[14]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Т.е. получается, что мы должны отдельно завести тип целое число, определить для него операции, потом отдельно завести тип натуральное число опять определив все операции? DG>>Потом также отдельно завести типы [0..10), [-4..1000], [0..255], [0..65535] и т.д.? G>Да. Именно так. В противном случае наследование будет использовано не по назначению.
Приведи, пожалуйста, твое определение понятие "наследование".
DG>>ps DG>>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
G>Найди в гугле материал по "Liskov Substitution Principle", там на примерах объясняется, чем в реальной жизни чревато нарушение этого принципа. А чревато оно настолько, что надо вырывать руки нах программерам, которые им пренебрегают.
не нашел там примеров из реальной жизни
G>Ну вот, самый простой пример. Представь, что у тебя есть написанная и отлаженная подсистема, работающая с абстрактным типом А. Пусть А — это эллипс. Подсистема делает последовательность преобразований G>1) Растянуть А по оси ох в два раза. G>2) Повернуть А на 90 градусов по часовой стрелке. G>3) Растянуть А по оси ох в два раза. G>Легко видеть, что это преобразование инвариантно для эллипса, который является кругом. Попробуй написать подкласс А для круга, если перечисленные операции обладают деструктивной семантикой (т. е. они изменяют объект, а не создают новый — что нормально для императивных языков).
И в чем в данном случае проблема?
тип объекта будет меняться следующим образом.
1) круг -> эллипс
2) эллипс -> эллипс
3) эллипс -> круг
Re[15]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
G>>Ну вот, самый простой пример. Представь, что у тебя есть написанная и отлаженная подсистема, работающая с абстрактным типом А. Пусть А — это эллипс. Подсистема делает последовательность преобразований G>>1) Растянуть А по оси ох в два раза. G>>2) Повернуть А на 90 градусов по часовой стрелке. G>>3) Растянуть А по оси ох в два раза. G>>Легко видеть, что это преобразование инвариантно для эллипса, который является кругом. Попробуй написать подкласс А для круга, если перечисленные операции обладают деструктивной семантикой (т. е. они изменяют объект, а не создают новый — что нормально для императивных языков).
DG>И в чем в данном случае проблема?
DG>тип объекта будет меняться следующим образом. DG>1) круг -> эллипс DG>2) эллипс -> эллипс DG>3) эллипс -> круг
Проблема в том, что деструктивные операции замкнуты, т. е. не могут изменить класс объекта. Ну не может в ООП меняться тип во время жизни объекта. И мне крайне любопытно, как будут выглядеть реализации этой пары методов. Тот код, который ты присылал, совершенно не решает проблемы. Учти, что класс А не абстрактный и не интерфейс — это самый полноценный эллипс.
Re[16]: Задачка про Circle/Ellipse: "официальный" ответ.
G>Проблема в том, что деструктивные операции замкнуты, т. е. не могут изменить класс объекта.
G> Ну не может в ООП меняться тип во время жизни объекта.
кто сказал, что в ООП не может меняться тип объекта?
G> И мне крайне любопытно, как будут выглядеть реализации этой пары методов. Тот код, который ты присылал, совершенно не решает проблемы. Учти, что класс А не абстрактный и не интерфейс — это самый полноценный эллипс.
class Figure
{
object GetService(Type type)
{
if (type == typeof(Circle) && IsCircle)
return new Circle(this);
if (type == typeof(Ellipse) && IsEllipse)
return new Ellipse(this);
throw new Exception ("Данный тип не поддерживается данной фигурой");
}
}
Вот и, например, код для приведенного тобой алгоритма:
Здравствуйте, DarkGray, Вы писали:
DG>Не согласен. DG>Обобщение — есть обобщение. Где сказано, что обобщение должно давать пересечение (а не сумму) операций, доступных для объектов A1 и A2. DG>Пример обобщения: DG>Над фигурой возможны следующие операции: передвинуть, повернуть, изменить размер. DG>Но для фигур "точки", "окружности", "всей плоскости" часть из этих операций бессмыслена.
В таком случае т ы получаешь класс тигра, который ест все ((с) А.А.Милн). Забавная штука, но практической ценности не имеющая. Это означает отсутствие какой бы то ни было классификации, поскольку любой объект потенциально обладает поведением любого другого объекта, стало быть по его типу нельзя делать никаких выводов о его поведении. Примером такой системы может считаться JavaScript. Не имею ничего против подобных моделей, но лично предпочитаю иметь в запасе несколько более декларативно выразительные средства разработки. S>>Используя альтернативные определения наследования можно получать альтернативные результаты. S>>Ну и какое же натуральное число ты собрался получить из целого числа "-2"? DG>Либо 0, либо ошибку, либо 2, взависимости от контекста.
Очень интересно. А как ты определишь "контекст"? Да еще так, чтобы он был достаточно очевиден? S>> А из строки "бармаглот"? DG>либо ошибку, либо 0, либо некое число, если это слово на каком-то языке обозначает число.
Увы, не означает. DG>>>Что полезного (кроме громоздкости) нам дает такая модель? S>>А то, что другого варианта корректной эмуляции поведения этих двух классов чисел в рамках ООП я не вижу.
DG>Человек оперируется окружающим миром в рамках ООП или нет?
Чего? DG>Если в рамках ООП, то почему человек может, а машина — нет?
Чего?
S>>Ну, смотря для чего эта модель является удобной. Видишь ли, если это целая двойка, то из нее нельзя извлечь квадратный корень — эта операция не определена для целых чисел. DG>Где не опредена? В языке C?
В математике, друг мой, в математике. Которая доказала свою полезность массой замечательных результатов, наблюдаемых на каждом шагу. DG>Кто сказал, что результат операции должен принадлежать тому же типу, что и операнды?
Никто не сказал. Просто в математике операции, про которые мы говорим, традиционно определяются именно так. Никто не мешает сделать незамкнутую операцию сложения двух элементов множества. DG>Возвращается всегда объект "2.5", но если мы работаем в поле целых чисел, то данных объект преобразуется к 2.
Очень здорово. А это комплексная 2.5 или вещественная? Или это кватернион? А может, это комплексный тензор ранга 1? DG>Возвращается множество {-1; 1; i}, если мы работаем в поле натуральных чисел, то результат будет 1, если действительных, то {-1; 1}, если в поле комплексных, то все множество.
Ну, во первых должно все-таки вернуться множество {-1; 1; i;-i}. Во вторых, это выглядит несколько искусственно, согласись. Для того, чтобы банально определить, какое натуральное число дает 16, будучи умноженным само на себя четырежды, мы вынуждены выполнять комплексные операции с множествами, а потом выбирать те из результатов, которые лежат в том множестве, с которым мы работаем. Имхо, это неоправданное усложнение простой модели. По крайней мере, в математике по этому пути не пошли. DG>Кто сказал, что ООП должна быть статической?
Да никто не сказал. Пожалуйста — вот тебе JavaScript. DG>Статическая типизация нам только дает только одно преимущество, по сравнению с динамической. Основным преимуществом статической типизации является то, что мы заранее (на этапе компиляции) можем проверить правильность выражений. DG>Всё. Больше преимуществ нет.
Ну как тебе сказать. Вообще говоря, помимо этого можно эффективно выполнять некоторые оптимизации.
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[17]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, INTP_mihoshi, Вы писали: S>>Более того, результат операции 5/2 радикальным образом зависит от того, какие 2 и 5 мы рассматриваем — целые либо рациональные. В твоей более удобной модели нет никакого способа понять, какой из этих результатов будет возвращен! Ведь у тебя ровно одна двойка и одна пятерка! Еще рекомендую подумать над тем, что должен вернуть корень четвертой степени из единицы. INT>Это все глюки sintactic sugar. Целочисленное деление и целое деление — это две большие разницы. И приведение типов — это тоже операция, даже если она не требует упоминания в тексте.
Не понял. Про какой такой сахар ты упомянул? Про то, что у нас есть возможность неявно преобразовывать 2 к 2.0? Ну так это и есть то, о чем я говорю — целая двойка — это совсем другой объект, чем 2.0. INT>Типизация у ООП тоже не имеет отношения. Типизация — это свойство контроля правильности и sintactic sugar для операций (чтобы можно было разные / отличать по типам операндов, а не указывать каждый раз).
Ну, я не уверен в правильности своих определений, но я имел в виду не перегрузку операций по типам операндов, а то, что ADT однозначно определяет набор применимых к нему операций. Перегрузка — совершенно необязательный атрибут. Можешь считать, что 2.0 и 2 отличаются ровно тем, что к первому применимо DivideFloats(,), а ко второму — нет. INT>Кстати, чем динамическая типизация отличается от статической кроме того, что в ней иногда можно опускать упоминания операции приведения типа?
Ну, вообще-то в статической типизации тоже можно опускать операции приведения типа. Основное отличие — в том, что тип выражения нельзя определить до его вычисления. В предлагаемой модели результат конкатенации строк "1" и "0" ведет себя как число, а результат конкатенации строк "де" и "дуля" — нет.
Хуже того, в предлагаемой модели вообше как-то странно говорить о типе, т.к. он не определяет никаких свойств объекта. Потому как к нему потенциально применима любая операция, а стало быть все объекты равноправны. Это уже нестрогая типизация.
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[18]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Не согласен. DG>>Обобщение — есть обобщение. Где сказано, что обобщение должно давать пересечение (а не сумму) операций, доступных для объектов A1 и A2. DG>>Пример обобщения: DG>>Над фигурой возможны следующие операции: передвинуть, повернуть, изменить размер. DG>>Но для фигур "точки", "окружности", "всей плоскости" часть из этих операций бессмыслена. S>В таком случае т ы получаешь класс тигра, который ест все ((с) А.А.Милн).
Почему всё? Я же четко определил, что для фигуры определены 3 операции. А дальше идет нечеткая логика:
для большинства (но не для всех) фигур — эти операции выполняются корректно.
S> Забавная штука, но практической ценности не имеющая. Это означает отсутствие какой бы то ни было классификации, поскольку любой объект потенциально обладает поведением любого другого объекта, стало быть по его типу нельзя делать никаких выводов о его поведении. Примером такой системы может считаться JavaScript. Не имею ничего против подобных моделей, но лично предпочитаю иметь в запасе несколько более декларативно выразительные средства разработки.
Удобнее смешивать статическую и динамическую типизацию, и на практике, динамическая типизация используется очень часто.
Взять тот же графический редактор. Без динамической типизации редактор просто не написать.
S>>>Используя альтернативные определения наследования можно получать альтернативные результаты. S>>>Ну и какое же натуральное число ты собрался получить из целого числа "-2"? DG>>Либо 0, либо ошибку, либо 2, взависимости от контекста. S>Очень интересно. А как ты определишь "контекст"? Да еще так, чтобы он был достаточно очевиден?
например, по тому типу, которому присваивается результат.
или, например, по настройкам в реестре или еще где-то.
или банально:
void Func()
{
context<int>
{
Out(2/5);
}
}
DG>>>>Что полезного (кроме громоздкости) нам дает такая модель? S>>>А то, что другого варианта корректной эмуляции поведения этих двух классов чисел в рамках ООП я не вижу.
Что значит корректной? и что значит рамки ООП?
DG>>Кто сказал, что результат операции должен принадлежать тому же типу, что и операнды? S>Никто не сказал. Просто в математике операции, про которые мы говорим, традиционно определяются именно так. Никто не мешает сделать незамкнутую операцию сложения двух элементов множества. DG>>Возвращается всегда объект "2.5", но если мы работаем в поле целых чисел, то данных объект преобразуется к 2. S>Очень здорово. А это комплексная 2.5 или вещественная? Или это кватернион? А может, это комплексный тензор ранга 1?
Если мы "знаем" про тензоры и кватернионы, то это и второе, и третье. и четвертое.
DG>>Возвращается множество {-1; 1; i}, если мы работаем в поле натуральных чисел, то результат будет 1, если действительных, то {-1; 1}, если в поле комплексных, то все множество. S>Ну, во первых должно все-таки вернуться множество {-1; 1; i;-i}. Во вторых, это выглядит несколько искусственно, согласись. Для того, чтобы банально определить, какое натуральное число дает 16, будучи умноженным само на себя четырежды, мы вынуждены выполнять комплексные операции с множествами, а потом выбирать те из результатов, которые лежат в том множестве, с которым мы работаем. Имхо, это неоправданное усложнение простой модели. По крайней мере, в математике по этому пути не пошли.
на практике используют lazy-подход. Такой подход часто применяется при работе с матрицами.
При таком подходе операции реально выполняются только когда действительно нужен результат.
class Number
{
SqrtResult Sqrt()
{
return new SqrtResult(this);
}
}
class SqrtResult
{
Number source;
operator Number()
{
return SimpleSqrt(source);
}
operator Complex()
{
return ComplexSqrt(source);
}
}
DG>>Кто сказал, что ООП должна быть статической? S>Да никто не сказал. Пожалуйста — вот тебе JavaScript.
Тогда уж Smalltalk, JavaScript — все-таки скрипт(интерпетатор), а не полноценный язык.
DG>>Статическая типизация нам только дает только одно преимущество, по сравнению с динамической. Основным преимуществом статической типизации является то, что мы заранее (на этапе компиляции) можем проверить правильность выражений. DG>>Всё. Больше преимуществ нет. S>Ну как тебе сказать. Вообще говоря, помимо этого можно эффективно выполнять некоторые оптимизации.
Оптимизации можно делать и при динамической типизации, например, используя тот же lazy-подход.
Re[17]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали: DG>кто сказал, что в ООП не может меняться тип объекта? DG>
DG>class Figure
DG>{
DG> object GetService(Type type)
DG> {
DG> if (type == typeof(Circle) && IsCircle)
DG> return new Circle(this);
DG> if (type == typeof(Ellipse) && IsEllipse)
DG> return new Ellipse(this);
DG> throw new Exception ("Данный тип не поддерживается данной фигурой");
DG> }
DG>}
DG>
Ну что за подтасовки! Это та самая эмуляция поведения неоюбъеектных сущностей при помощи ООП. При помощи (конечно же!) создания нового объекта. Ты же только что критиковал меня за введение двух разных двоек. Ай-яй-яй, как нехорошо .
Увы, то, что ты потерял, называется Identity.
Вот этот код:
на самом деле действительно приводит к тому, что иходный f совпадает с конечным. Потому, что все операции применяются к трем совершенно разным временным объектам
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[18]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, DarkGray, Вы писали: DG>>кто сказал, что в ООП не может меняться тип объекта? DG>>
DG>>class Figure
DG>>{
DG>> object GetService(Type type)
DG>> {
DG>> if (type == typeof(Circle) && IsCircle)
DG>> return new Circle(this);
DG>> if (type == typeof(Ellipse) && IsEllipse)
DG>> return new Ellipse(this);
DG>> throw new Exception ("Данный тип не поддерживается данной фигурой");
DG>> }
DG>>}
DG>>
S>Ну что за подтасовки! Это та самая эмуляция поведения неоюбъеектных сущностей при помощи ООП. При помощи (конечно же!) создания нового объекта. Ты же только что критиковал меня за введение двух разных двоек. Ай-яй-яй, как нехорошо .
То что создается новый объект — это деталь реализации.
Могу функцию GetService объявить так (так называемый "жирный интерфейс"):
class Figure:
Circle,
Ellipse
{
object GetService(Type type)
{
if (type == typeof(Circle) && IsCircle)
return this;
if (type == typeof(Ellipse) && IsEllipse)
return this;
throw new Exception ("Данный тип не поддерживается данной фигурой");
}
}
Могу объявить вот так (подход под названием "подключение реализаций"):
class Figure
{
public Figure()
{
this.circle = new Circle(this);
this.ellipse = new Ellipse(this);
}
Circle circle;
Ellipse ellipse;
object GetService(Type type)
{
if (type == typeof(Circle) && IsCircle)
return circle;
if (type == typeof(Ellipse) && IsEllipse)
return ellipse;
throw new Exception ("Данный тип не поддерживается данной фигурой");
}
}
S>Увы, то, что ты потерял, называется Identity.
С чего ты это взял?
У классов Circle и Ellipse перекрытая операция ==, которая сравнивает через фигуру.
ps
Агрегирование в Com-е построено именно по такому принципу. Но аггрегирование — это и есть наследование.
S>Вот этот код: S>
S>на самом деле действительно приводит к тому, что иходный f совпадает с конечным. Потому, что все операции применяются к трем совершенно разным временным объектам
Для внешнего кода — это один и тот же объект, потому что он выглядит как один объект.
Re[17]: Задачка про Circle/Ellipse: "официальный" ответ.
G>>Проблема в том, что деструктивные операции замкнуты, т. е. не могут изменить класс объекта.
G>> Ну не может в ООП меняться тип во время жизни объекта.
DG>кто сказал, что в ООП не может меняться тип объекта?
G>> И мне крайне любопытно, как будут выглядеть реализации этой пары методов. Тот код, который ты присылал, совершенно не решает проблемы. Учти, что класс А не абстрактный и не интерфейс — это самый полноценный эллипс.
DG>
DG>class Figure
DG>{
DG> object GetService(Type type)
DG> {
DG> if (type == typeof(Circle) && IsCircle)
DG> return new Circle(this);
DG> if (type == typeof(Ellipse) && IsEllipse)
DG> return new Ellipse(this);
DG> throw new Exception ("Данный тип не поддерживается данной фигурой");
DG> }
DG>}
DG>
DG>Вот и, например, код для приведенного тобой алгоритма: DG>
DG>Какое из ООП-правил нарушается для данного кода?
1) Этот код просто-напросто не работает. У тебя нигде не сохраняется промежуточный результат. У тебя только что созданный объект удаляется после каждой операции. Нехорошо. Нерабочий код за пример не канает.
2) В реальном мире ты не всегда можешь менять реализацию компонента. Я же сказал — он уже написан и отлажен.
И написан он вот как:
Компонент к тому-же большой и сложный, и отрефакторить нет возможности. Очень реалистичная ситуация.
ВОТ ТЕПЕРЬ напиши такой подкласс Ellipse, который будет Circle. Об этом были примеры Лисков.
Re[19]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>Почему всё? Я же четко определил, что для фигуры определены 3 операции.
Мало ли что ты определил. А для строк определены еще пять. Поскольку у тебя неизвестно, означает ли строка "треугольник" фигуру, придется всем реализовывать по восемь. Или ты заплатки приделаешь? Типа вот тут была строгая типизация, а теперь не совсем? DG>А дальше идет нечеткая логика: DG>для большинства (но не для всех) фигур — эти операции выполняются корректно.
Угу. А почему бы вероятность корректного выполнения не ввести? Чтобы уж до конца по нечеткой логике пройти? Иначе ценность этого утверждения равна нулю. Я бы, как работодатель, на утверждение "моя программа в некоторых (но не во всех) случаях делает что-то осмысленное", сказал бы "ну, в некоторых (не во всех) случаях я заплачу" . DG>Удобнее смешивать статическую и динамическую типизацию, и на практике, динамическая типизация используется очень часто. DG>Взять тот же графический редактор. Без динамической типизации редактор просто не написать.
Честно говоря, ни разу не пробовал писать объектно-ориентированный графический редактор. Почему-то мне не кажется, что я не напишу его без динамической типизации.
DG>например, по тому типу, которому присваивается результат. DG>или, например, по настройкам в реестре или еще где-то.
Я рад. Отладка такой программы будет представлять собой сущее удовольствие. Особенно меня интересует процесс введения новых контекстов и обеспечение их миграции в распределенной среде (ну или хотя бы при инсталляции ) DG>или банально: DG>
Ну вот примерно это я себе и представлял, когда ты говорил о контексте. А теперь скажи мне, чем это вдруг стало лучше старого доброго
void Func()
{
Out(int(2.0)/int(5.0);
}
? (Я, конечно, имею в виду совершенно обратный случай) DG>>>>>Что полезного (кроме громоздкости) нам дает такая модель? S>>>>А то, что другого варианта корректной эмуляции поведения этих двух классов чисел в рамках ООП я не вижу.
DG>Что значит корректной?
А той, что привычные нам математические операции имеют привычную семантику. И умножение вектора на скаляр дает всегда вектор, а не только утром в понедельник. И некоторым комплексным числам соответствуют целые, а некоторым — нет. Поэтому преобразование из целых в комплексные мы можем себе позволить сделать неявно, а в обратную сторону — явно. А еще лучше — потребовать ловить исключение InvalidTypeCast на случай отсутствия соответствия каким-то явным образом, хоть
int Int(Complex c)
{
InvalidTypeCastYields<0>
{
return (int)c;
}
}
хоть
int Int(Complex c)
{
try
return (int)c;
catch(InvalidTypeCast)
return 0;
}
DG>и что значит рамки ООП?
А это как раз и значит, что поведение объектов детерминировано. DG>Если мы "знаем" про тензоры и кватернионы, то это и второе, и третье. и четвертое.
Зашибись. То есть от того, что я взял и в своем углу проекта кватернион заимплементил, ты должен там у себя весь код прошерстить на предмет потери семантики? Очень здорово. DG>на практике используют lazy-подход. Такой подход часто применяется при работе с матрицами. DG>При таком подходе операции реально выполняются только когда действительно нужен результат.
А, ну это — по-нашему. То есть мы откладываем валидацию до тех приятных времен, когда она уже ничем не сможет нам помочь Я представляю счастье того, кто обнаружит неприводимость строки "дапошливывсе" к дате експирации кредитной карточки в webcommerce проекте. Его ждет незабываемый поиск возможного источника этого чуда, потому что отрепортят ему только локейшн чтения, а никак не записи.
DG>Тогда уж Smalltalk, JavaScript — все-таки скрипт(интерпетатор), а не полноценный язык.
Э, брат, не пойдет. Смолток — динамически, но строго типизированный язык. А ты пытаешься тут изобрести язык, где пока Ева не взяла то, что ей дали, в рот, оно могло быть вовсе и не яблоком. DG>Оптимизации можно делать и при динамической типизации, например, используя тот же lazy-подход.
Ага. Только совсем другие. Я тебе не открою тайну, если скажу, что lazy-подход возможен и в статике?
... << RSDN@Home 1.1.3 beta 2 >>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[18]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Какое из ООП-правил нарушается для данного кода? G>1) Этот код просто-напросто не работает. У тебя нигде не сохраняется промежуточный результат. У тебя только что созданный объект удаляется после каждой операции. Нехорошо. Нерабочий код за пример не канает.
Результат сохраняется в самой фигуре.
G>2) В реальном мире ты не всегда можешь менять реализацию компонента. Я же сказал — он уже написан и отлажен. G>И написан он вот как: G>
G>Компонент к тому-же большой и сложный, и отрефакторить нет возможности. Очень реалистичная ситуация. G>ВОТ ТЕПЕРЬ напиши такой подкласс Ellipse, который будет Circle. Об этом были примеры Лисков.
например, вот так:
class Figure
{
Hashtable storage = new Hashtable();
object GetService(Type type)
{
if (type == typeof(Circle) && IsCircle)
return new Circle(this);
if (type == typeof(Ellipse) && IsEllipse)
return new Ellipse(this);
throw new Exception ("Данный тип не поддерживается данной фигурой");
}
operator Ellipse()
{
return (Ellipse)GetService(typeof(Ellipse));
}
operator Circle()
{
return (Circle)GetService(typeof(Circle));
}
}
class Ellipse
{
public Ellipse(Figure figure){this.figure = figure;}
Figure figure;
public double Angle {get {return (double)figure.storage["Angle"];} set {figure.storage["Angle"] = value;}
public double Width {get {return (double)figure.storage["Width"];} set {figure.storage["Width"] = value;}
public void ResizeOX(double k)
{
Width = Width * k;
}
public void Rotate(double angle)
{
Angle = Angle + angle;
}
}
//вызов уже написанного компонента:void Main()
{
Figure f = new Figure();
Component c = new Component();
c.DoSomeThing(f);
}
Re[19]: Задачка про Circle/Ellipse: "официальный" ответ.
S>> Забавная штука, но практической ценности не имеющая. Это означает отсутствие какой бы то ни было классификации, поскольку любой объект потенциально обладает поведением любого другого объекта, стало быть по его типу нельзя делать никаких выводов о его поведении. Примером такой системы может считаться JavaScript. Не имею ничего против подобных моделей, но лично предпочитаю иметь в запасе несколько более декларативно выразительные средства разработки. DG>Удобнее смешивать статическую и динамическую типизацию, и на практике, динамическая типизация используется очень часто. DG>Взять тот же графический редактор. Без динамической типизации редактор просто не написать.
А вот здесь согласен Действительно динамическая типизация удобнее. Не надо мозг в трубочку заворачивать при проектировании объектной модели.
DG>>>Кто сказал, что ООП должна быть статической? S>>Да никто не сказал. Пожалуйста — вот тебе JavaScript.
А кто сказал что ООП должно быть императивным? Да здравствуют динамически типизированные функциональные ООП языки — долой прокрустово ложе принципа Лисков и деструктивных операций! Сбросим, так сказать, цепи!
DG>>>Статическая типизация нам только дает только одно преимущество, по сравнению с динамической. Основным преимуществом статической типизации является то, что мы заранее (на этапе компиляции) можем проверить правильность выражений. DG>>>Всё. Больше преимуществ нет. S>>Ну как тебе сказать. Вообще говоря, помимо этого можно эффективно выполнять некоторые оптимизации. DG>Оптимизации можно делать и при динамической типизации, например, используя тот же lazy-подход.
Что-то мне этот разговор сильно напоминает Оптимизации на порядок проще делать при наличии статической типизации. Здесь спорить не о чем. It's fuckt.
Re[20]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Почему всё? Я же четко определил, что для фигуры определены 3 операции. S>Мало ли что ты определил. А для строк определены еще пять. Поскольку у тебя неизвестно, означает ли строка "треугольник" фигуру, придется всем реализовывать по восемь. Или ты заплатки приделаешь? Типа вот тут была строгая типизация, а теперь не совсем?
Но в VisualBasic-е именно так и сделано.
Хотим работаем через статическую типизацию, хотим — работаем через динамическую.
DG>>А дальше идет нечеткая логика: DG>>для большинства (но не для всех) фигур — эти операции выполняются корректно. S>Угу. А почему бы вероятность корректного выполнения не ввести? Чтобы уж до конца по нечеткой логике пройти? Иначе ценность этого утверждения равна нулю. Я бы, как работодатель, на утверждение "моя программа в некоторых (но не во всех) случаях делает что-то осмысленное", сказал бы "ну, в некоторых (не во всех) случаях я заплачу" .
Может быть где-нибудь в вакууме программа и работает всегда и на всех данных.
Но на практике, среднестатичная программа работает без ошибок на среднестатичном железе в среднестатичной ОС при использовании среднестатичного ПО при обработке среднестатичных данных и т.д. А шаг влево и вправо каряется gpf-ом.
Т.е. фактически, когда разработчик говорит "моя программа работает всегда" — это он просто забыл снял розовые очки.
DG>>например, по тому типу, которому присваивается результат. DG>>или, например, по настройкам в реестре или еще где-то. S>Я рад. Отладка такой программы будет представлять собой сущее удовольствие. Особенно меня интересует процесс введения новых контекстов и обеспечение их миграции в распределенной среде (ну или хотя бы при инсталляции )
S>Ну вот примерно это я себе и представлял, когда ты говорил о контексте. А теперь скажи мне, чем это вдруг стало лучше старого доброго S>void Func() S>{ S> Out(int(2.0)/int(5.0); S>} S>? (Я, конечно, имею в виду совершенно обратный случай)
Тем, что контекст может быть определен сразу на всю программу, может быть определен косвенно, может быть определен конечный пользователем и т.д.
DG>>>>>>Что полезного (кроме громоздкости) нам дает такая модель? S>>>>>А то, что другого варианта корректной эмуляции поведения этих двух классов чисел в рамках ООП я не вижу.
DG>>Что значит корректной? S>А той, что привычные нам математические операции имеют привычную семантику. И умножение вектора на скаляр дает всегда вектор, а не только утром в понедельник. И некоторым комплексным числам соответствуют целые, а некоторым — нет. Поэтому преобразование из целых в комплексные мы можем себе позволить сделать неявно, а в обратную сторону — явно. А еще лучше — потребовать ловить исключение InvalidTypeCast на случай отсутствия соответствия каким-то явным образом, хоть
DG>>и что значит рамки ООП? S>А это как раз и значит, что поведение объектов детерминировано. DG>>Если мы "знаем" про тензоры и кватернионы, то это и второе, и третье. и четвертое. S>Зашибись. То есть от того, что я взял и в своем углу проекта кватернион заимплементил, ты должен там у себя весь код прошерстить на предмет потери семантики? Очень здорово.
нет, потому что контексты разные. В своем контексте я кватерионы не объявлял.
DG>>на практике используют lazy-подход. Такой подход часто применяется при работе с матрицами. DG>>При таком подходе операции реально выполняются только когда действительно нужен результат. S>А, ну это — по-нашему. То есть мы откладываем валидацию до тех приятных времен, когда она уже ничем не сможет нам помочь Я представляю счастье того, кто обнаружит неприводимость строки "дапошливывсе" к дате експирации кредитной карточки в webcommerce проекте. Его ждет незабываемый поиск возможного источника этого чуда, потому что отрепортят ему только локейшн чтения, а никак не записи.
А вот здесь уже статика и нужна, т.е. компилятор (не программист) видит, что объект будет использоваться в качестве даты, поэтому при присваивании значения необходимо проверить, есть ли преобразование к дате или нет.
Именно по такой схеме построены, например, шаблоны в C++.
Re[20]: Задачка про Circle/Ellipse: "официальный" ответ.
S>>>Ну как тебе сказать. Вообще говоря, помимо этого можно эффективно выполнять некоторые оптимизации. DG>>Оптимизации можно делать и при динамической типизации, например, используя тот же lazy-подход. G>Что-то мне этот разговор сильно напоминает Оптимизации на порядок проще делать при наличии статической типизации. Здесь спорить не о чем. It's fuckt.
Но код получается проще и чище, когда определением статической типизации занимается компилятор, а не программист.
Вопрос на засыпку:
Сколько интерфейсов необходимо определить, чтобы уметь работать с коллекциями?
Коллекции бывают: readonly/не readonly, с порядком, без порядка, с добавлением/без добавления, с удалением/без удаления, с последовательным доступом/индексным доступом/без доступа и т.д.
Сколько интерфейсов будет поддерживать, например, массив?
Как будет описываться, например, компонент, который сортирует коллекции? Какие интерфейсы он должне требовать в декларации?
Re[21]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
S>>>>Ну как тебе сказать. Вообще говоря, помимо этого можно эффективно выполнять некоторые оптимизации. DG>>>Оптимизации можно делать и при динамической типизации, например, используя тот же lazy-подход. G>>Что-то мне этот разговор сильно напоминает Оптимизации на порядок проще делать при наличии статической типизации. Здесь спорить не о чем. It's fuckt.
DG>Но код получается проще и чище, когда определением статической типизации занимается компилятор, а не программист.
Да, конечно, все так. Это теоретически возможно. Скоро такие технологии дойдут до зрелых production-систем, лет эдак через 5-10. Я с нетерпением жду времени, когда динамически типизированные языки позволят писать очень быстрые программы. Но динамическая типизация хоть и облегчает дизайн, но не отменяет принцип Лисков.Наследование должно применяться по назначению, иначе поддержка системы (или итеративная разработка) превратится в ад кромешный. Наступишь на эти грабли сам на достаточно сложной системе — это станет очевидно.
Его отменяют функциональные языки. Интересно, когда научатся делать эффективный copy elimination для функциональных языков, ведь с этим все гораздо сложнее. И интереснее
DG>Вопрос на засыпку: DG>Сколько интерфейсов необходимо определить, чтобы уметь работать с коллекциями?
DG>Коллекции бывают: readonly/не readonly, с порядком, без порядка, с добавлением/без добавления, с удалением/без удаления, с последовательным доступом/индексным доступом/без доступа и т.д.
DG>Сколько интерфейсов будет поддерживать, например, массив?
DG>Как будет описываться, например, компонент, который сортирует коллекции? Какие интерфейсы он должне требовать в декларации?
Да, здесь при ООП дизайне в самом деле можно свернуть себе шею. Чтобы этого не произошло, в языке С++ нам помогут templates. И при этом типизация остается статической. Я думаю, ты это знаешь и без моих комментариев.
Re[19]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>>>Какое из ООП-правил нарушается для данного кода? G>>1) Этот код просто-напросто не работает. У тебя нигде не сохраняется промежуточный результат. У тебя только что созданный объект удаляется после каждой операции. Нехорошо. Нерабочий код за пример не канает.
DG>Результат сохраняется в самой фигуре.
Он у тебя не сохранялся. У тебя фигура удалялась сразу после изменения, Синклер тебе тоже на это указал. Ну да ладно, ты приготовил новый код.
G>>2) В реальном мире ты не всегда можешь менять реализацию компонента. Я же сказал — он уже написан и отлажен. G>>И написан он вот как:
skipped DG>например, вот так:
skipped
То, что ты написал, это абсолютно тоже самое, что и
class Figure : public IEllipse, ICircle
{
}
только выглядит страшнее. Но на самом деле ты так делать не можешь, потому как Ellipse тоже является частью нашего компонента, он уже написан. Трогать его нельзя, можно только наследоваться. Попробуй, и посмотри что получится. Вот _в_этом_ смысл принципа Лисков.
Re[22]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Вопрос на засыпку: DG>>Сколько интерфейсов необходимо определить, чтобы уметь работать с коллекциями?
DG>>Коллекции бывают: readonly/не readonly, с порядком, без порядка, с добавлением/без добавления, с удалением/без удаления, с последовательным доступом/индексным доступом/без доступа и т.д.
DG>>Сколько интерфейсов будет поддерживать, например, массив?
DG>>Как будет описываться, например, компонент, который сортирует коллекции? Какие интерфейсы он должне требовать в декларации?
G>Да, здесь при ООП дизайне в самом деле можно свернуть себе шею. Чтобы этого не произошло, в языке С++ нам помогут templates. И при этом типизация остается статической. Я думаю, ты это знаешь и без моих комментариев.
Но остается другая проблема. C++-шаблоны совсем не поддерживают компонентность. Нельзя написать на C++ компонент, который наружу выставляет шаблонный метод или шаблонный объект.
Re[20]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>>>Какое из ООП-правил нарушается для данного кода? G>>>1) Этот код просто-напросто не работает. У тебя нигде не сохраняется промежуточный результат. У тебя только что созданный объект удаляется после каждой операции. Нехорошо. Нерабочий код за пример не канает.
DG>>Результат сохраняется в самой фигуре. G>Он у тебя не сохранялся. У тебя фигура удалялась сразу после изменения, Синклер тебе тоже на это указал. Ну да ладно, ты приготовил новый код.
Можно заметить, что новый код только доопределял идею первоначального кода
G>>>2) В реальном мире ты не всегда можешь менять реализацию компонента. Я же сказал — он уже написан и отлажен. G>>>И написан он вот как: G>skipped DG>>например, вот так: G>skipped G>То, что ты написал, это абсолютно тоже самое, что и
G>class Figure : public IEllipse, ICircle G>{ G>}
Не то же самое.
В моем случае, отдельные грани могут добавляться прямо в runtime, могут разрабатываться по плагинной технологии (поведение класса расширается конечным пользователем) и т.д.
В случае прямого наследования от IEllipse и ICircle мы всех этих прелестей лишены.
G>только выглядит страшнее. Но на самом деле ты так делать не можешь, потому как Ellipse тоже является частью нашего компонента, он уже написан. Трогать его нельзя, можно только наследоваться. Попробуй, и посмотри что получится. Вот _в_этом_ смысл принципа Лисков.
В нормальной архитектуре, компоненты работают не с классами, а с интерфейсами.
Поэтому все вышеприведенное остается в силе.
Re[21]: Задачка про Circle/Ellipse: "официальный" ответ.
G>>То, что ты написал, это абсолютно тоже самое, что и
G>>class Figure : public IEllipse, ICircle G>>{ G>>}
DG>Не то же самое. DG>В моем случае, отдельные грани могут добавляться прямо в runtime, могут разрабатываться по плагинной технологии (поведение класса расширается конечным пользователем) и т.д. DG>В случае прямого наследования от IEllipse и ICircle мы всех этих прелестей лишены.
А-а, так ты пытаешься эмулировать динамическую типизацию? А я-то смотрю — и не догоняю, к чему это все. Так бы и сказал сразу. Тады понятно.
Но в любом случае, ты отдаешь себе отчет в том, что просто предложил обходной путь (workaround), но не опроверг принцип Лисков?
G>>только выглядит страшнее. Но на самом деле ты так делать не можешь, потому как Ellipse тоже является частью нашего компонента, он уже написан. Трогать его нельзя, можно только наследоваться. Попробуй, и посмотри что получится. Вот _в_этом_ смысл принципа Лисков.
DG>В нормальной архитектуре, компоненты работают не с классами, а с интерфейсами. DG>Поэтому все вышеприведенное остается в силе.
Ну, это ты зря совсем. Компонента — это то, что я захочу, если я дизайнер. Возьму — и назову функционально законченную группу плюсовых классов "компонентой", и не буду заморачиваться, так как использовать это буду тоже из С++. Но если ты настаиваешь, пусть это будет "подсистема", так нейтральнее.
А теперь по делу. Наследование есть наследование, а реализация интерфейса — это другое, хоть и похоже. Ты, фактически предлагаешь отказаться от наследования. А мы обсуждаем именно его, отношение наследования! Ну не знаю как "мы", я по крайней мере.
В С++, например, от такого подхода нет никакого толку, благодаря наличию множественного наследования и темплейтов. Если использовать сильные стороны С++, то интерфейсы там встретишь нечасто, и уж точно они не являются основной парадигмой (если конечно не нашиковать приложение на мелкие COM объекты, чего делать низзя ни в коем случае без строгих на то показаний — уж больно велик семантический разрыв между COM и С++).
Re[22]: Задачка про Circle/Ellipse: "официальный" ответ.
G>А теперь по делу. Наследование есть наследование, а реализация интерфейса — это другое, хоть и похоже. Ты, фактически предлагаешь отказаться от наследования. А мы обсуждаем именно его, отношение наследования! Ну не знаю как "мы", я по крайней мере.
Наследование... У наследования есть два разных смысла. Первый смысл: это наследование реализации — новый класс наследует поведение, аттрибуты, реализованные интерфейсы и т.д. от предка.
Второй смысл — это наследование интерфейса — этот термин не совсем правильный (т.к. а что именно мы наследуем при наследовании от интерфейса?). Поэтому правильно говорить, что класс реализует интерфейсы, а не наследуется от них.
Все что я говорил выше про наследовании — это было именно наследование реализации.
У Лисковой, наоборот, говорится про интерфейсы, т.е. если класс наследуется от интерфейса эллипс, то реализация этого наследования должна быть корректной.
Рассмотрим класс "эллипс". Этот класс может обозначать две разные сущности:
1. Элиппс, не являющейся кругом. Или другими словами — это эллипс без вырожденных случаев.
2. Эллипс с учетом всех вырожденных случаев. Фактически такой эллипс должен содержать поведение и для таких вырожденных случаев, как круг, точка и может быть такие вырожденные случаи, как "плоскость"(эллипс бесконечного радиуса), "отрезок" (эллипс нулевой длины и какой-то ширины), "прямая" (эллипс нулевой длины и бесконечного радиуса),
"полоса" (эллипс какой-то ширины и бесконечной длины).
Теперь поговорим об интерфейсах для данного случая.
Интерфейсы получаются следующие:
Не вырожденный эллипс ( 0 < ширина < бесконечность, 0 < высота < бесконечность, ширина != высота)
Круг
"Точка"
"Плоскость"
"Полоса"
"Отрезок"
Обобщенный эллипс
Рассмотрим интерфейс "круг". Здесь тоже возможны два разных интерфейса, которые скрываются под одним словом "круг".
С одной стороны — это интерфейс, который поддерживает все операции доступные над кругом, в том числе и растяжение по одной из оси, с другой стороны — возможен интерфейс, который допускает только те операции, результат которых остается в поле "круг" (т.е. в результате после операции круг остается кругом).
рассмотрим интерфейс "обобщенный эллипс".
Например, метод "Дай оси симметрии". такой метод в отличии от такого же метода для невырожденного эллипса, должен возвращать не только пару осей, как для невырожденного эллипса, но и должен уметь возвращать множество из бесконечного числа осей симметрии, т.к. у частного случая "круг" именно такой набор осей симметрии.
Или возьмем метод "Дай больший радиус", если для невырожденного эллипса такой метод имел смысл, то для обобщенного эллипса — этот метод уже может заканчиваться ошибкой, т.к. большего радиуса у вырожденных случаев (круг/точка) просто нет.
Получается, что когда мы говорим про интерфейс "обобщенный эллипс" мы также подразумеваем два разных интерфейса: один интерфейс — это операции, которые доступны над любым вариантом эллипса, второй интерфейс — это набор операций, которые доступны хотя бы над одним вариантом эллипса.
Компоненты не имеют право, напрямую, ссылаться на типы-реализации (иначе получаем нарушение инкапсуляции и нарушение полиморфизма). Компоненты имеют право только пользоваться вышеприведенными интерфейсами.
Теперь вернемся к наследованию, как я уже вышеупомянул, термин "наследование" в первую очередь означает наследование реализации.
Рассмотрим класс-реализацию "обобщенный эллипс".
Этот класс должен реализовывать все вышеуказанные интерфейсы, а их у нас получилось, как минимум 12 штук.
Т.е. получается, что класс-реализация "обобщенный эллипс" содержит реализации интерфейсов "круг", "точка", "невырожденный эллипс" и т.д.
Рассмотрим класс-реализацию "круг".
Этот класс содержит 3 реализации интерфейсов: класс-реализация "точка", класс-реализация "круг с операциями из поля круг", класс-реализация "круг с операциями, выводящие из поля круг".
Теперь рассмотрим наследование реализаций.
Наследовать мы можем, как класс-реализацию "круг" от класс-реализации "обобщенный эллипс", так и "обобщенный эллипс" от "круга".
-- наследование класса-реализации "обобщенный эллипс" от класса-реализации "круг" --
Здесь все просто. Обобщенный эллипс добавляет реализацию тех методов, которые не были реализованы в круге.
-- Наследование класса-реализации "круг" от класса-реализации "обобщенный эллипс". --
Вот мы и добрались до самого интересного.
В данном случае такое наследование тоже может быть. Класс-реализация "круг" после наследования от класса-реализации "обощенный эллипс" отключает ненужные ему реализации, оставляя только необходимые.
В данном случае, нет нарушений правил Лисковой, потому что внешние компоненты не имеют права ссылаться на класс-реализации, они имеют право только ссылаться на интерфейсы.
А для всех внешних компонентов, данный класс-реализация выглядит как правильная класс-реализация "круг"-а.
И для внешних компонентов такое наследование совершенно прозрачно.
Поговорим чуть-чуть о динамической типизации.
Как мы уже выше убедились, у нас есть интерфейсы, которые оставляют класс в том же поле, что и до операции, но есть также и операции, которые переводят класс из одного поля в другое.
Если программе достаточно интерфейсов первого рода, то можно обойтись статической типизацией, а также тем, что объект не меняет набор поддерживаемых интерфейсов по ходу работы.
Если же нам нужны и интерфейсы второго рода и не достаточно интерфейсов первого рода, то необходимы также и динамической типизации, а также изменение набора поддерживаемых интерфейсов у объекта по ходу работы.
Все текущие индустриальные языки (C++, Java, C#) довольствуются интерфейсами только первого рода, и не поддерживают, напрямую, интерфейсы второго рода.
G>В С++, например, от такого подхода нет никакого толку, благодаря наличию множественного наследования и темплейтов. Если использовать сильные стороны С++, то интерфейсы там встретишь нечасто, и уж точно они не являются основной парадигмой (если конечно не нашиковать приложение на мелкие COM объекты, чего делать низзя ни в коем случае без строгих на то показаний — уж больно велик семантический разрыв между COM и С++).
Я правильно, понял, что ты предлагаешь писать компоненты, которые ссылаются не на интерфейсы, а ссылаются сразу на классы-реализации?
А как быть тогда с инкапсуляцией и полиморфизмом?
Здравствуйте, Gaperton, Вы писали:
G>Началось все достаточно давно с появления концепции "Абстрактный Тип Данных" (то, что сейчас в ООП называется "инкапсуляцией"). Определение таково: тип данных, определяемый набором операций над ним. Концепция совершенно математическая, и вообще говоря самостоятельная. Известны языки, поддерживающие только инкапсуляцию, без наследования и полиморфизма. Уже в конце 70-х курс обучения программированию в Массачусетском технологическом институте был полностью основан на концепции ADT. Желающие могут ознакомиться с этим курсом и сейчас в замечательной книге "Использование абстракций и спецификаций при разработке программ" (Лисков Б., Гатэг Дж. 1989). Кстати, если кто не читал, рекомендую , получите массу удовольствия.
Все верно.
Единственное дополнение, которое я хотел сделать, состоит в следующем.
Читать книгу Лисков и Гатега долго, но есть книга попроще, да и к математике поближе.
Я имею в виду элементарный учебник Ахо, Ульмана и Хопкрофта "Структуры данных и алгоритмы", недавно (пере?)изданный на русском.
В первой же главе там излагается понятие АТД самым простым способом.
АТД именно и трактуется как математическая модель (наверное, точнее было бы сказать: "математическая структура").
Наверное, проще рекомендовать для прочтения эту книгу.
Но существует одно качество, которое нельзя купить, — это надежность. Цена надежности — погоня за крайней простотой. Это цена, которую очень богатому труднее всего заплатить.
Хоар
Re[13]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
G>>Наследование это не отношение "подмножество". Говоря об типах в ООП мы обязаны принять во внимание, что [абстрактный] тип данных (класс) определяется только операциями над ним. При правильном применении наследования в любых языках должен соблюдаться "принцип подстановки Лисков", который состоит в том, что "подкласс должен быть пригоден к использованию во всех контекстах, что и базовый класс", что означает, что операции в подклассе должны работать и сохранять семантику. G>>Другими словами, "сужающее" наследование — совершенно некорректная штука.
Не неправильно. DG>В каких отношениях тогда находится тип целое числа и тип натуральное число? DG>Между ними можно провести отношение наследования или это совсем разные типы, между которыми ничего общего?
DG>Т.е. получается, что мы должны отдельно завести тип целое число, определить для него операции, потом отдельно завести тип натуральное число опять определив все операции? DG>Потом также отдельно завести типы [0..10), [-4..1000], [0..255], [0..65535] и т.д.?
DG>ps DG>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
Лисков — это женщина. Ее зовут Бабара.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, beretta, Вы писали:
B>Здравствуйте, Кирилл Осенков, Вы писали:
B>Все хотел спросить. А можно ли список параметров метода считать одним объектом?
G>>Да, здесь при ООП дизайне в самом деле можно свернуть себе шею. Чтобы этого не произошло, в языке С++ нам помогут templates. И при этом типизация остается статической. Я думаю, ты это знаешь и без моих комментариев.
DG>Но остается другая проблема. C++-шаблоны совсем не поддерживают компонентность. Нельзя написать на C++ компонент, который наружу выставляет шаблонный метод или шаблонный объект.
Ну и что с того? Какая разница, как будет называться изолированный функционал, готовый к повторному использованию? Особенно, если отдельно я его не собираюсь продавать? Мне как инженеру все равно, будет это достойно называться "компонентом" или нет, лишь бы было удобно и решало задачу.
Например, наша реализация контрола Grid на темплейтах объективно на порядок удобнее в использовании в С++ чем всевозможные AciveX контролы аналогичного назначения (кода получается в разы меньше, при большей гибкости, простоте, и наглядности). Да, ее возможно нельзя назвать "компонентом" в твоих терминах, но это всех волнует в последнюю очередь.
Re[24]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, Gaperton, Вы писали:
G>>>Да, здесь при ООП дизайне в самом деле можно свернуть себе шею. Чтобы этого не произошло, в языке С++ нам помогут templates. И при этом типизация остается статической. Я думаю, ты это знаешь и без моих комментариев.
DG>>Но остается другая проблема. C++-шаблоны совсем не поддерживают компонентность. Нельзя написать на C++ компонент, который наружу выставляет шаблонный метод или шаблонный объект.
G>Ну и что с того? Какая разница, как будет называться изолированный функционал, готовый к повторному использованию? Особенно, если отдельно я его не собираюсь продавать? Мне как инженеру все равно, будет это достойно называться "компонентом" или нет, лишь бы было удобно и решало задачу.
Большие проекты разрабатывал?
Большие в том смысле, когда над одним проектом работает три-пять разных компаний (команд), при этом сотрудники каждой отдельной компании могут быть связаны подпиской о неразглашении, поэтому не имеют права отдавать другим свой код в виде исходников. Причем у каждой компании есть свои предпочтения по используемым библиотекам, языкам, компиляторам.
Но даже если можно передавать исходники — все равно это не спасает от проблем с разными библиотеками, версий библиотек, разными компиляторами и т.д.
И как вот такую сборную солянку собирать вместе без компонентов?
G>Например, наша реализация контрола Grid на темплейтах объективно на порядок удобнее в использовании в С++ чем всевозможные AciveX контролы аналогичного назначения (кода получается в разы меньше, при большей гибкости, простоте, и наглядности). Да, ее возможно нельзя назвать "компонентом" в твоих терминах, но это всех волнует в последнюю очередь.
А теперь представь, что таких контролов/классов тысячи, разрабатывают их разные команды, может быть даже на разных компиляторах, а тебе все эти контролы/классы надо собрать в виде цельного проекта.
Твои действия?
ps
Самое страшное — не дай бог у каких-нибудь исходников имена макросов совпадут между собой или совпадут с именами чего-нибудь полезного. Дальше только приходится долго прыгать с бубном, а не заниматься полезными делами.
Re[25]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, DarkGray, Вы писали:
DG>Здравствуйте, Gaperton, Вы писали:
G>>>>Да, здесь при ООП дизайне в самом деле можно свернуть себе шею. Чтобы этого не произошло, в языке С++ нам помогут templates. И при этом типизация остается статической. Я думаю, ты это знаешь и без моих комментариев.
DG>>>Но остается другая проблема. C++-шаблоны совсем не поддерживают компонентность. Нельзя написать на C++ компонент, который наружу выставляет шаблонный метод или шаблонный объект.
G>>Ну и что с того? Какая разница, как будет называться изолированный функционал, готовый к повторному использованию? Особенно, если отдельно я его не собираюсь продавать? Мне как инженеру все равно, будет это достойно называться "компонентом" или нет, лишь бы было удобно и решало задачу.
DG>Большие проекты разрабатывал?
Ну, давай посчитаем мой последний проект
Клиент:
ядро — 45 мегов исходников на С++.
Сервер данных:
ядро — 15 мегов исходников на С++.
Код, общий для клиента и сервера, не считая библиотек сторонних производителей — еще мегов 10.
Это все не считая внешних компонент, юнит-тестов, сервисных и конфигурационных приблуд, и прочего софта, имеющего прямое отношение к клиенту и серверу (с ними там гигабайты получаются. Правда.).
Возраст самых старых кусков кода до 10 лет.
Один продукт, 50 разработчиков, распределенная команда Россия — Штаты. Проект уже давно успешно продается, и активно развивается.
Моя группа отвечала за движок базы данных и встроенный язык, и наша работа часто серьезно пересекалась с деятельностью других групп.
А ты большие проекты разрабатывал?
DG>Большие в том смысле, когда над одним проектом работает три-пять разных компаний (команд), при этом сотрудники каждой отдельной компании могут быть связаны подпиской о неразглашении, поэтому не имеют права отдавать другим свой код в виде исходников. Причем у каждой компании есть свои предпочтения по используемым библиотекам, языкам, компиляторам. DG>Но даже если можно передавать исходники — все равно это не спасает от проблем с разными библиотеками, версий библиотек, разными компиляторами и т.д. DG>И как вот такую сборную солянку собирать вместе без компонентов?
Не надо меня подозревать в упертости, там где код надо завернуть в компоненты (как в твоем примере, например, надо обеспечить независимую разработку и обязательно скрыть детали реализации), я это сделаю. Но мелко нашинковывать свой плюсовый код на COM компоненты просто потому, что это модно и современно, не буду ни за какие коврижки — слишком большую цену за это придется заплатить. Я хочу это подчеркнуть потому, что твой предыдущий пример был о контейнерной библиотеке (STL прямо скажем не много теряет от "несовместимости с современными компонентными технологиями"), а мой — об элементе GUI для MS C++.
G>>Например, наша реализация контрола Grid на темплейтах объективно на порядок удобнее в использовании в С++ чем всевозможные AciveX контролы аналогичного назначения (кода получается в разы меньше, при большей гибкости, простоте, и наглядности). Да, ее возможно нельзя назвать "компонентом" в твоих терминах, но это всех волнует в последнюю очередь.
DG>А теперь представь, что таких контролов/классов тысячи, разрабатывают их разные команды, может быть даже на разных компиляторах, а тебе все эти контролы/классы надо собрать в виде цельного проекта. DG>Твои действия?
Реорганизовать разработку, конечно Как можно работать в таком бардаке? Если разные команды разработали тысячи классов, и проект совсем никак не хочет собираться, кому-то надо дать по башке, и неважно, что этот кто-то будет говорить про компоненты. Я бы начал с того, что уволил менеджера. А потом начал разбираться с team lead-ами.
Re[13]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>ps DG>Лисков, наверное, жил в идеальном мире, где есть только черное и белое, а мы живем в реальном мире, в котором поддержка только части исходного интерфейса — это норма.
Лисков = она (баба)
Удачи тебе, браток!
Re[14]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, Glоbus, Вы писали:
G>Лисков = она (баба)
Во-первых, не "баба", а женщина (дама, леди, как угодно, но не "баба").
Во-вторых, на этот факт уже два раза указывали в этом топике.
Re[26]: Задачка про Circle/Ellipse: "официальный" ответ.
DG>>Большие проекты разрабатывал? G>Ну, давай посчитаем мой последний проект G>Клиент: G>ядро — 45 мегов исходников на С++.
G>Сервер данных: G>ядро — 15 мегов исходников на С++.
G>Код, общий для клиента и сервера, не считая библиотек сторонних производителей — еще мегов 10.
G>Это все не считая внешних компонент, юнит-тестов, сервисных и конфигурационных приблуд, и прочего софта, имеющего прямое отношение к клиенту и серверу (с ними там гигабайты получаются. Правда.).
G>Возраст самых старых кусков кода до 10 лет.
Итого мегов 70, если я не ошибаюсь исходников недавно пролетавшей винды мегов 200. Вот бы мне такой фантазии, чтоб нагенерить хоть 10 мегов. Или у вас по принципу "Что вижу, то и пою"
Re[27]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, beretta, Вы писали:
B>Здравствуйте, Gaperton, Вы писали:
DG>>>Большие проекты разрабатывал? G>>Ну, давай посчитаем мой последний проект G>>Клиент: G>>ядро — 45 мегов исходников на С++.
G>>Сервер данных: G>>ядро — 15 мегов исходников на С++.
G>>Код, общий для клиента и сервера, не считая библиотек сторонних производителей — еще мегов 10.
G>>Это все не считая внешних компонент, юнит-тестов, сервисных и конфигурационных приблуд, и прочего софта, имеющего прямое отношение к клиенту и серверу (с ними там гигабайты получаются. Правда.).
G>>Возраст самых старых кусков кода до 10 лет.
B>Итого мегов 70, если я не ошибаюсь исходников недавно пролетавшей винды мегов 200. Вот бы мне такой фантазии, чтоб нагенерить хоть 10 мегов.
Фантазии. Не верят, гляди ка
B>Или у вас по принципу "Что вижу, то и пою"
Умные все стали, блин. Нет, у нас по принципу 50 человек на 10 лет. Знаешь, начиная с некоторого рамера системы, ее возраста, и размера команды, вы все будете мечтать совсем о другом — как бы не нагенерить лишних мегов. И что самое смешное, у вас это будет не получаться.
А вообще, вот тебе простой рассчет, чтобы снять вопросы про фантазию. Считай: один программер в серднем по индустрии дает 20K строк в год. 50 человек — 1М строк в год. За 10 лет — 10М строк. Пусть строка в среднем 10 байт. Получаем 100Мб исходников.
А насчет винды ты не прав. По интернету проходила статья про то, как билдится Win 2000. Она у них полдня компилируется на сетке из десятка машин. У нас, слава богу, пока справляется одна, только придется погулять несколько часов.
Если интересно посмотреть на это самому, могу организовать экскурсию к нам на работу, и показать. Я лично неплохо ориентируюсь в 18 мегах года, из которых написал сам не более 500 кил.
Re[27]: Задачка про Circle/Ellipse: "официальный" ответ.
Здравствуйте, beretta, Вы писали:
B>Здравствуйте, Gaperton, Вы писали:
DG>>>Большие проекты разрабатывал? G>>Ну, давай посчитаем мой последний проект G>>Клиент: G>>ядро — 45 мегов исходников на С++.
G>>Сервер данных: G>>ядро — 15 мегов исходников на С++.
G>>Код, общий для клиента и сервера, не считая библиотек сторонних производителей — еще мегов 10.
G>>Это все не считая внешних компонент, юнит-тестов, сервисных и конфигурационных приблуд, и прочего софта, имеющего прямое отношение к клиенту и серверу (с ними там гигабайты получаются. Правда.).
G>>Возраст самых старых кусков кода до 10 лет.
B>Итого мегов 70, если я не ошибаюсь исходников недавно пролетавшей винды мегов 200. Вот бы мне такой фантазии, чтоб нагенерить хоть 10 мегов. Или у вас по принципу "Что вижу, то и пою"
Фантазии не хватает Ничего страшного, я придумал выход. boost у тебя стоит? Посчитай его объем исходников. У тебя должно получится примерно следующее.
boost_1_30_0 34,5 Мb
из них
boost_1_30_0/boost 12,1 Мb
boost_1_30_0/libs 19,4 Mb
Так что если будет нужно — кровь износу — нагенерить мегов, и вдруг закончится фантазия — можно их понадергать из boost!
Здравствуйте, beretta, Вы писали:
B>Здравствуйте, Кирилл Осенков, Вы писали:
B>Все хотел спросить. А можно ли список параметров метода считать одним объектом?
Можно. Есть даже такой патерн Параметр. А в перле дефакто и вовсе все параметры передаются в списке, т.е. можно сказать, пакуются в объект Параметр.