Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Под простотой же в данном случае следует понимать
эта статья вода который и так понятен если не всем, то подавляющему большинству разработчиков
можно было бы сократить статью до одного предложения без потери ценности
почему у Вас не получилось привести хотя бы 2-3 примера в статье?
struct S1
{
//прочие поля структуры...
char name[50];
};
struct S2
{
//прочие поля структуры...
std::string name;
};
Если вы используете паттерн проектирования там, где нет проблемы, которую решает данный паттерн – то вы нарушаете KISS, внося ненужные усложнения в код. Если вы НЕ используете паттерн проектирования там, где есть проблема, соответствующая паттерну – то вы опять-таки нарушаете KISS, делая код сложнее, чем он мог бы быть.
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 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("Неопределенное пересечение");
}
}
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);
РассчитатьПересечениеС(Фигруа ДругаяФигура, Число НомерВызова), если НомерВызова>2, кидаем исключение
Это лишняя операция, которая никак не обусловлена собственно задачей.
У всех разные критерии «простоты».
она нужна чтобы тест, проверяющий что вы указали правила расчёта пересечений для всех фигур упал, а не зациклился
правила расчёта пересечений для всех фигур упал, а не зациклился
Я в статье дал определение простоты, взятое из словаря Ушакова, даже ссылку дал.
Stack overflow больше не приводит к падению?
вынести эти правила за пределы фигур
Вы умеете предвидеть будущее и гарантировать что код фигур не изменится так, что исключение StackOverflow будет кидаться в тесте по другим причинам, помимо не написания алгоритма расчёта пересечения для некоторой пары фигур?
Есть такой принцип, ООП называется, который говорит что неплохо бы данные и код который с ними работает в одной сущности хранить.
не критично, почему он кинулся, мне критично, что тест упал
Во-первых, ООП — не единственная парадигма программирования.
А во-вторых, ваша задача изначально нарушает этот (ООП) подход: ваше действие производится над двумя объектами
то есть, по-вашему, добавление одной операции сравнения двух чисел — слишком большая цена, чтобы сделать тест более понятным и не адски тормозным, а также получить информацию почему тест упал?
Суть в том, что данные и код их использующий находятся в одной сущности. В примере квадрат не умеет сам рассчитывать пересечение с треугольником, и передаёт данные о себе треугольнику — квадрат использует свои данные для нахождения ответа.
Критично, что у вас детали реализации (защита от бесконечной рекурсии) просочились в интерфейс.
И какие же данные передает о себе квадрат?
Один из вариантов решения — не передавать значение через аргумент функции, а передавать через protected переменную
Самого себя
Вот ваши данные, нужные для расчета, и утекли наружу объекта.
Intersect фигуру. Предположим, что он не знает, как с ней считать пересечение. Что он делает? Правильно передает туда себя. Это важно — не свои данные (потому что у каждой фигуры «свои данные» отличаются, и вы не можете зафиксировать их в интерфейсе), а именно себя. this. Что происходит дальше? Фигура, куда он себя передал — предположим, что она знает, как считать пересечения с квадратом — берет из квадрата данные о нем (координаты), и решает задачу.Intersect другую фигуру, возвращает new Intersection<TThis, TOther>(this, other).Intersect().создавать класс Пространство только ради того чтобы пересечение двух фигур посчитать — глупо. Для этого веские причины нужны, не стоит плодить лишние сущности. Вот если алгоритмы расчёта пересечений очень сложные, или если фигура очень много чего умеет помимо как пересекаться — тогда пожалуйста. Но даже тогда неплохо сделать чтобы фигура делегировала обращение расчёта пересечения пространству.Чаще удобнее машину просить поехать, а не законы физики или пространство.
вопрос: чем с точки зрения ООП это отличается от «правило расчета перечислений получило квадрат и круг, из каждого получило данные, построило пересечение»
А вот теперь, пожалуйста, назовите объективный критерий, по которому ваше решение проще этого или это решение проще вашего.
Здесь правило расчета перечислений — завистливая функция (Фаулер, «Рефакторинг»), это smell.
Если функция использует больше данные постороннего класса, чем своего, то её нужно перенести в этот посторонний класс. Если использует данные нескольких классов — то в тот класс, из которого используется больше всего данных, или, как вариант, в тот класс, данные которого важнее в данном контексте использования.
А теперь вы добавили круги (два параметра на объект). Будете дописывать логику в треугольники
Преимущество решения: при добавлении новой фигуры не нужно менять ранее написанные классы
Smell — это не обязательно сигнал к исправлению
Еще полезно подумать о том, как вы будете реализовывать расчет пересечений для n фигур
В каком из них должна быть функция расчета
Завистливая функция — это сигнал к исправлению, давайте обойдёмся без софистики
Поскольку пересечение — тоже фигура (скорее всего какой нибудь скруглённый многоугольник), то пересечение 3х фигур находится так: фигура1.Пересечение(фигура2.Пересечение(фигура3)).
один из хороших подходов — когда код найдётся и там и там.
Не всякая функция, которая обрабатывает больше данных, чем ей доступно локально, является завистливой.
Это плохой подход, потому что он нарушает SRP.
В данном случае функция была завистливой, давайте обойдёмся без софистики
По вашему, SRP — это в «каждом классе максимум одна публичная функция»? Кстати, в земле нет непосредственно самого кода копания.
объективного критерия простоты все еще нет
Например, поиск завистливых функций довольно легко автоматизируется.
вы можете автоматизировать поиск кандидатов на «завистливые» функции, но не финальное их определение
введения дополнительной не содержащейся в коде информации о предметной области
Смогу при таком условии (я снова повторяюсь, не пора ли плавно заканчивать спор?):
введения дополнительной не содержащейся в коде информации о предметной области
class ДелательПересечений
{
public:
typedef TYPELIST_3<Прямоугольник, Элипс, Многоугольник> ПоддерживаемыеФигуры.
}
в том числе благодаря тому что имена переменных (p1, p2, lhs) не информативны
что для всех фигур предусмотрели расчет пресечения… поддерживать его сложнее
все это разбросанно по разным файлам и со временем может превратиться в кошмар
а это опять же проверки через динамическое приведение типов
там юнит-тест это проверяет
Почти во всех современных IDE есть горячие клавиши для быстрого перехода к нужному классу.
а это опять же проверки через динамическое приведение типов
А в чем тогда смысл следовать KISS, если все проверки можно свалить на юнит тесты?
Запуск юнит теста, только для проверки, что все варианты предусмотрели — дольше, чем просто пробежаться взглядом по десятку функций.
Если навигация по коду не удобна без средств предоставляемых IDE, стоит задуматься, а все ли хорошо сделано.
если код тяжело читается в простом текстовом редакторе без подсветки синтаксиса, то это плохой стиль кодирования
а они и есть самое запутанное и опасное место, в котором можно допустить ошибки
Опять же расчет пресечения фигур не является неотемлемым свойством фигуры.
Наличие юнит-тестов не гарантируют что код простой и понятный (вы сами то поняли что спросили?).
В реальных проектах считается хорошим тоном запускать все тесты перед отправкой кода в репозиторий.
Можно и без помощи IDE найти два файлика. В любом случае просмотреть 2 маленьких файлика проще, чем один гигантский в 100 раз больше обоих.
Вариант, который вы предлагаете, и с подсветкой, и без подсветки воспринимается тяжело.
вопрос был что в конкретно данном случае вам не нравится, а не почему в целом вам не нравятся проверки типов
можно взглядом оценить, что ничего не забылии
если есть необходимость писать юнит тест для проверки, что добавили все методы, то это уже не очень простой кодТак что через некоторое время выясняется что приложение иногда падает с ошибками при поиске пересечений… После этого программисту приходится изучать класс СтатическийДелец, который ничего общего не имеет ни с фигурами, ни с пересечениями, и задача решается.
Так что через некоторое время выясняется что приложение иногда падает с ошибками при поиске пересечений…
После этого программисту приходится изучать класс СтатическийДелец, который ничего общего не имеет ни с фигурами, ни с пересечениями, и задача решается.
Вариант развития событий 2. Понадобилось программисту вычислить пересечение 2х фигур. Зашёл он в класс фигуры и ничего не увидел.
Смотрим треугольник, делаем по аналогии
Дальше пограмист должен посмотреть на все оставшиеся классы (ну не на все, еще на пары другой было бы достаточно), что бы понять, что для каждой новой фигуры необходимо реализовать ее пересечение со всеми остальными
Можно сделать объявление функции поиска пересечений рядом с базовым классом для фигур принимающую в качестве параметров две ссылки(или указателя) на базовый класс.
в моем случае после этого нужно только добавить в список новый тип, а в вашем написать длинный блок if-else
Поэтому перенесём FindIntersection в IShape.
Проще найти конкретный алгоритм поиска пересечения, так как теперь не нужно искать его в гигантском классе среди множества методов с одинаковыми именами.
В каком классе находится метод, рассчитывающий пересечение круга и квадрата?
Как стороннему программисту, который первый раз видит код, это узнать, не перебирая все классы?
Intersection<Square,Circle>, что, в общем-то, существенно проще Find usages)наследника Intersection<Square,Circle>
в Square просто нет метода расчета пересечения с кругом. Первая реакция — WTF?
В результате пересечения прямоугольника и круга может получится, в зависимости от размера фигур: прямоугольник, круг, скруглённый прямоугольник — посмотрите внимательнее код, там есть комментарий. Поэтому ваше предложение не подходит.
Intersection<T1,T2> в ходе выполнения может возвращать что угодно, вас не смущает?раз не хватает смекалки метод пересечения круга и квадрата поискать и в круге, и в квадрате
Зато есть механизм расчёта пересечения Square с любой фигурой, можно его посмотреть, раз не хватает смекалки… поискать и в круге, и в квадрате
понимании ООП
KISS — принцип проектирования, содержащий все остальные принципы проектирования