Pull to refresh
Comments 84
Под простотой же в данном случае следует понимать

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

Иначе слишком абстрактно, каждый будет иметь свое понимание терминов.
Почитайте «Рефакторинг» Фаулера и «Рефакторинг с использованием шаблонов» Кириевски, но даже примеры из этих двух книг не будут исчерпывающими. KISS — слишком общий и всеохватывающий принцип
Дьявол кроется в деталях. Если примеров так много что даже в двух книгах нет исчерпывающего описания, почему у Вас не получилось привести хотя бы 2-3 примера в статье? Поддерживаю в данном вопросе dkukushkin. Без примеров эта статья вода который и так понятен если не всем, то подавляющему большинству разработчиков (К слову сказать я тоже как и остальные читал эти книги и думаю что большинство разработчиков прочитали хотя Фаулера). Возьмите какой нибудь один но неоднозначный пример. Например было поставлено условие выполнить некоторое действие — на это написали несколько строк кода. Затем оказалось что на каком то шаге надо выполнить другое действие и дописали условие. Ну и затем требования разрастаются на такие десяток условий. И в какой то момент приходит понимание что проще завести State Machine или обработчики и.д. и т.п. что с одной стороны усложняет код а с другой стороны на самом деле упрощает понимание кода. В какой момент стоит провести такой рефакторинг что бы было упрощение? Вместо двух условий? А может трех? Собственно ценность любой статьи кроется в раскрытии подобных ньюансов. Без этого можно было бы сократить статью до одного предложения без потери ценности.
эта статья вода который и так понятен если не всем, то подавляющему большинству разработчиков

Думаю, если человек знает, что KISS — принцип проектирования, содержащий все остальные принципы проектирования, то ему не следует открывать статью с одноимённым названием. А если всё-таки открыл, то не следует жаловаться что не узнал ничего нового.

можно было бы сократить статью до одного предложения без потери ценности

А вот если человек НЕ знает, что KISS — принцип проектирования, содержащий все остальные принципы проектирования, то название статьи и содержание статьи дадут ему разное количество новой и полезной информации

почему у Вас не получилось привести хотя бы 2-3 примера в статье?

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

Первое что пришло в голову, но что можно встретить в реальных проектах. Есть структура, в том числе хранит строки заранее известной максимальной длинны. Можно использовать массив символов, а можно std::string:
struct S1
{
//прочие поля структуры...
char name[50];
};

struct S2
{
//прочие поля структуры...
std::string name;
};


Без дополнительной информации не очень ясно какой из вариантов соответствует KISS, а какой его нарушает.
К тому же два примера всё же в статье есть:
Если вы используете паттерн проектирования там, где нет проблемы, которую решает данный паттерн – то вы нарушаете KISS, внося ненужные усложнения в код. Если вы НЕ используете паттерн проектирования там, где есть проблема, соответствующая паттерну – то вы опять-таки нарушаете KISS, делая код сложнее, чем он мог бы быть.
Это если читатель знает про паттерны. А если нет? Необходимость искать что-то в процессе чтения статьи не доставляет приятных эмоций. Статья хороша для начинающих, так как принцип KISS на слуху и их статья может заинтересовать. А парочка примеров, может даже на разных языках, добавят красок статье. Она станет более реальной более практичной более близкой читателю.
Думаю, статья была бы лучше, если бы вы написали парочку хороших примеров, я бы добавил их в статью и написал кого благодарить за примеры. Возьмётесь? Примеры можете оставлять прямо в комментариях или отправить личным сообщением.
Пример илюстрирующий, что простой код и код требующий минимального количества
знаний не одно и то же. Разработчик должен знать и использовать все
возможности языка.

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

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

void DoHatchArea(Rectangle&, Rectangle&);
void DoHatchArea(Rectangle&, Ellipse&);
void DoHatchArea(Rectangle&, Poly&);
void DoHatchArea(Ellipse&, Poly&);
void DoHatchArea(Ellipse&, Ellipse&);
void DoHatchArea(Poly&, Poly&);


Отлично, алгоритмы штриховки есть, осталось вызвать нужный для каждой пары фигур.
В C++ есть dynamic_cast, который позволяет привести указатель базовый класс к
дочернему, если он указывает на дочерний класс. Простая реализация «в лоб» будет
выглядеть как-то так:

void DoubleDispatch(Shape& lhs, Shape& rhs)
{
    if(Rectangle* p1 = dynamic_cast<Rectangle*>(&lhs))
    {
        if(Rectangle* p2 = dynamic_cast<Rectangle*>(&rhs))
            DoHatchArea(*p1, *p2);
        else if(Ellipse* p2 = dynamic_cast<Ellipse*>(&rhs))
            DoHatchArea(*p1, *p2);
        else if(Poly* p2 = dynamic_cast<Poly*>(&rhs))
            DoHatchArea(*p1, *p2);
        else
            Error("Неопределенное пересечение");
    }
    else if(Ellipse* p1 = dynamic_cast<Ellipse*>(&lhs))
    {
        if(Rectangle* p2 = dynamic_cast<Rectangle*>(&rhs))
            DoHatchArea(*p2, *p1);
        else if(Ellipse* p2 = dynamic_cast<Ellipse*>(&rhs))
            DoHatchArea(*p1, *p2);
        else if(Poly* p2 = dynamic_cast<Poly*>(&rhs))
            DoHatchArea(*p1, *p2);
        else
            Error("Неопределенное пересечение");
    }
    else if(Poly* p1 = dynamic_cast<Poly*>(&lhs))
    {
        if(Rectangle* p2 = dynamic_cast<Rectangle*>(&rhs))
            DoHatchArea(*p2, *p1);
        else if(Ellipse* p2 = dynamic_cast<Ellipse*>(&rhs))
            DoHatchArea(*p2, *p1);
        else if(Poly* p2 = dynamic_cast<Poly*>(&rhs))
            DoHatchArea(*p1, *p2);
        else
            Error("Неопределенное пересечение");
    }
    else
    {
        Error("Неопределенное пересечение");
    }
}


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

