Основы ООП: композиция (дизайн)

При проектировании программ с применением ООП встает задача нахождения оптимального (по некоторым заданным характеристикам) разбиения на классы и отношений между ними. В русском языке для обозначения этого лучше всего подошел бы термин композиция (как составление, соединение частей в единое целое). К сожалению, данный термин сильно перегружен в английской терминологии, и используется с разными значениями, включая и один из частных случаев "составления" классов. Это отступление необходимо, потому что при чтении другой компьютерной литературы Вы можете найти различные толкования термина "композиции": как в обобщенном, так и более узком смысле. Мы постараемся изложить данную концепцию, конкретизируя смысл терминов в каждом случае (когда подразумевается общий "дизайн/ проектирование" программного интерфейса, а когда — "композиционное агрегирование").

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

  • Композиция (полное включение или композиционное агрегирование) объектов-полей в объект-владелец. Связь таких объектов описывается отношением "целое-часть", причем часть не может существовать вне целого. Говорят, что объект-владелец "имеет" ("has a") объект-свойство, а объект-свойство "является частью" ("part of") объекта-владельца. Владелец создает и уничтожает свои части. Удаление владельца приводит к удалению всех его частей; владелец не может существовать без частей.
  • Агрегирование объектов-полей объектом-владельцем представляет собой более "мягкое" включение. Хотя отношение также описывается как "целое-часть", владелец лишь содержит ссылки на части, которые могут назначаться, меняться и существовать в отрыве от целого. Более того, одна часть может использоваться в нескольких "владельцах".
  • Ассоциация, то есть одно- или двухсторонняя связь независимых объектов, имеющая произвольный прикладной смысл. Говорят, что один объект "использует" другой.

Не следует забывать еще один тип отношений: "является" ("is a"), рассмотренный ранее в разделе про наследование.

В качестве примера полного включения можно привести машину и её двигатель. Здесь под машиной понимается полноценное средство передвижения. Без мотора это не так. И конкретный двигатель принадлежит в каждый момент времени только одной машине. Ситуации, когда двигателя в машине еще нет (на заводе) или уже нет (в автомастерской), эквивалентны тому, что мы сломали исходный код программы.

Примером агрегирования является состав групп студентов для занятий по определенным курсам: группа для каждого курса включает несколько студентов, причем любой из них может относиться и к другим группам (если слушает несколько предметов). Группа "имеет" слушателей. Выход студента из состава группы, не сказывается на учебном процессе группы (остальные продолжают учиться).

Наконец, для демонстрации идеи ассоциации рассмотрим компьютер и принтер. Можно сказать, что компьютер использует принтер для печати. Принтер может быть включен или выключен по необходимости, причем один и тот же принтер можно использовать с разных компьютеров. Все компьютеры и принтеры существуют независимо друг от друга, но могут использоваться совместно.

Что касается характеристик, которыми принято руководствоваться при проектировании классов, то к наиболее известным относятся:

  • DRY (Don't repeat yourself) — "не дублируй код": вместо этого выноси общие части в родительские (по возможности, абстрактные) классы;
  • SRP (Single Responsibility Principle) — "разделяй сферы ответственности": один класс должен выполнять одну задачу, а если это не так — нужно его раздробить на более мелкие;
  • OCP (Open-Closed Principle) — "пиши код открытым для расширения, но закрытым для модификации": если в классе X "зашито" несколько вариантов расчета и могут появиться новые, сделай базовый (абстрактный) класс для отдельного расчета и на его основе создавай специфические варианты ("расширение" функционала), подключаемые к классу X без его модификации.

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