Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Песочница > ООП: Квадрат и прямоугольник.


Автор: DeadSoul 19.5.2006, 00:13
Извините, более подходящего раздела не увидел.

Ответ на вопрос ЗНАЮ. Знающие ответ пусть помолчат, а новички попробуют на него ответить.

Дано: нужно реализовать два класса Square(квадрат) и Rectangle(прямоугольник). Понятно, что они очень похожи. Какой класс следует унаследовать от другого(или не нужно наследовать вообще smile  ) ? 

Автор: Void 19.5.2006, 00:15
DeadSoul, тут от одного флейма очухаться не успели, а ты новый затеваешь? smile
Ignat, убери тему от греха подальше! smile 

Автор: DeadSoul 19.5.2006, 00:20
Void, не понял. При чем тут флейм? К тематике сайта вопрос имеет совсем прямое отношение

И вопрос на самом деле очень хороший. 

Автор: Void 19.5.2006, 00:21
DeadSoul, я вот http://forum.vingrad.ru/index.php?showtopic=95670 тему имею в виду. Если здесь начнется продолжение, увольте. 

Автор: maxim1000 19.5.2006, 00:37
ответа тут нет
это полностью зависит от контекста задачи
если очень хочется наследовать и при этом получить как можно более общий ответ, то - наследовать от класса "фигура" smile

то, что квадрат является прямоугольником в геометрическом смысле, ещё не значит, что он будет являться ним в той модели, которая будет описана в программе
причина простая - в программе объекты могут меняться, в геометрии и математике вообще величины не меняются
с другой стороны, в большинстве популярных языков, мягко говоря, не принято, чтобы объект менял свой класс

вот и получается, что объект прямоугольник, скорее всего будет иметь методы SetWidth и SetHeight
если квадрат наследовать от прямоугольника, то наличие в нём этих методов будет некорректным (по крайней мере, мне так кажется) - функции, получающие на вход прямоугольник вправе ожидать, что они могут сделать соотношение сторон любым

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

возможно, если работать с константными объектами (как это происходит в математике), т.е. запретить им менять свои параметры, можно было бы наследовать квадрат от прямоугольника... впрочем, это - то, о чём я говорил - зависимость от контекста задачи...

Цитата(DeadSoul @  18.5.2006,  23:13 Найти цитируемый пост)
Извините, более подходящего раздела не увидел.

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

Добавлено @ 00:39 
например, Майерс (если не ошибаюсь) вообще не советует наследовать что-либо от конкретных классов
часто более удобно выделить то общее, что мы в них видим, в абстрактный класс и наследовать от него... 

Автор: Quadr0 19.5.2006, 01:27
...

Автор: chipset 19.5.2006, 01:40
А ещё можно сделать шаблон Quad и вводить зависимости между сторонами стратегиями. К примеру:
quad<figures::rectangle> rect;
quad<figures::square> sq;
quad<figures::rhombus> rh;

Где классы figures:: обозначают геометрические правила для сторон.
А ещё можно создать класс polygon и определять там количество вершин и опять-же, зависимости.
polygon<4, figures::square> sq;

Я даже могу предположить проверку совместимости кол-ва сторон с выбранной моделью, используя шаблоны можно такого понатворить!
И хотя это не ООП а АОП и в реальном проекте я вряд-ли такое буду практиковать, теоретически выглядит гламурно smile  

Автор: Exception 19.5.2006, 07:43
Либо вообще не определять квадрат (а зачем?), либо определить классы Квадрат и Прямоугольник производными от Четырёхугольника и предоставить в Квадрате метод ToRectangle(), либо попытаться реализовать вариант, предложенный Чипсетом. 

Автор: skyboy 19.5.2006, 11:00
Опять - двадцать пять?  smile Я так понимаю, в этих войнах погибнут только самые зелёные, а бывалые зачинщики только посмеиваться будут... 
В самом деле, у квадрата лучше реализовать свойство "размер", который будет хранить в единственном экземпляре расстояние между двумя связанными вершинами, а прямоугольник уже наследовать от параллелограмма... Впрочем, что общего у квадрата и прямоугольника? Прямой угол между сторонами каждого угла? Так изменение параметров отличается.. Зачем наследовать одно от другого? 
О! Квадрат - от ромба, а прямоугольник - от паралелограмма, все они - от четырёхугольника, который просто хранит положение вершин и производит масштабирование(без нарушения пропорций). 
Т.е.:


                                     /---------- ромб --------------- квадрат
--- четырёхугольник---|
                                     \______ параллелограмм ---------- прямоугольник

 

Автор: ivashkanet 19.5.2006, 11:15
skyboy, к вашему сведению Ромб --- это параллелограмм 

Автор: Exception 19.5.2006, 14:30
И что? Это не значит, что наследоваться имеет смысл. 