Что же делать? На помощь приходят шаблоны. Реализуем класс, который будет
содержать алгоритмы штриховки и обработку ошибки, если пересечение
неопределенно:
class HatchingExecutor
{
public:
    //Разные алгоритмы штризовки области пересечения
    void DoHatchArea(Rectangle&, Rectangle&);
    void DoHatchArea(Rectangle&, Ellipse&);
    void DoHatchArea(Rectangle&, Poly&);
    void DoHatchArea(Ellipse&, Poly&);
    void DoHatchArea(Ellipse&, Ellipse&);
    void DoHatchArea(Poly&, Poly&);

    //Функция для обработки ошибок
    void OnError(Shape&, Shape&);
};


И теперь перепишем поиск:
template<
    class Executor,
    class BaseLhs,
    class TypesLhs,
    class BaseRhs = BaseLhs,
    class TypesRhs = TypesLhs,
    typename ResulType = void
>
class StaticDispatcher
{
    typedef typename TypesLhs::Head Head;
    typedef typename TypesLhs::Tail Tail;
public:
    static ResultType Go(BaseLhs& lhs, BaseRhs& rhs, Executor& exec)
    {
        if(Head* p1 = dynamic_cast<Head*>(&lhs))
        {
            return StaticDispatcher<
                Executor, 
                BaseLhs, NullType, 
                BaseRhs, TypesRhs>::DispatchRhs(*p1, rhs, exec);
        }
        else
        {
            return StaticDispatcher<
                Executor,
                BaseLhs, Tail,
                BaseRhs, TypesRhs>::Go(lhs, rhs, exec);
        }
    }

    template<class SomeLhs>
    static ResultType DispatchRhs(SomeLhs& lhs, BaseRhs& lhs, Executor& exec)
    {
        typedef typename TypesRhs::Head Head;
        typedef typename TypesRhs::Tail Tail;

        if(Head* p2 = dynamic_cast<Head*>(&rhs))
        {
            return exec.Fire(lhs, *p2);
        }
        else
        {
            return StaticDispatcher<
                Executor,
                SomeLhs, NullType,
                BaseRhs, Tail>::DispatchRhs(lhs, rhs, exec);
        }
    }
}

template<
    class Executor,
    class BaseLhs,
    class BaseRhs,
    class TypesRhs,
    typename ResultType
>
class StaticDispatcher<
    Executor,
    BaseLhs, NullType,
    BaseRhs, TypesRhs,
    ResulType>
{
    static void Go(BaseLhs& lhs, BaseRhs& rhs, Executor& exec)
    {
        exec.OnError(lhs, rhs);
    }
}

template<
    class Executor,
    class BaseLhs,
    class TypesLhs
    class BaseRhs,
    class TypesRhs,
    typename ResultType
>
class StaticDispatcher<
    Executor,
    BaseLhs, TypesLhs,
    BaseRhs, NullType,
    ResulType>
{
    static void DispatchRhs(BaseLhs& lhs, BaseRhs& rhs, Executor& exec)
    {
        exec.OnError(lhs, rhs);
    }
}



А тепрь как это все использовать:
typedef StaticDispatcher<
    HatchingExecutor, Shape,
    TYPELIST_3(Rectangle, Ellipse, Poly)> Dispatcher;
Shape* p1 = ...;
Shape* p2 = ...;
HatchingExecutor exec;
Dispatcher::Go(*p1, *p2, exec);


Теперь в при добавлении новой фигуры достаточно заменить
TYPELIST_3(Rectangle, Ellipse, Poly) на TYPELIST_4(RoundedRectangle, Rectangle, Ellipse, Poly) и добавить методы в HatchingExecutor.

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


