В объектно-ориентированном программировании одной из полезных возможностей является использование так называемого композиционного подхода. Этот подход подразумевает существование класса-контейнера, именуемого агрегатором, который включает вызовы из других классов. Таким образом, при создании объекта класса-контейнера автоматически создаются объекты классов, входящих в его состав.
Чтобы оценить значимость композиции в программировании, можно провести аналогии с обыденной жизнью. Многие биологические и технические системы состоят из более простых элементов, которые тоже являются объектами. Например, животное состоит из различных органов, таких как сердце и желудок; а компьютер — из различных компонентов, таких как процессор и память.
Композиция часто остаётся в тени, не выделяясь как основное свойство объектно-ориентированного программирования, наряду с наследованием, инкапсуляцией и полиморфизмом, так как используется реже.
Важно не путать композицию с наследованием, включая множественное. Наследование означает принадлежность к определённой группе или общности, в то время как композиция рассматривает объект как целое, состоящее из частей. Наследование переносит атрибуты другого класса без создания экземпляров родительского класса. Композиция же создаёт объекты входящих классов через класс-агрегатор.
Рассмотрим пример реализации композиции на Python. Допустим, требуется создать программу для расчёта площади обоев, предназначенных для обклейки помещения. Окна, двери, пол и потолок оклеивать не нужно.
Прежде чем взяться за разработку программы, займемся проектированием в объектно-ориентированном стиле. Комната представляется в виде прямоугольного параллелепипеда, состоящего из шести прямоугольников. Суммарная площадь параллелепипеда вычисляется как сумма площадей всех его прямоугольных граней. Площадь каждой из них равна произведению её длины на ширину.
Поскольку обои клеятся только на стены, то площадь верхнего и нижнего прямоугольников можно не учитывать. Из рисунка видно, что площадь одной стены равна xz, а другой - yz. Поскольку противоположные стороны равны, общая площадь четырёх стен равна S = 2xz + 2yz = 2z(x+y). Затем от общей площади нужно вычесть площадь дверей и окон, поскольку они не оклеиваются.
Мы можем выделить три типа объектов: окна, двери и комнаты. Все они имеют свои классы. Окна и двери являются частями комнаты, так что они включаются в состав объекта «помещение».
Для этой задачи существенны лишь два параметра: длина и ширина. Поэтому классы для «окон» и «дверей» можно объединить в один. Если бы интерес представляли другие свойства, такие как толщина стекла или материал двери, то стоило бы создавать отдельные классы. В данном случае сосредоточимся на вычислении площади объекта:
class Win_Door: def __init__(self, x, y): self.square = x * y
Класс «комната» выполняет роль контейнера для окон и дверей. Он обязан вызывать класс «окно-дверь».
Хотя в помещении обычно есть окна и двери, возможна ситуация, когда требуется обклеить помещение без окон и дверей, как, например, кладовую. Поэтому в конструкторе стоит учитывать размеры только самой комнаты, а детали интерьера добавлять через метод, позволяющий добавлять любые объекты-компоненты в список.
class Room: def __init__(self, x, y, z): self.square = 2 * z * (x + y) self.wd = [] def addWD(self, w, h): self.wd.append(WinDoor(w, h)) def workSurface(self): new_square = self.square for i in self.wd: new_square -= i.square return new_square r1 = Room(6, 3, 2.7) print(r1.square) # выведет 48.6 r1.addWD(1, 1) r1.addWD(1, 1) r1.addWD(1, 2) print(r1.workSurface()) # выведет 44.6
Практическая работа
Описанная выше программа нуждается в улучшениях. Следует провести доработку и исправления, согласно предложенному плану.
При вычислении оклеиваемой площади значение поля self.square
остаётся неизменным. Оно всё ещё хранит полную площадь стен и может быть полезно,
если изменится список объектов wd
, потребующись повторный расчёт оклеиваемой площади.
Тем не менее, в классе отсутствует функциональность для сохранения данных о длинах сторон, которые могут быть полезны. Если потребуется изменить какую-либо из величин у существующего объекта, то удобно было бы иметь исходные параметры, чтобы пересчитать нужные площади. Таким образом, значения самих площадей хранить необязательно.
Внесите изменения в код, чтобы объекты класса Room имели четыре поля: width
, length
, height
и wd
.
Площадь помещений будет вычисляться при обращении к соответствующим методам.
Хоть программа уже рассчитывает площадь для оклейки, но не указывает, сколько потребуется рулонов обоев. Добавьте метод, который определяет количество необходимых рулонов, получая в качестве параметров длину и ширину одного рулона.
Создайте интерфейс для программы, чтобы она запрашивала данные у пользователя и предоставляла информацию о площади оклеиваемой поверхности и количестве рулонов, которые нужно приобрести.