Автор: DeadSoul 19.5.2006, 23:53
maxim1000 ответил на вопрос правильно. Тут наследовать одщно от другого нельзя. Аналогично нельзя наследовать и все предложенные выше классы.

1. Наследовать прямоугольник от квадрата нельзя. Очевидно.

2. Почему нельзя наследовать квадрат от прямоугольника:
Код

Square* ptr=CreateObject();
ptr->SetHeigth( 10 );
ptr->SetWigth( 20 );
assert( ptr->GetHeight()==10 );

данный код корректен для прямоугольника, но некорректен для квадрата.

Наилучший выход - реализация квадрата через прямоугольник, используя делегирование 

Автор: Void 20.5.2006, 01:22
Цитата(DeadSoul @  20.5.2006,  01:53 Найти цитируемый пост)
Наилучший выход - реализация квадрата через прямоугольник, используя делегирование

Учитывая, что LSP нарушается в данном примере только при наличии изменяющих объект операций, нельзя ли вместо делегирования обойтись такой иерархией?

ImmutableRectangle
Rectangle : ImmutableRectangle
ImmutableSquare : ImmutableRectangle
Square : ImmutableSquare

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

Автор: maxim1000 20.5.2006, 02:26
Цитата(DeadSoul @  19.5.2006,  22:53 Найти цитируемый пост)
Почему нельзя наследовать квадрат от прямоугольника

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

Цитата(DeadSoul @  19.5.2006,  22:53 Найти цитируемый пост)
Наилучший выход - реализация квадрата через прямоугольник, используя делегирование

не в курсе, что такое делегирование... типа наследования  реализации (private в C++)?
тогда не всегда стоит - квадрату нужно меньше данных для описания, чем прямоугольнику, так что получится неоправданая трата времени
но "не стоит" здесь значит, что путь неоптимальный с точки зрения ресурсов, и при их нехватке лучше так не делать...

Цитата(Void @  20.5.2006,  00:22 Найти цитируемый пост)
ImmutableRectangle
Rectangle : ImmutableRectangle
ImmutableSquare : ImmutableRectangle
Square : ImmutableSquare

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

Автор: chipset 20.5.2006, 04:50
Честно говоря, я бы не наследовал ничего вообще. ООП это хорошо, но читабельность тоже рулит и если придется поступится ООПом то я положу его на алтарь читабельности без малейшего промедления. 

Автор: skyboy 20.5.2006, 10:47
Цитата

К Вашему сведению ромб - это параллелограм

ivashkanet, да ну?  smile Я и не знал... На самом деле, ситуация у них такая же, как и у квадрата с прямоугольником: паралеллограм в общем виде имеет два размера, по одному для каждой стороны, а для ромба нужна только одна длина. И хранить её тоже в единственном варианте, а не дублировать, как бы пришлоь делать после наследования от параллелограма.  smile 

Автор: DeadSoul 20.5.2006, 12:18
Цитата(maxim1000 @  20.5.2006,  02:26 Найти цитируемый пост)
не в курсе, что такое делегирование... типа наследования  реализации (private в C++)?

Код

class Square
{
  Rectangle rect_;
};
 

Автор: Exception 20.5.2006, 12:26
Как я уже сказал, либо вообще лучше не наследовать (предоставив метод ToRectangle() у квадрата), либо использовать стратегии, как говорил Чипсет, либо пользовать вариант Войда, который мне тоже нравится. 

Автор: Се ля ви 20.5.2006, 15:27
Квадрат - частный случай прямоугольника, прямоугольник, соответственно, более общий. Квадрат можно описать длиной одной его стороны, а прямоугольник - длинами двух его сторон, имеющих различную длину.

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

Квадрат
|   длина ребра
|
|- Прямоугольник
        длина ребра
        длина ребра2

Это сэкономит память, поскольку представлять квадрат как прямоугольник с одинаковыми длинами рёбер будет не рационально с точки зрения оптимизации памяти - налицо дублирование данных.

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

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


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

Можно задать в Прямоугольнике методы, которые возвращают оба ребра - первое и второе и в квадрате интерфейс будет тот же самый, но метод, возвращающий второе ребро будет перегружен и реально будет вызывать метод первый, и только первый метод будет возвращать длину первого ребра. Тогда получаем оптимальный компромиcс.

Единственное, что не очень красиво - это всплывающая проблема именования рёбер - их четыре, но реально отличаются из них в прямоугольнике - 2, а в квадрате - вообще только одно уникальное ребро в плане длины, так что если нам нужно установить длину ребра, то нам лучше реализовать методы "верниГоризонтальноеРебро" и "верниВертикальное".

P.S. Впрочем, вся эта модель заметно усложняется, если нам понадобиться создавать всё дерево наследования объектов Планиметрии, а не только эти два, хотя и можно при этом действовать похожим способом.  

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)