Пример взят из книги Андрея Александреску «Современное проектирование на C++».
Этот ваш так называемый «простой» код с StaticDispatcher-ом просто ужасен. Чтобы рассчитать пересечение фигур нужно знать о классах HatchingExecutor и StaticDispatcher. При добавлении новой фигуры нужно модифицировать строку typedef StaticDispatcher<...>, а без знания StaticDispatcher-а до этого не догадаться. Проблема усугубляется тем, что в устройстве StaticDispatcher-а очень сложно разобраться, в том числе благодаря тому что имена переменных (p1, p2, lhs) не информативны. Простым это решение точно не назовёшь. Как бы я сделал: добавил бы в интерфейс Фигура виртуальную функцию РассчитатьПересечениеС(Фигруа ДругаяФигура). Пусть есть три фигуры: треугольник, квадрат и круг. В конкретную фигуру треугольник поместил бы код умеющий рассчитывать пересечение с квадратом и кругом, а если это не квадрат и круг то просим неизвестную фигуру саму рассчитать пересечение. Квадрат не умеет рассчитывать пересечение с треугольником, считает треугольник неизвестной фигурой и сразу просит у треугольника рассчитать пересечение. Добавил бы простенькую защиту от зацикливания изменив параметры в функции: РассчитатьПересечениеС(Фигруа ДругаяФигура, Число НомерВызова), если НомерВызова>2, кидаем исключение. Добавил бы юнит-тест проверяющий что все фигуры умеют пересекаться; в языках, поддерживающих рефлексию, в тесте бы искал всех наследников Фигуры через рефлексию, чтобы тест не нужно было менять при добавлении новой фигуры.
Преимущество решения: при добавлении новой фигуры не нужно менять ранее написанные классы, чтобы найти код пересечения треугольника квадрата нужно посмотреть максимум 2 класса: квадрат и треугольник, а не устраивать расследование в стиле Шерлока Холмса. Этот код простой и интуитивно понятный, нет левых классов ДелательПересечений и СтатическийДелец.
РассчитатьПересечениеС(Фигруа ДругаяФигура, Число НомерВызова), если НомерВызова>2, кидаем исключение

… и все, «простота» потеряна. Если у вас для каждого вызова ожидается проверка, без которой нельзя обойтись — нафиг-нафиг такое счастье.
Можно эту ситуацию решить в соответствии с возможностями языка. Проверку можно вынести в базовый класс — в Фигуру. Можно сделать проверку аспектом, и аспект джойнить на всех наследников фигуры. Если в языке для наследников наследуется и контракт, можно указать условие в контракте базового класса. Лично мне больше нравится вариант с вынесением проверки в базовый класс. Для того чтобы вызвать функцию РассчитатьПересечениеС никаких проверок делать не нужно. И ещё, НомерВызова лучше сделать параметром со значением по-умолчанию.
Сравнение числа с двойкой в классе Фигура — это не просто? А что в этом сложного?
Это лишняя операция, которая никак не обусловлена собственно задачей.

Собственно, вот вам и демонстрация того, что правило «будь проще» слишком неоднозначно. У всех разные критерии «простоты».
Это лишняя операция, которая никак не обусловлена собственно задачей.

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

У всех разные критерии «простоты».

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

Stack overflow больше не приводит к падению?

правила расчёта пересечений для всех фигур упал, а не зациклился

Вот и у вас появились «правила расчета пересечений». Осталось вынести эти правила за пределы фигур (поскольку ни одна фигура, на самом деле, сама не знает, как считать пересечения с другой) — и будет хорошо.

Я в статье дал определение простоты, взятое из словаря Ушакова, даже ссылку дал.

Вот в вашей реализации есть «искусственность», поэтому определение Ушакова не подходит. Ну и вообще, определения из толкового словаря плохо подходят в качестве метрик при разработке ПО.
Stack overflow больше не приводит к падению?

Вы умеете предвидеть будущее и гарантировать что код фигур не изменится так, что исключение StackOverflow будет кидаться в тесте по другим причинам, помимо не написания алгоритма расчёта пересечения для некоторой пары фигур? И сколько у вас в проекте по времени тесты выполняются?

вынести эти правила за пределы фигур

Есть такой принцип, ООП называется, который говорит что неплохо бы данные и код который с ними работает в одной сущности хранить. Например есть класс машина, у машины есть функция ехать. Так вот, машина не сама едет, её законы физики заставляют ехать. Но создавать класс ЗаконыФизики только ради того чтобы координаты машины изменить — глупо. Точно также создавать класс Пространство только ради того чтобы пересечение двух фигур посчитать — глупо. Для этого веские причины нужны, не стоит плодить лишние сущности. Вот если алгоритмы расчёта пересечений очень сложные, или если фигура очень много чего умеет помимо как пересекаться — тогда пожалуйста. Но даже тогда неплохо сделать чтобы фигура делегировала обращение расчёта пересечения пространству.Чаще удобнее машину просить поехать, а не законы физики или пространство.
Вы умеете предвидеть будущее и гарантировать что код фигур не изменится так, что исключение StackOverflow будет кидаться в тесте по другим причинам, помимо не написания алгоритма расчёта пересечения для некоторой пары фигур?

Не умею. Но мне не критично, почему он кинулся, мне критично, что тест упал.

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

Во-первых, ООП — не единственная парадигма программирования.
А во-вторых, ваша задача изначально нарушает этот подход: ваше действие производится над двумя объектами, поэтому данные как минимум одного из них будут обрабатываться «снаружи». В вашем подходе этот объект выбирается случайным образом (нет никакой системы в том, какой объект получает данные какого). В подходе со внешним расчетчиком, по крайней мере, все понятно.
не критично, почему он кинулся, мне критично, что тест упал

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

Во-первых, ООП — не единственная парадигма программирования.

И что с того??? Напомню, вы говорили терминами ООП: «Осталось вынести эти правила за пределы фигур».

