Пусть у нас есть класс Point ("Точка") (оставшийся от предыдущей статьи или новый):
Метод ToString()
В C# любой объект может превращаться в строку. Например, если мы напишем в файле Program.cs
печать в консоль точки point1, то в консоли появится
За превращение объекта в строку отвечает метод ToString() (его название переводится как "к строке"). Он есть у всех классов, даже когда мы его не пишем, в силу наследования всех классов от object (потом мы изучим, что это такое). Также ToString() вызывается автоматически, нам необязательно писать point1.ToString().
Но такая печать в консоль, какая есть по умолчанию, нас не устраивает. Поэтому перепишем этот метод на свой. Начнём набирать название метода, и выберем соответствующую подсказку в Rider:
Метод должен возвращать в ответ строку:
О ключевых словах public и override будет в следующих статьях, а пока мы их не трогаем. Теперь печать в консоль стала информативнее:
Класс "Прямоугольник"
Добавим класс Rectangle ("Прямоугольник"). Прямоугольник можно задавать двумя точками - левой нижней и правой верхней. Поэтому конструктор прямоугольника должен получать на вход две точки:
Давайте проверим, как это работает. Для этого нужно создать прямоугольник и распечатать его. Опять-таки, поскольку прямоугольник задаётся двумя точками, то чтобы распечатать его, достаточно распечатать две точки:
Проверим:
Проверка на корректность
Любой нормальный метод должен проверять, что ему на вход подают корректные данные, и в случае чего кидать исключение с человекочитаемой ошибкой. Какие некорректные данные могут быть у нас? Координаты точек могут быть любыми, тут всё просто. А в прямоугольнике? Левая нижняя точка должна быть левее и ниже, чем правая верхняя. Соответственно, если она не левее или не ниже, мы должны кидать ошибку. Поэтому конструктор прямоугольника должен выглядеть так:
Обратите внимание, что тут "ИЛИ" - мы кидаем ошибку, если левая нижняя точка правее ИЛИ выше правой верхней.
Здесь для превращения точки в строку будет использоваться всё тот же ToString() точки, поэтому, если нам потребуется поменять формат печати точки (например, заменить круглые скобки фигурными), то поменяв ToString() точки, мы изменим печать и в прямоугольнике.
Крайние случаи
Добавим прямоугольнику метод, который проверяет, лежит ли заданная точка внутри прямоугольника. Этот метод должен выдавать в ответ результат вычисления логического условия (тип данных bool):
Здесь условия должны выполняться одновременно, поэтому "И".
Однако здесь есть крайний случай. Если точка находится на границе, выдавать true или false? Применительно к коду, должны ли быть неравенства строгими >, < или нестрогими >=, <=? Подобные крайние случаи бывают во всех областях деятельности, не только в геометрии. Например, если нужно найти наибольшее из чисел в списке, то что делать, если список пуст? Программист должен выявлять подобные крайние случаи и расспрашивать аналитика о более точной постановке задачи.
Проверим код:
Сделаем метод, вычисляющий пересечение прямоугольников:
Крайний случай здесь - это когда они не пересекаются. Нужно проверять это и либо кидать исключение, либо возвращать в ответ null. В реальной жизни об этом нужно спрашивать аналитика, а мы пойдём вторым путём. Обратите внимание, что на вход метода подаётся только один прямоугольник - вторым прямоугольником будет тот, у которого вызывается метод:
Соответственно, rectangle.BottomLeft - это точка прямоугольника rectangle, а просто BottomLeft - это точка прямоугольника, у которого вызван метод.
Нижняя координата итогового прямоугольника - это максимум из нижних координат двух прямоугольников. А верхняя - это минимум из верхних координат. И так далее. Для нас эта математика не важна, просто используйте мои формулы.
Создание прямоугольника-пересечения разбито на несколько строк 40-43 для удобочитаемости. Слишком длинные строки - это плохо для Git, потому что при соединении работы нескольких программистов иногда требуется видеть три копии файла одновременно. Обратите внимание, что конец команды (закрывающая скобка) выровнен по вертикали с началом команды, а каждый входной аргумент написан на своей строке.
Крайний случай здесь выявляется очень просто - прямоугольники не пересекаются, если итоговый "прямоугольник" получается вывернутым наизнанку:
Поэтому мы сначала вычисляем координаты этого прямоугольника, а потом проверяем, является ли он "вывернутым".
Мы должны тестировать все возможные случаи, в том числе и крайние. Поэтому не поленимся и напишем такие тесты:
Переиспользование кода
Когда код в нескольких местах программы дублируется, нужно выносить дублирующийся кусок в отдельную функцию / метод. Дублирование кода - это плохо. Во-первых, при изучении кода вам нужно будет прочитать все места и понять, что они делают, что дольше, чем если прочитать одно место. Во-вторых, при необходимости изменить код нужно будет менять все места. В-третьих, можно забыть поменять одно из мест и код получится неконсистентным (разные места перестанут соответствовать друг другу).
Мы уже видели это, когда сделали преобразование точки в строку. Метод ToString() точки был написан в одном месте и вызывался при печати как точки, так и прямоугольника, и ещё при кидании исключения. Чтобы поменять логику печати точки, достаточно было изменить только одно место - эти изменения подтягивались во все места, где было нужно.
Сделаем у прямоугольника метод, который получает на вход прямоугольник и точку и проверяет, лежит ли эта точка в пересечении текущего и поданного на вход прямоугольников. У нас уже есть метод для построения пересечения и метод, проверяющий, лежит ли точка в прямоугольнике. Поэтому мы обязаны переиспользовать их:
Метод Intersect может вернуть null, а у несуществующего прямоугольника нельзя вызвать метод Contains(). Поэтому у нас горит жёлтое предупреждение:
Что делать, если пересечение отсутствует? По идее, тогда точка не лежит в пересечении - надо вернуть в ответ false:
Сделайте ещё один метод Intersect(), принимающий на вход список прямоугольников и находящий пересечение всех них. Для этого используйте цикл. Перед каждой итерацией цикла проверяйте текущее пересечение на null, потому что пересекать null с чем-то ещё нельзя. Переиспользуйте имеющиеся методы.
Сделайте ещё один метод IsInRectanglesIntersection() для точки и списка прямоугольников. Переиспользуйте имеющиеся методы.
Итак, мы написали много кода. А теперь самое интересное. Пусть теперь границы прямоугольника не включаются во внутренность. Чтобы поменять эту логику, достаточно поправить один метод - который определяет, лежит ли точка внутри прямоугольника. Все остальные методы вызывают его, так что изменение в нём одном меняет всю логику программы.
Вот это - сила переиспользования кода!
Далее
Инкапсуляция в ООП
Оглавление