А во-вторых, ваша задача изначально нарушает этот (ООП) подход: ваше действие производится над двумя объектами

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

Критично, что у вас детали реализации (защита от бесконечной рекурсии) просочились в интерфейс.

Суть в том, что данные и код их использующий находятся в одной сущности. В примере квадрат не умеет сам рассчитывать пересечение с треугольником, и передаёт данные о себе треугольнику — квадрат использует свои данные для нахождения ответа.

И какие же данные передает о себе квадрат?
Критично, что у вас детали реализации (защита от бесконечной рекурсии) просочились в интерфейс.


Согласен, это проблема. Один из вариантов решения — не передавать значение через аргумент функции, а передавать через protected переменную. Однако вариант с тем чтобы просто ловить исключение по переполнению стека проще. Как пришёл домой, проверил — стек довольно быстро переполняется :) Тут вы были правы. Спасибо что помогли сделать простое решение ещё проще.

И какие же данные передает о себе квадрат?

Самого себя
Один из вариантов решения — не передавать значение через аргумент функции, а передавать через protected переменную

Это тоже грязное решение. Просто в этом случае вы засоряете не public-интерфейс, а protected.

Самого себя

Мы же оба понимаем, что для вычисления пересечения двух фигур необходимо знать о них немножко больше, чем «сама фигура». Например, для квадрата нам понадобится как минимум одна пара координат (+угол и размер или +вторая пара координат). Эти данные вторая фигура будет должна получить из квадрата тем или иным способом. Вот ваши данные, нужные для расчета, и утекли наружу объекта.
Вот ваши данные, нужные для расчета, и утекли наружу объекта.

Повторюсь, квадрат использовал свои данные для получения ответа. Не важно, что в момент поиска ответа он использовал какие то другие классы, помимо квадрата. Это не противоречит ООП. Если бы противоречило, то все программы состояли из одного единственного класса.
Допустим есть класс человек, однажды у человека спросили: «мальчик, сколько тебе лет?». Тут то данные и утекли наружу класса. С тех пор мальчик сам решает, по детскому ему билету ехать или по взрослому. Кондуктора вскоре упразднили, потому что на вопрос «кто тут ещё не заплатил», все молчали, дабы не передавать никаких данных об объекте наружу объекта. :)
Неа.

Вот пойдем на примере. Вот квадрат получает в методе Intersect фигуру. Предположим, что он не знает, как с ней считать пересечение. Что он делает? Правильно передает туда себя. Это важно — не свои данные (потому что у каждой фигуры «свои данные» отличаются, и вы не можете зафиксировать их в интерфейсе), а именно себя. this. Что происходит дальше? Фигура, куда он себя передал — предположим, что она знает, как считать пересечения с квадратом — берет из квадрата данные о нем (координаты), и решает задачу.

А теперь внимание, вопрос: чем с точки зрения ООП это отличается от «правило расчета перечислений получило квадрат и круг, из каждого получило данные, построило пересечение»? Так вот — ничем. Вообще.

Более того, даже любимый вами интерфейс можно сохранить: любая фигура, получив в метод Intersect другую фигуру, возвращает new Intersection<TThis, TOther>(this, other).Intersect().

ООП? ООП. Работает? Работает. Юнит-тестируемо? Элементарно.

А вот теперь, пожалуйста, назовите объективный критерий, по которому ваше решение проще этого или это решение проще вашего.
Тут уже пошла тавтология. Я по этому поводу уже высказывался:
создавать класс Пространство только ради того чтобы пересечение двух фигур посчитать — глупо. Для этого веские причины нужны, не стоит плодить лишние сущности. Вот если алгоритмы расчёта пересечений очень сложные, или если фигура очень много чего умеет помимо как пересекаться — тогда пожалуйста. Но даже тогда неплохо сделать чтобы фигура делегировала обращение расчёта пересечения пространству.Чаще удобнее машину просить поехать, а не законы физики или пространство.

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

вопрос: чем с точки зрения ООП это отличается от «правило расчета перечислений получило квадрат и круг, из каждого получило данные, построило пересечение»

В таком случае это процедурный подход, а не ООП. Здесь правило расчета перечислений — завистливая функция (Фаулер, «Рефакторинг»), это smell. Если функция использует больше данные постороннего класса, чем своего, то её нужно перенести в этот посторонний класс. Если использует данные нескольких классов — то в тот класс, из которого используется больше всего данных, или, как вариант, в тот класс, данные которого важнее в данном контексте использования.
А вот теперь, пожалуйста, назовите объективный критерий, по которому ваше решение проще этого или это решение проще вашего.

Довольно занимательная статья, которая поможет понять почему не стоит плодить сущности без необходимости.
Без необходимости — не стоит. Вопрос в том, как объективно определить эту необходимость.
«как объективно определить эту необходимость» — как с заказчиком договорились, так и объективная необходимость. Why not?
Because заказчику нечего делать в коде программы. В комментарии выше под сущностью понимается деталь реализации, а не бизнес-сущность.
Здесь правило расчета перечислений — завистливая функция (Фаулер, «Рефакторинг»), это smell.

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

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

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

Или вот: у вас была логика расчета для треугольников (три параметра на объект). Все круто. А теперь вы добавили круги (два параметра на объект). Будете дописывать логику в треугольники (где она должна быть по вашим правилам), или оставите в кругах (чтобы не нарушать OCP)?

Ну и да, объективного критерия простоты все еще нет.

PS Еще полезно подумать о том, как вы будете реализовывать расчет пересечений для n фигур.
А теперь вы добавили круги (два параметра на объект). Будете дописывать логику в треугольники

как я говорил ранее (я снова повторяюсь, не пора ли плавно заканчивать спор?):
Преимущество решения: при добавлении новой фигуры не нужно менять ранее написанные классы


Smell — это не обязательно сигнал к исправлению

Завистливая функция — это сигнал к исправлению, давайте обойдёмся без софистики

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

Легко. Поскольку пересечение — тоже фигура (скорее всего какой нибудь скруглённый многоугольник), то пересечение 3х фигур находится так: фигура1.Пересечение(фигура2.Пересечение(фигура3)).

В каком из них должна быть функция расчета

Экскаватор копает землю. Вопрос: где будете искать код копки земли? Экскаватор копает землю или земля копается экскаватором? Если ответ не напрашивается исходя из предметной области, то один из хороших подходов — когда код найдётся и там и там. В экскаваторе непосредственно и в земле как Visitor: Земля.Копаться(IКопатель копатель){копатель.Копать(Я);}. Если код копания находится не в земле и не в экскаваторе, а «на другой планете» — это плохой подход, потому что код очень сложно найти. Тут всё аналогично, только вместо экскаватора и земли два разных типа фигур.
Завистливая функция — это сигнал к исправлению, давайте обойдёмся без софистики

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

Поскольку пересечение — тоже фигура (скорее всего какой нибудь скруглённый многоугольник), то пересечение 3х фигур находится так: фигура1.Пересечение(фигура2.Пересечение(фигура3)).

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

один из хороших подходов — когда код найдётся и там и там.

Это плохой подход, потому что он нарушает SRP.

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

В данном случае функция была завистливой, давайте обойдёмся без софистики

Это плохой подход, потому что он нарушает SRP.

По вашему, SRP — это в «каждом классе максимум одна публичная функция»? Кстати, в земле нет непосредственно самого кода копания. Предлагаете всё-таки запихать код «на другую планету»?
В данном случае функция была завистливой, давайте обойдёмся без софистики

Вы не можете это формально доказать.

По вашему, SRP — это в «каждом классе максимум одна публичная функция»? Кстати, в земле нет непосредственно самого кода копания.

Нет, SRP — это «у каждого элемента должно быть не более одной причины для изменения».
Ну так, класс земля не изменится, если экскаватор будет её по другому копать.
Зато если функция «копать» внезапно начнет получать два параметра вместо одного (например, объем копаемого) — будет изменение.
В таком случае будет понятно, что код копания нужно искать в экскаваторе, и Visitor в земле уже будет не нужен
То есть сначала был в двух местах, а потом добавился параметр — и стало в одном месте.

Кул.
объективного критерия простоты все еще нет

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

Во-первых, нет прямой связи между «завистливыми» функциями и простотой.
А во-вторых, вы можете автоматизировать поиск кандидатов на «завистливые» функции, но не финальное их определение.
вы можете автоматизировать поиск кандидатов на «завистливые» функции, но не финальное их определение


Смогу при таком условии (я снова повторяюсь, не пора ли плавно заканчивать спор?):
введения дополнительной не содержащейся в коде информации о предметной области

Эта дополнительная информация будет о важности определённых данных в определённых контекстах использования. Это довольно сложно и на практике вряд ли кто-то будет так делать, но теоретически — вполне осуществимо.
Смогу при таком условии (я снова повторяюсь, не пора ли плавно заканчивать спор?):
введения дополнительной не содержащейся в коде информации о предметной области

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

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

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

Ваше решение проще реализовать, в нем проще разобраться, но поддерживать его сложнее.

В варианте из Александреску — сложнее разобраться, но дальнейшее добавление фигур сводится к описанию класса новой фигуры и добавлению методов расчета пересечений, список типов можно передавать не отдельно, а вместе с классом ДелательПересечений, что-то типа:
class ДелательПересечений
{
public:
    typedef TYPELIST_3<Прямоугольник, Элипс, Многоугольник> ПоддерживаемыеФигуры.
}


И опять же можно написать юнит тест.

в том числе благодаря тому что имена переменных (p1, p2, lhs) не информативны

Во-первых их можно переименовать (выбор названия для переменных это вообще отдельная тема), а во-вторых в чем не информативность — функция поиска пересечения двух фигур принимает параметры lhs, rhs — левый фигура, правая фигура. Можно просто shape1 и shape2. При таком использовании и учитывая что перемененных мало (lhs, rhs, p1, p2) понять их значение не сложно.

что для всех фигур предусмотрели расчет пресечения… поддерживать его сложнее

Почитайте повнимательнее, там юнит-тест это проверяет. К тому же не уверен что ваше решение это умеет проверять.

все это разбросанно по разным файлам и со временем может превратиться в кошмар

Код пересечения конкретной пары фигур легко находится. Почти во всех современных IDE есть горячие клавиши для быстрого перехода к нужному классу. Вам принципиально хранить все функции расчёта пересечений в одном файле? А если фигур миллион?

а это опять же проверки через динамическое приведение типов

Во-первых, что в конкретном данном случае вам в этом не нравится? Во-вторых, а в вашем примере динамических приведений как будто нету? Head* p2 = dynamic_cast<Head*>(&rhs) — а это тогда что?
там юнит-тест это проверяет

А в чем тогда смысл следовать KISS, если все проверки можно свалить на юнит тесты? Запуск юнит теста, только для проверки, что все варианты предусмотрели — дольше, чем просто пробежаться взглядом по десятку функций. Юнит тест интереснее использовать, для проверки правильности расчета пересечений, что без вникания в алгоритм сделать сложно, и все равно можно упустить какие-нибудь пограничные варианты.

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

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

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

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

а это опять же проверки через динамическое приведение типов

Я как раз и имел ввиду, что от них никуда не деться, а они и есть самое запутанное и опасное место, в котором можно допустить ошибки и внесение изменений в него хотелось бы минимизировать. В вашем варианте каждый новый класс будет сложнее предыдущих по количеству таких проверок.
А в чем тогда смысл следовать KISS, если все проверки можно свалить на юнит тесты?

Наличие юнит-тестов не гарантируют что код простой и понятный (вы сами то поняли что спросили?).

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

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

Если навигация по коду не удобна без средств предоставляемых IDE, стоит задуматься, а все ли хорошо сделано.

Можно и без помощи IDE найти два файлика. В любом случае просмотреть 2 маленьких файлика проще, чем один гигантский в 100 раз больше обоих. Ну если прям очень надо, можно сложить все алгоритмы нахождения пересечения в один класс, и вызывать его уже из фигур.

если код тяжело читается в простом текстовом редакторе без подсветки синтаксиса, то это плохой стиль кодирования

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

а они и есть самое запутанное и опасное место, в котором можно допустить ошибки

вопрос был что в конкретно данном случае вам не нравится, а не почему в целом вам не нравятся проверки типов

Опять же расчет пресечения фигур не является неотемлемым свойством фигуры.

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


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

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

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

Можно и без помощи IDE найти два файлика. В любом случае просмотреть 2 маленьких файлика проще, чем один гигантский в 100 раз больше обоих.

Откуда в 100 раз больше обоих — посмотреть заголовочный файл, в котором есть только объявление методов этого достаточно. На 10 фигурах это будет 55 методов, в последней фигуре будет 10 методов проверки пересечений + собственные методы (перегруженные из базового класса, сеттеры/геттеры...). Так что не в 100 раз больше. Но это вопрос вкуса, и то какие манипуляции в целом производятся с фигурами.

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

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

вопрос был что в конкретно данном случае вам не нравится, а не почему в целом вам не нравятся проверки типов

Не нравится, что нужно вручную писать все проверки на то какой тип был передан в каждом методе поиска пересечений. Получается первоначальный вариант, но разнесенный по разным файлам. В варианте с шаблонами и списком типов это будет выполнено автоматически. Не надо создавать новых условий для проверки, только в список типов добавить еще один тип.
Давайте представим проект, в котором использовалось предложенное вами решение. Приходит на проект новый человек (ну или бывалый разработчик не открывал конкретно этот кусок кода с фигурами около полугода). Ему говорят: добавь-ка новую фигуру. Открывает он фигуру, и не видит никаких намёков на вычисление пересечения. Пересечение он не реализует. Юнит-теста не было написано, потому что, как вы говорите
можно взглядом оценить, что ничего не забыли
и
если есть необходимость писать юнит тест для проверки, что добавили все методы, то это уже не очень простой код
Так что через некоторое время выясняется что приложение иногда падает с ошибками при поиске пересечений… После этого программисту приходится изучать класс СтатическийДелец, который ничего общего не имеет ни с фигурами, ни с пересечениями, и задача решается.

Вариант развития событий 2. Понадобилось программисту вычислить пересечение 2х фигур. Зашёл он в класс фигуры и ничего не увидел. После этого он стал смотреть весь код, в которых используются фигуры. Пролистав много кода, нашёл он класс HatchingExecutor (Делатель штриховки). Однако в этом классе есть только алгоритмы пересечений для конкретных фигур, а не для абстрактной фигуры, поэтому программист написал свой switch-case для поиска пересечений 2х абстрактных фигур. Однако кто-то это заметил и сказал что вроде где-то видел механизм поиска пересечений. После этого он стал искать в проекте все места использования HatchingExecutor, благо было оно всего одно. Изучив StaticDispatcher, программист смог таки вызвать функцию поиска пересечений двух фигур. Много кода пролистано, несколько часов потрачено, нужная функция вызвана. Хэппи энд. Ну или может быть такой вариант: толстенная инструкция по особенностям проекта пролистана, 20 минут потрачено, нужная функция вызвана (но для такого финала нужно создать документацию и постоянно тратить силы на её поддержание).

Теперь рассмотрим ситуацию, если Фигура реализована так, как я предлагал:
Нужно добавить фигуру. Смотрим треугольник, делаем по аналогии, всё работает.
Нужно вызвать пересечение 2х фигур. Смотрим любую конкретную фигуру или абстрактную фигуру, вызываем нужную функцию, всё работает.
Так что через некоторое время выясняется что приложение иногда падает с ошибками при поиске пересечений…

Согласен, был не прав, юнит тест все таки нужен.

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

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

Вариант развития событий 2. Понадобилось программисту вычислить пересечение 2х фигур. Зашёл он в класс фигуры и ничего не увидел.

Можно сделать объявление функции поиска пересечений рядом с базовым классом для фигур принимающую в качестве параметров две ссылки(или указателя) на базовый класс. Тело функции будет содержать вызов метода StaticDispatcher c HatchingExecutor. И достаточно не толстенной документации а строчки с комментарием. И не придется ничего искать. А если функций для работы с фигурами несколько, то их объявление можно собрать в одном месте.

Смотрим треугольник, делаем по аналогии

И юнит тест падает, потому что в фигура треугольник была добавлена третьей по счету и в ней были реализованы только пересечения с Прямоугольником и Элипсом. А кроме них оказалось, что есть и другие фигуры. Дальше пограммист должен посмотреть на все оставшиеся классы (ну не на все, еще на пары другой было бы достаточно), что бы понять, что для каждой новой фигуры необходимо реализовать ее пересечение со всеми остальными. и тратит кучу времени на написание однотипных if-else. Чтобы таких изысканий не было стоит сразу, в базовом классе, написать комментарий к виртуальному методу поиска пересечения/штриховки.

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

Программист обладает как минимум зачатками логики, и, зная что 1)есть треугольник, круг и трапеция, и 2)написав алгоритм расчёта пересечения треугольника и круга, догадается что расчёт пересечения треугольника и трапеции с неба не упадёт. А если не догадается, то посмотрит функцию вычисления пересечений и увидит что там помимо круга есть ещё и обработка остальных фигур (НеизвестнаяФигура.РассчитатьПересенияС(Я)). По-моему, проблема надуманная.

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

Расположение кода в файлах служит второстепенным средством структурирование кода. Первостепенным (и более важным) является само устройство кода, т.е. расположение алгоритмов в методах и расположение данных и методов в классах.

в моем случае после этого нужно только добавить в список новый тип, а в вашем написать длинный блок if-else

Принцип KISS как раз таки и говорит, что «не нужно стрелять по воробьям из пушки». StaticDispatcher довольно сложен: он должен учитывать, что если трапеция является наследником многоугольника, то вызовется код расчёта именно трапеции (хотя фигура и является многоугольником), также он должен уметь переставлять местами аргументы функции (StaticDispatcher из книги это умеет). В конце концов, StaticDispatcher настолько сложен, что вы неправильно скопировали его куски из книги (он не компилируется по многим причинам), и никто этого не заметил. Кроме того, StaticDispatcher — протекающая абстракция, то есть чтобы его использовать, нужно знать его внутреннее устройство: при добавлении новой фигуры нужно изменять массив типов, так что игнорировать его сложность не удастся. Можно конечно написать инструкцию, но принципы проектирования направлены на создание понятного кода, а не кода, к которому требуются пояснения. В картинной галерее художник ведь не стоит возле картины с указкой и не говорит: «эта бесформенная клякса — собака, а эта непонятная загагулина — человек» (речь не идёт о всякого роде сюрреалистических картинах, где зритель получает удовольствие от осознания разгаданной загадки). Увидите вы кучу клякс, пожмёте плечами и пройдёте мимо. Только в случае с программой пройти мимо не получится… Сложность StaticDispatcher (пушки) может быть оправдана, только если блоков if-else будет очень много (гигантский воробей). Не думаю что их будет настолько много. Ну а если со временем вдруг количество фигур и увеличится, тогда можно будет и побороться с этой проблемой. Но в этом далеком будущем программа уже изменится, и лучшее решение в ней может отличаться от лучшего решения в настоящий момент. Кроме того, это будущее может так и не наступить.
Похоже, каждый из нас останется при своем мнении. У меня не хватает аргументов убедить Вас, что StaticDispatcher это не столь сложно как кажется, и в то же время меня не убеждают Ваши аргументы, что лучше оставить все как есть.

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

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

В любом случае, спасибо за дискуссию, для себя я почерпнул некоторые интересные мысли.
Добавил в статью пример с кодом, который, как мне кажется, устроит нас обоих. Пример написан на C#
Согласен, пример показывает ту же идею — как можно избавиться от кучи if — else, которая появитсья при реализации решения «в лоб» используя возможности языка. А остальное уже особенности конкретного языка и предпочтений разработчика.
«простой код и код требующий минимального количества знаний не одно и то же. Разработчик должен знать и использовать все возможности языка»

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

Если мне как заказчику ПОДХОДИТ (здесь и сейчас) РЕЗУЛЬТАТ работы разработчика, то мне в среднем чуть более чем наплевать, какие возможности каких языков он использовал или не использовал.
Заказчику все равно и то, как система спроектирована, и какие паттерны применял или не применял разработчик, и каким методологиям он следовал. Заказчику важно получить продукт в срок, за оговоренные деньги и чтобы этот продукт работал так, как он себе это представляет. Программа может «глючить», могут быть «утечки» памяти, если программа не предназначена работать без перезапуска длительное время, главное, чтобы это не было заметно заказчику. Но разговор не о взаимоотношениях заказчик-исполнитель. Разговор о самосовершенствовании разработчика.

Если программист хочет выполнять задачи эффективно, если ему не все равно, что думают о его коде другие разработчики, то он должен изучать свои инструменты и уметь их правильно использовать. А в отношении качества кода разработчик должен только себе (может быть, своим коллегам, если работает в команде). Иными словами, если человек хочет что-то уметь, он должен этому учиться. А кому он должен? — только себе.
Очень перекликается с en.wikipedia.org/wiki/Worse_is_better и кстати сам Richard P. Gabriel на его сайте очень интересно разбирает нетривиальные последствия подхода. В моем понимании, Worse-Is-Better победил — это современный Agile
Исторически KISS расшифровывается как keep it simple stupid или keep it simple , stupid. А вариации на тему keep it S&S: simple & straightforward, smart & simple, simple silly, sort & simple, super simple, safe and sound и т.д. — подгонка аббревиатуры KISS к ситуации.
На мой взгляд, вариации с «straightforward» и «short» наиболее точно отражают суть принципа KISS в программировании (но в других областях, например в авиастроении, могут оказаться более удачными и другие расшифровки). Из этих двух вариаций мне больше нравится «straightforward», потому что в очень редких случаях «короче» не означает «проще».
Аргумент есть прямо в комментарии, на который вы отвечаете.
Не совсем понимаю, почему люди не согласны с тем, что straightforward («простой», «прямолинейный», «честный», «открытый», «откровенный») наиболее точно отражает суть принципа KISS в программировании. Потому что историческое название из авиастроения, откуда появился принцип, «Keep it simple stupid»? Или почему то ещё?
Потому что KISS расшифровывается именно так, как расшифровывается. А все остальное, что вы ему приписываете — апокрифика.
Цитата:
«На мой взгляд, принцип KISS может быть полезен лишь для начинающих проектировщиков, которые не знают или не понимают основных принципов проектирования.»
Он и есть основной принцип проектирования/разработки :)
KISS — это о балансе. Хотелось бы больше размышлений о поиске этого самого баланса, а не так прямолинейно, как здесь — «KISS нарушен, делаем по-другому».
Поэтому перенесём FindIntersection в IShape.

И вы хотите сказать, что количество методов в IShape не будет расти с увеличением количества фигур?

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

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

Как стороннему программисту, который первый раз видит код, это узнать, не перебирая все классы?

Там 3 примера кода. В первом примере — искать usages Круга или Квадрата, это сказано в статье. После первого рефакторинга, во втором и третьем примерах, поведение, специфичное для Круга и Квадрата, находится внутри классов Круг и Квадрат. Для того чтобы понять, что искать нужно именно там, нужно изучить ООП (хотя по usages тоже можно найти).
Ага, в Square просто нет метода расчета пересечения с кругом. Первая реакция — WTF?

(Ну и да, если бы паттерн реализовывали правильно, искать нужно было бы наследника Intersection<Square,Circle>, что, в общем-то, существенно проще Find usages)
наследника Intersection<Square,Circle>

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

в Square просто нет метода расчета пересечения с кругом. Первая реакция — WTF?

Зато есть механизм расчёта пересечения Square с любой фигурой, можно его посмотреть, раз не хватает смекалки метод пересечения круга и квадрата поискать и в круге, и в квадрате. Если у вас есть идея как написать замечательный простой код, работать с которым сможет и codemonkey без зачатков логики, и в котором можно будет ещё проще найти нужный метод, то, может быть, поделитесь?
В результате пересечения прямоугольника и круга может получится, в зависимости от размера фигур: прямоугольник, круг, скруглённый прямоугольник — посмотрите внимательнее код, там есть комментарий. Поэтому ваше предложение не подходит.

Правда не подходит? А то, что Intersection<T1,T2> в ходе выполнения может возвращать что угодно, вас не смущает?

раз не хватает смекалки метод пересечения круга и квадрата поискать и в круге, и в квадрате

Если нужна «смекалка», значит, код не очевиден.

Что возвращает нас к обсуждению субъективности «простоты» кода.
Эммм… а кто-то говорил, что смекалка тут является обязательным требованием? Я же предложил и другой подход:
Зато есть механизм расчёта пересечения Square с любой фигурой, можно его посмотреть, раз не хватает смекалки… поискать и в круге, и в квадрате

хотя тут речь идёт не о смекалке, а скорее о понимании ООП. А как я говорил в статье, простой код и код, для написания которого требуется как можно меньше усилий и знаний — не одно и тоже.
понимании ООП

А «понимание ООП» — это универсальная и объективная характеристика?

Проще говоря: если бы я увидел код по вашему «третьему сценарию» в боевом проекте, я бы пошел к разработчику разбираться, что это, зачем оно взялось, и где он планирует работать через полгода.
Ух, полегче, горячий парень. Агрессия порождает только агрессию. Я просто хотел помочь тебе разобраться в принципах проектирования.
Я вас об этой помощи просил? Вроде бы нет.
Only those users with full accounts are able to leave comments. Log in, please.