Композиция или наследование: как выбрать?
… не было ни композиции, ни наследования, только код.
И был код неповоротливым, повторяющимся, нераздельным, несчастным, избыточным и измученным.
Основным инструментом для повторного использования кода была копипаста. Процедуры и функции были редкостью, подозрительными новомодными штучками. Вызов процедур был дорогим удовольствием. Части кода, отделенные от основной логики, вызывали недоумение!
Мрачные были времена.
Но вот лучик ООП воссиял над миром… Правда, несколько десятилетий 1 никто этого не замечал. Покуда не появился графический интерфейс 2 , которому, как выяснилось, очень-очень не хватало ООП. Когда нажимаешь на кнопку в окне, что может быть проще, чем отправить кнопке (или ее представителю) сообщение «Нажатие» 3 и получить результат?
И вот тут ООП взлетел. Было написано множество 4 книг, расплодились бесчисленные 5 статьи. Так что сегодня-то каждый может в объектно-ориентированное программирование, так?
Увы, код (и интернет) говорит, что не так
Самые жаркие споры и наибольшее непонимание, похоже, вызывает выбор между композицией и наследованием, зачастую выраженный мантрой «предпочитайте композицию наследованию». Вот об этом и поговорим.
Когда мантры вредят
В житейском плане «предпочитать композицию наследованию» в целом нормально, хоть я и не любитель мантр. Несмотря на то, что они зачастую и несут зерно истины, слишком легко поддаться соблазну и бездумно следовать лозунгу, не понимая, что за ним скрывается. А это всегда выходит боком.
Желтушные статьи с заголовками вроде «Наследование — зло» 6 тоже не по мне, особенно если автор пытается обосновать свои набросы, сначала неправильно применяя наследование, а потом делая вывод, что оно во всем виновато. Ну типа «молотки — отстой, потому что ими нельзя завинтить шуруп.»
Определения
Далее в статье я буду понимать под ООП «классический» объектный язык, который поддерживает классы со свойствами, методами и простое (одиночное) наследование. Никаких вам интерфейсов, примесей, аспектов, множественного наследования, делегатов, замыканий, лямбд, — ничего, кроме самых простых вещей:
- Класс: именованная сущность из предметной области, возможно, имеющая предка (суперкласс), определенная как набор полей и методов.
- Поле: именованное свойство с определенным типом, которое может, в частности, ссылаться на другой объект (см. композиция).
- Метод: именованная функция или процедура, с параметрами или без них, реализующая какое-то поведение класса.
- Наследование: класс может унаследовать — использовать по умолчанию — поля и методы своего предка. Наследование транзитивно: класс может наследоваться от другого класса, который наследуется от третьего, и так далее вплоть до базового класса (обычно — Object ), возможно, неявного. Наследник может переопределить какие-то методы и поля чтобы изменить поведение по умолчанию.
- Композиция: если поле у нас имеет тип Класс, оно может содержать ссылку на другой объект этого класса, создавая таким образом связь между двумя объектами. Не влезая в дебри различий между простой ассоциацией, агрегированием и композицией, давайте «на пальцах» определим: композиция — это когда один объект предоставляет другому свою функциональность частично или полностью.
- Инкапсуляция: мы обращаемся с объектами как с единой сущностью, а не как с набором отдельных полей и методов, тем самым скрываем и защищаем реализацию класса. Если клиентский код не знает ничего, кроме публичного интерфейса, он не может зависеть от деталей реализации.
Наследование фундаментально
Наследование — это фундаментальное понятие ООП. В языке программирования могут быть объекты и сообщения, но без наследования он не будет объектно-ориентированным (только основанным на объектах, но все еще полиморфным).
… как и композиция
Композиция это тоже фундаментальное свойство, причем любого языка. Даже если язык не поддерживает композицию (что редкость в наши дни), люди все равно будут мыслить категориями частей и компонентов. Без композиции было бы невозможно решить сложные задачи по частям.
(Инкапсуляция тоже вещь фундаментальная, но сейчас речь не о ней)
Так от чего весь сыр-бор?
Ну хорошо, и композиция, и наследование фундаментальны, в чем дело-то?
А дело в том, что можно подумать, что одно всегда может заменить другое, или что первое лучше или хуже второго. Разработка ПО — это всегда выбор разумного баланса, компромисс.
С композицией все более-менее просто, мы с ней постоянно сталкиваемся в жизни: у стула есть ножки, стена состоит из кирпичей и цемента и тому подобное. А вот наследование, несмотря на свое простое определение, может все усложнить и запутать, если хорошенько не поразмыслить над тем, как его применять. Наследование это весьма абстрактная штука, о нем можно рассуждать, но так просто его не потрогаешь. Мы, конечно, можем сымитировать наследование, используя композицию, но это, как правило, слишком много возни. Для чего нужна композиция — очевидно: из частей собрать целое. А вот с наследованием сложнее, потому что оно сразу о двух вещах: о смысле и о механике.
Наследование смысловое
Как в биологии классификация таксонов организует их в иерархии, так наследование отражает иерархию понятий из предметной области. Упорядочивает их от общего к частному, собирает родственные идеи в ветви иерархического древа. Смысл (семантика) класса по большей части выражен в его интерфейсе — наборе сообщений, которые класс способен понять, но также определяется и теми сообщениями, которыми класс отвечает. Унаследовался от предка — будь добр не только понять все сообщения, которые мог понять предок, но также и уметь ответить как он (сохранить поведение предка — прим. пер.) И поэтому наследование связывает наследника с предком гораздо сильнее, чем если бы мы взяли просто экземпляр предка как компонент. Обратите внимание, даже если класс делает что-то совсем простое, почти не имеет логики, его имя несет существенную смысловую нагрузку, разработчик делает из него важные выводы о предметной области.
Наследование механическое
Говоря о наследовании в механическом плане, мы имеем в виду, что наследование берет данные (поля) и поведение (методы) базового класса и позволяет использовать их повторно или же дополнить в наследниках. С точки зрения механики, если потомок унаследует реализацию (код) предка, то неизбежно получит и его интерфейс.
Я уверен, что в недопонимании виновата именно эта двойственная природа наследования 7 в большинстве ОО-языков. Многие считают, что наследование — это чтобы повторно использовать код, хотя оно не только для этого. Если придавать повторному использованию чрезмерное значение — жди беды в архитектуре. Вот пара примеров.
Как не надо наследовать. Пример 1
class Stack extends ArrayList < public void push(Object value) < … >public Object pop() < … >>
Казалось бы, класс Stack , все хорошо. Но посмотрите внимательно на его интерфейс. Что должно быть в классе с именем Stack? Методы push() и pop() , что же еще. А у нас? У нас есть get() , set() , add() , remove() , clear() и еще куча барахла, доставшегося от ArrayList , которое стеку ну вообще не нужно.
Можно было бы переопределить все нежелательные методы, а некоторые (например, clear() ) даже и адаптировать под наши нужды, но не многовато ли работы из-за одной ошибки в дизайне? На самом деле трех: одной смысловой, одной механической и одной комбинированной:
- Утверждение «Stack это ArrayList» ложно. Stack не является подтипом ArrayList . Задача стека — обеспечить выполнение правила LIFO (последним пришел, первым ушел), которое легко удовлетворяется интерфейсом push/pop, но никак не соблюдается интерфейсом ArrayList .
- Механически наследование от ArrayList нарушает инкапсуляцию. Клиентскому коду не должно быть известно, что мы решили использовать ArrayList для хранения элементов стека.
- Ну и наконец, реализуя стек через ArrayList мы смешиваем две разные предметные области: ArrayList — это коллекция с произвольным доступом, а стек — это понятие из мира очередей, со строго ограниченным (а не произвольным) 8 доступом.
Последний пункт — незначительная на первый взгляд, но важная вещь. Посмотрим на нее пристальнее.
Как не надо наследовать. Пример 2
Частая ошибка при наследовании — это создать модель из предметной области, унаследовав ее от готовой реализации. Вот, скажем, нам надо выделить некоторых наших клиентов (класс Customer ) в определенное подмножество. Легко! Наследуемся от ArrayList , называем это CustomerGroup и понеслась.
Не тут-то было. Поступив так мы опять спутаем две предметные области. Старайтесь избегать этого:
- ArrayList это уже наследник списка, утилиты типа «коллекция», готовой реализации.
- CustomerGroup это совсем другая штука — класс из предметной области (домена).
- Классы из предметной области должны использовать реализации, а не наследовать их.
Слой предметной области не должен знать, как у нас там все внутри сделано. Рассуждая о том, что делает наша программа, мы оперируем понятиями из предметной области, и мы не хотим отвлекаться на нюансы внутреннего устройства. Если видеть в наследовании только инструмент повторного использования кода, мы раз за разом будем попадаться в эту ловушку.
Дело не в одиночном наследовании
Одиночное наследование пока остается самой популярной моделью ООП. Оно неизбежно влечет наследование реализации, которое приводит к сильному зацеплению (coupling — прим. пер.) между классами. Может показаться, что беда в том, что ветка наследования у нас только одна на обе потребности: и смысловую и механическую. Если использовали для одного, то для другого уже нельзя. А раз так, может быть множественное наследование все исправит?
Нет. Отношение наследования не должно пересекать границы между предметными областями: инструментальной (структуры данных, алгоритмы, сети) и прикладной (бизнес-логика). Если CustomerGroup будет наследовать ArrayList и одновременно, скажем, DemographicSegment, то две предметные области переплетутся между собой, а «видовая принадлежность» объектов станет неочевидна.
Предпочтительно (по крайней мере, с моей точки зрения) делать так. Наследуемся от имеющихся в языке инструментальных классов по минимуму, ровно настолько, чтобы реализовать «механическую» часть вашей логики. Потом соединяем получившиеся части композицией, но не наследованием. Иными словами:
От инструментов можно наследовать только другие инструменты.
Это очень частая ошибка новичков. Что не удивительно, ведь так просто взять и унаследовать. Редко где встретишь обсуждения, почему именно это неправильно. Еще раз: бизнес-сущности должны пользоваться инструментами, а не быть ими. Мухи (инструменты) — отдельно, котлеты (бизнес-модели) — отдельно.
Так когда же нужно наследование?
Наследуемся как надо
Чаще всего — и при этом с наибольшей отдачей — наследование применяют для описания объектов, незначительно отличающихся друг от друга (в оригинале используется термин «differential programming» — прим. пер.) Например, нам нужна особенная кнопка с небольшими дополнениями. Нормально, наследуемся от существующего класса Кнопка. Потому что наш новый класс, это все еще кнопка, а мы полностью наследуем API класса Кнопка, его поведение и реализацию. Новая функциональность только добавляется к существующему. А вот если в наследнике часть функциональности убирается, это повод задуматься, а нужно ли наследование.
Наследование полезнее всего для группировки сходных сущностей и понятий, определения семейств классов, и вообще для организации терминов и понятий, описывающих предметную область. Зачастую, когда значительная часть предметной логики уже реализована, исходно выбранные иерархии наследования перестают работать. Если всё к тому идет, не бойтесь разобрать и заново сложить эти иерархии 9 так, чтобы они лучше соответствовали и работали друг с другом.
Композиция или наследование: что выбрать?
В ситуации, когда вроде бы подходит и то и другое, взгляните на дизайн в двух плоскостях:
- Структура и механическое исполнение бизнес-объектов.
- Что они обозначают по смыслу и как взаимодействуют.
Пока наследование остается внутри одной плоскости, все нормально. Но если иерархия проходит через две плоскости сразу, это плохой симптом.
Например, у вас есть один объект внутри другого. Внутренний объект реализует значительную часть поведения внешнего. У внешнего объекта куча прокси-методов, которые тупо пробрасывают параметры во внутренний объект и возвращают от него результат. В этом случае посмотрите, а не стоит ли унаследоваться от внутреннего объекта, хотя бы частично.
Разумеется, никакие инструкции не заменят голову на плечах. Когда строишь объектную модель, вообще полезно думать. Но если вам хочется конкретных правил, то пожалуйста.
- Оба класса из одной предметной области
- Наследник является корректным подтипом (в терминах LSP — прим. пер.) предка
- Код предка необходим либо хорошо подходит для наследника
- Наследник в основном добавляет логику
Иногда все эти условия выполняются одновременно:
- в случае моделирования высокоуровневой логики из предметной области
- при разработке библиотек и расширений для них
- при дифференциальном программировании (автор снова использует термин «differential programming», очевидно, понимая под ним нечто, отличное от DDP — прим. пер.)
Если это не ваш случай, то и наследование вам, скорее всего, будет нужно не часто. Но не потому, что надо «предпочитать» композицию наследованию, и не потому что она «лучше». Выбирайте то, что подходит наилучшим образом для конкретно вашей задачи.
Надеюсь, эти правила помогут вам понять разницу между двумя подходами.
Послесловие
Отдельная благодарность сотрудникам ThoughtWorks за их ценный вклад и замечания: Питу Хогсону, Тиму Брауну, Скотту Робинсону, Мартину Фаулеру, Минди Ор, Шону Ньюхэму, Сэму Гибсону и Махендре Кария.
1
Первый официальный ОО-язык, SIMULA 67, появился в 1967 году.
2
Системные и прикладные программисты приняли на вооружение C++ в середине 1980-х, но перед тем, как ООП стал общепринятым, прошел еще десяток лет.
3
Я намеренно упрощаю, не говорю про паб/саб, делегатов и тому подобное, чтобы не раздувать статью.
4
На момент написание этого текста Амазон предлагает 24777 книг по ООП.
5
Поиск в гугле по фразе «объектно-ориентированное программирование» дает 8 млн результатов.
6
Поиск в гугле выдает 37600 результатов по запросу «наследование это зло».
7
Смысл (интерфейс) и механику (исполнение) можно разделить за счет усложнения языка. См. пример из спецификации языка D.
8
С грустью замечу, что в Java Stack унаследован от Vector .
9
Проектирование для повторного использования через наследования выходит за рамки темы статьи. Просто имейте в виду, что ваш дизайн должен удовлетворить потребности и тех, кто пользуется базовым классом, и тех, кому нужен наследник.
Переводчик выражает благодарность ООП-чату в Telegram, без которого этот текст не смог бы появиться.
- Программирование
- Анализ и проектирование систем
- Совершенный код
- Проектирование и рефакторинг
- ООП
Средства гармонизации

Объемно-пространственная структура и тектоника являются основными общими категориями композиции. Для того чтобы композицию привести в полную гармонию, создать соразмерность и гармоничность соотношений всех ее частей и деталей, придать ей наиболее полную эстетическую выразительность, необходимо применить некоторые специфические средства композиции или, как их называют, средства гармонизации.
Симметрия и асимметрия является наиболее простым и ясным средством композиции зданий. Оно определяет основу построения как всей объемно-пространственной композиции, так и отдельных частей здания и архитектурных деталей. Принцип симметрии и асимметрии используется и при создании архитектурных ансамблей и планировочных комплексов.
Симметрией называется строго закономерное расположение одинаковых элементов относительно оси или плоскости, проходящих через геометрический центр плоскости или объема.
Вертикальные оси симметрии присутствуют в композиции центрических зданий. Вертикальные плоскости симметрии присутствуют в композиции отдельных зданий, ансамблей, интерьеров. Эта симметрия называется зеркальной. В вертикальных и горизонтальных проекциях плоскости симметрии превращаются в оси. В сложных композициях может быть несколько таких осей, которые в этом случае подразделяются на главные и второстепенные.
В произведениях архитектуры абсолютно строгая симметрия встречается редко, что объясняется сложным функциональным содержанием зданий; в большинстве случаев применяется так называемая частично нарушенная симметрия (например, при строго симметричном по форме плане несимметричное расположение некоторых помещений).
При асимметричном построении композиции ее отдельные элементы располагаются так, что оси симметрии полностью или частично отсутствуют. При этом неравные по величине и разные по форме части располагаются так, что создают зрительное равновесие, чем сохраняют единство композиции.
В сложных композициях симметрия и асимметрия обычно сопутствуют друг другу. Выбор того или иного приема построения композиции в каждом конкретном случае зависит от функциональных особенностей здания, места его расположения (значение здания в ансамбле площади, улицы и т. д.) и идейно-художественного замысла.
Симметричный прием помогает выразить строгость, торжественность, парадность. В архитектурной практике иногда встречаются формалистические приемы, когда несоответствующее функциональное содержание насильно втиснуто в симметричную композицию, ради ее ложной «представительности».
В современной архитектуре асимметричный прием композиции получил широкое распространение. Это объясняется стремлением к наиболее органичной связи формы со сложным функциональным содержанием современных зданий и желанием придать композиции более свободный живописный характер, максимально сблизить ее с природой.
Метр и ритм в архитектуре проявляются как закономерное повторение и чередование элементов (архитектурных деталей, форм, объемов). Это чередование используется в качестве специфического средства композиции как для отдельных зданий, так и для целых ансамблей.

Пример метроритмических и ритмических построений в архитектуре: а) библиотека им. Салтыкова-Щедрина в Ленинграде; б) Московский государственный университет на Ленинских горах
Существует два вида повторности — метрическая и ритмическая. Простейший вид повторности — метр — основан на чередовании одинаковых элементов с равными интервалами между ними.
Более сложный вид повторности — ритм — основан на закономерном изменении форм и интервалов. Этот порядок, помимо повторности, характеризуется изменением каких-либо свойств элементов и интервалов: нарастание или убывание их числа, размеров, форм и т. д. Метр и ритм в архитектуре часто выступают в единстве, образуя еще более сложные — метроритмические сочетания.
Все эти закономерности выступают прежде всего как непосредственное выражение функциональных и конструктивных особенностей здания. Вместе с тем метр и ритм является мощным средством художественной выразительности. Метрическое построение характеризует покой, статичность композиции; ритмическое — выражает направленность, динамичность (рис. 9).
При современном строительстве массовых зданий индустриальными методами метрическая структура играет значительную роль в композиции. Это метрическое расположение квартир жилых зданий, школьных классов, больничных палат, рабочих помещений различных учреждений и лабораторий. Этот внутренний строй подчеркивается чередованием одинаковых конструктивных элементов — стеновых панелей, оконных проемов и т. д.
На этом равномерном фоне часто создаются ритмические акценты, связывающие элементы метрического ряда в целостную композицию. Такими акцентами служат балконы, эркеры, проемы лестничных клеток, входы.
Метроритмические закономерности широко используются в комплексной ансамблевой застройке, где метрические и ритмические ряды образуются уже не отдельными архитектурными элементами, а группами зданий и пространством между ними.
Ритмические закономерности связаны со всеми средствами композиции, в том числе и с принципом симметрии. Так, например, ритмическое расположение элементов часто присуще зданиям, имеющим ясно выраженный композиционный центр. Как правило, это крупные общественные здания. Здесь прием симметрии подчеркивает их значительность, а ритмическое нарастание к центру создает зрительное движение ко входу в здание.
Масштабность в архитектуре — это восприятие человеком величины и значительности сооружения, это «подходящая предмету мера», приданная ему человеком, это, следовательно, соразмерность сооружения человеку и окружающей среде (в этом же значении употребляется иногда и термин «масштаб» в архитектуре).

Взаимосвязь абсолютных размеров здания, архитектурных членений и масштабности: а) колокольня монастыря в Москве; б) колокольня Ивана Великого в Московском Кремле
Здесь соизмеримость с человеком следует понимать не только как соотношение абсолютных размеров человека и архитектурных форм, но и как степень соответствия величины сооружения и его деталей тому назначению, которое придал ему человек.
Таким образом, понятие о масштабе сооружения нельзя заменить понятием о его размере. Маленькое по абсолютной величине здание может иметь крупный масштаб и большое здание — мелкий масштаб. Ярким примером этого может служить сравнение мавзолея и многоэтажного жилого дома. Одинаковые по своим физическим размерам здания могут иметь различную масштабность. Например, кинотеатр, универмаг и равный с ними по высоте п ширине жилой дом.
Подчеркнуть средствами архитектуры физическую величину сооружения не означает укрупнить его масштабность. Так здание, расчлененное поэтажными тягами или ярусами пилястр, зрительно будет казаться больше по размеру, чем такое же здание, решенное единым приемом — гладкой стеной или ордером, охватывающим все этажи. Но масштабность второго здания будет крупнее, чем первого. Такая закономерность объясняется тем, что архитектурные членения объема создают ощущение его большой физической величины, но размельчают масштаб. Так, например, многоярусная колокольня Новодевичьего монастыря кажется выше колокольни Ивана Великого, хотя на самом деле она ниже на 9 м, но масштаб последней несравненно крупнее, значительнее.
Все сказанное выше вовсе не означает, что масштабная выразительность ничем не связана с абсолютными размерами здания. Если маленькое здание расчленить множеством деталей, как большое, или большое трактовать, как маленькое, то и в том и в другом случае здания окажутся немасштабными. Большое будет казаться маленьким, и наоборот.
Таким образом, масштабность, как н все другие средства композиции, — это не формальный композиционный прием, а закономерность, вытекающая из жизни, из самой материальной природы архитектуры.
Масштабность архитектурных сооружений обусловливается их функциональным назначением, характером идейно-художественного содержания, местом и значением в ансамбле улицы, площади, города, наконец, природным окружением; большое влияние на трактовку масштабности сооружения оказывают применяемые для строительства материалы и конструкции.

Примеры подобия линейных элементов в архитектуре: а) членение отрезков на подобные части; б) членение антаблемента на подобные части (Пантеон в Риме); в) членение фасада и его элементов на подобные части (Палаццо Строцци во Флоренции)
Масштабное здание не только гармонично, красиво, оно в первую очередь целесообразно, удобно. Если масштабный строй композиции не соответствует содержанию здания и его окружению, мы говорим, что масштаб искажен, здание немасштабно.
Масштабная выразительность сооружений и ансамблей дифференцируется в соответствии с их конкретным содержанием и назначением. Общественным зданиям, как правило, соответствует несколько укрупненная, приподнятая масштабность; для уникальных общественных зданий, играющих организующую роль в ансамбле, характерен величественный, монументальный масштаб: жилым зданиям присущ более скромный, естественный масштаб.
Масштабность может быть качеством не только здания или ансамбля; это качество присуще и таким пространственным архитектурным образованиям, как площадь, улица, микрорайон. Здесь масштабный строй имеет свои закономерности, но строится на тех же принципах.
Каким же образом достигается та или иная масштабная выразительность, что оказывает влияние на восприятие масштаба зданий, площадей, улиц? Масштабный строй складывается в результате взаимодействия, согласованности масштабных связей: соразмерности здания с человеком, соразмерности частей здания и целого, соразмерности здания с окружающей средой. В создании масштабного строя сооружения используются различные средства композиции: членение объема, пластическая разработка архитектурных деталей, фактура, цвет и т. д.
Масштабные представления человека в архитектуре возникают и постепенно формируются в практике и процессе развития архитектуры как представления о целесообразном: о размерах и форме помещений, предназначенных для той или иной деятельности человека, о размере ступеней, удобных для подъема, о размере и форме окон и дверей различного назначения. Поэтому эти привычные элементы зданий, имеющие относительно постоянные размеры, называют «указателям и» масштаба. Большое значение при восприятии масштаба имеют также привычные представления человека о свойствах известных ему строительных материалов.
Особое значение в создании масштабного строя сооружения имеет пластическая разработка архитектурных деталей, характер которой уточняет масштабное выражение архитектурной формы. Детали монументального здания, крупные по своим абсолютным размерам, нуждаются в соответствующей разработке, моделировке. Мелкие детали не нуждаются в дополнительной разработке.
В масштабном строе сооружения различие масштаба его внешних и внутренних форм является закономерным. Это объясняется тем, что архитектурные элементы и детали всегда выглядят относительно крупнее в интерьере, чем в наружном пространстве. Внешний объем здания поэтому разрабатывается в более крупных формах, чем интерьер. Переход из внешнего пространства в помещение требует изменения масштаба. Масштабность интерьера имеет свои закономерности.
На формирование масштабной выразительности здания оказывает влияние масштаб архитектурной среды, в которой оно находится, и значение этого здания в ансамбле. Сооружение, играющее в ансамбле ведущую роль, должно иметь более крупный масштаб и в то же время оно должно подчиняться общему масштабному строю ансамбля.
Ярким примером в этом смысле может служить здание мавзолея Ленина, которое решено в приподнятой монументальной масштабности и в то же время органически вошло в общий масштабный строй Красной площади, прекрасно согласуясь со всеми ее сооружениями и пространством.
Масштабный строй гостиницы «Россия» не имеет ничего общего с масштабным строем Кремля, храма Василия Блаженного, мавзолея. И хотя масштаб гостиницы не может идти ни в какое сравнение с величественным масштабом этих сооружений, она подавляет их своими размерами, своей массой.

Примеры пропорциональной зависимости в архитектурных сооружениях на основе подобия фигур: а) возможные виды пропорциональной взаимосвязи; б) Эрехтейон в Афинах; в) Грановитая палата в Московском Кремле
Таким образом, здание может быть масштабным или немасштабным по отношению к масштабу соседних зданий, масштабу улицы, площади, наконец, по отношению к масштабу города или населенного пункта.
Представления о масштабе, так же как и о других средствах композиции, изменяются, обогащаются и дополняются в процессе развития архитектуры. Каждая эпоха имеет свой оттенок масштабной выразительности архитектурных форм, соответствующий характеру и уровню развития общества.
Уровень развития современного общества, влияние свойств новых строительных материалов и методов строительства проявляются в укрупнении масштабного строя сооружений. Это выражается и в крупном шаге опор, и в крупных формах покрытий над большими внутренними пространствами, и в больших плоскостях стекла, в простых лаконичных профилях деталей.
Пропорции — одно из основных композиционных средств, применяемых в архитектуре для приведения соотношений всех частей сооружения в зрительную гармонию. Таким образом, пропорцией называется определенное соотношение, соразмерность частей между собой и с целым. Это относится к соразмерности линейных размеров (высота, ширина), площадей и объемов. Охватывая все сооружение или ансамбль, пропорции образуют в своем единстве пропорциональный строй.
Так же как и другие средства архитектурной композиции, пропорциональный строй неотделим от материальной основы сооружения, способствуя художественному выявлению ее специфических качеств. Характер пропорциональных соотношений в здании определяется конкретными условиями и требованиями — функциональными, техническими, экономическими. В то же время пропорции являются одним из важнейших средств создания архитектурно-художественного образа. С их помощью может быть выражена монументальность, торжественность или, наоборот, скромность, простота; применением того или иного пропорционального строя зданию может быть придана зрительная легкость или тяжеловесность. Таким образом, работа над пропорциями — это не изолированный отвлеченный процесс, а одна из сторон общего процесса проектирования, направленного па решение конкретных задач.
В современной архитектуре, в основе которой лежит простота объемов, лишенных декоративных украшений, пропорциональная гармонизация композиции приобретает ведущее значение в вопросах художественной выразительности.
Вопросы пропорций разрабатывались еще в древности. Исследованиями в этой области занимались зодчие древней Греции и Рима, известные архитекторы эпохи Возрождения, многие западно-европейские ученые, русские архитекторы. Интерес к этому вопросу не ослабевает и в настоящее время. Однако до сих пор еще нет стройной, научно разработанной теории пропорций.

Графическое построение пропорции золотого сечения / Пример членения фасада в золотых отношениях (церковь Вознесенья в Коломенском)
Остановимся на рассмотрении принципа основных пропорциональных систем, имеющих значение в практической работе архитектора.
Наиболее простой является модульная система пропорций, которая характеризуется кратностью всех размеров сооружения некоторой единой величине, называемой модулем. Эта величина служит мерой всех частей сооружений и используется для создания соразмерности, т. е. полного взаимного соответствия размеров здания и его частей.
Модульная система пропорций лежит в основе классических архитектурных ордеров, где за модуль принят нижний диаметр (греческие ордера) пли радиус (римские ордера) колонны. Все размеры сооружений — высота колонн и расстояние между ними, высота антаблемента и его частей, общие размеры здания и все детали измеряются этим модулем, т. е. числом укладывающихся в них радиусов или диаметров колонн.
Введение системы модульных отношений облегчало процесс возведения зданий из отдельных элементов (каменных блоков) и разбивку их на участке. Появившись как следствие практической необходимости, модуль в античной архитектуре приобрел п большой художественный смысл. Модульная система позволила установить простые, легко воспринимаемые глазом соотношения, которые получили широкое распространение в архитектурно-строительной практике.
В современном массовом строительстве модульная система пропорций является необходимой предпосылкой типизации и унификации всех строительных элементов и габаритов зданий. Другая система пропорции строится на принципе геометрического подобия.
Эта система имеет широкое применение, так как в практической работе архитектору, как правило, приходится иметь дело именно с геометрическим, линейным выражением различных математических зависимостей (числовых отношений).
Пропорциональная зависимость существует как между линейными, вертикально расположенными элементами зданий, так и между вертикальными и горизонтальными элементами. Первая зависимость может быть выражена геометрическим подобием отрезков, вторая — подобием фигур — прямоугольников. Подобие отрезков и фигур связывают отдельные элементы в определенную зависимость, что н приводит их в единое гармоничное целое.
Уже в своем простейшем выражении — геометрическом подобии отрезков — пропорция иллюстрирует взаимосвязь, взаимообусловленность и строгую согласованность входящих в нее членов.
На схеме представлена схема, показывающая графическую зависимость двух линейных элементов, расчлененных в отношении: А: а=В : Ь=С : с, и примеры такой пропорциональной зависимости в некоторых памятниках архитектуры.
Признаком подобия здесь является параллельное или перпендикулярное расположение диагоналей соответственно расположенных фигур. Это свойство диагоналей позволяет практически строить и находить пропорциональные отношения при проектировании и при исследовании произведений архитектуры.
Особое место среди различных пропорциональных систем занимает «з олотое сечение», которым широко пользовались зодчие античности. В эпоху Возрождения оно считалось «божественной пропорцией». Исследователи указывают на многочисленные примеры проявления «золотого сечения» в природе (строение дерева, листа и т. д.), чем и объясняют силу эмоционального воздействия этого пропорционального соотношения на человека.
Принцип построения «золотого сечения» заключается в делении отрезка таким образом, что большая его часть является средней пропорциональной величиной между всем отрезком и его меньшей частью: а : 6 = 6 : (а + b).
Геометрическое построение деления отрезка в таком отношении проще всего осуществляется при помощи прямоугольного треугольника с отношением катетов 1 : 2, где больший катет, условно принимаемый за единицу, делится в заданном отношении. Это отношение иррационально. Достаточно точным его выражением является: а = 0,382; 6 = 0,618. Золотое сечение можно обнаружить в пропорциях многих архитектурных сооружений различных эпох.
Как уже говорилось, пропорции теснейшим образом связаны со всеми остальными средствами композиции. Пропорционирование размеров здания должно быть согласовано с тектоническими, масштабными и другими закономерностями. Так, например, в Парфеноне использована пропорция золотого сечения в тесной связи с тектоническими особенностями этого сооружения. Применение этой же пропорциональной закономерности, но без связи с тектоникой (архитравная балка слишком велика по высоте для данного пролета) приводит к полному нарушению общей гармоничности сооружения.
Понятие пропорциональности, гармоничности неразрывно связано и с нашими представлениями об удобстве и функциональной целесообразности. Так, например, комната, имеющая в плане «хорошие» пропорции, удобна для проходящих в ней жизненных процессов.
Пропорциональный строй, таким образом, нельзя рассматривать абстрактно, оторвано от конкретного сооружения. Поиски лучших пропорций должны вестись с учетом материальной основы здания. При этом главным фактором пропорционирования является создание гармонической согласованности элементов сооружения, единства и соразмерности частей и целого.
Многие современные архитектурные формы, создаваемые из железобетона, очень пластичны, пространственны, они не имеют плоскостей, на которых основывалась классическая теория пропорций (подобие фигур, отношение отрезков и т. д.). Новым архитектурным формам должна соответствовать и новая теория пропорционирования. Разработка такой теории является актуальной проблемой.
Контраст и нюанс — одно из ярких средств достижения художественной выразительности в архитектуре, характеризующее и выявляющее степень сходства или различия между определенными свойствами сооружения.
Контрастные отношения подчеркивают резко выраженные различия этих свойств; нюансные отношения означают очень постепенный, едва заметный переход от одного свойства к другому.
В отношениях контраста или нюанса могут находиться размеры и форма (большое и малое, горизонтальное и вертикальное, прямолинейное и криволинейное, простое и сложное, массивное и легкое), фактура, окраска, освещенность.
Прием контраста или нюанса не может быть искусственно, формально присвоен той или иной композиции — он должен явиться следствием действительных свойств данного объекта — функциональных, конструктивных, тектонических. Только в тех случаях, когда контрастные пли нюансные отношения соответствуют логике построения композиции, они становятся сильным средством эмоциональной выразительности. Однако не всякие объективные свойства следует противопоставлять — здесь архитектор должен уметь правильно видеть, что следует подчеркнуть с определенной целью выявления главного, наиболее значительного для общего строя данной композиции.
Применение приема контрастных и нюансных отношений самых разнообразных свойств (формы, размеров, фактуры, цвета) мы можем видеть на примерах многих сооружений древней и современной архитектуры. Он широко используется и в застройке улиц, площадей, современных микрорайонов, городов.
Дополнительные средства архитектурной композиции (цвет, фактура, освещение, орнамент, скульптура и живопись) могут играть очень значительную роль в композиции. Называются они дополнительными условно, главным образом потому, что подчиняются главным средствам композиции (пропорции, метру и ритму, контрасту и нюансу), а применение некоторых из них (орнамент, скульптура, живопись) далеко не всегда необходимо. Однако в композиции некоторых сооружений скульптура и живопись играют очень значительную роль.
В архитектуре используются как средства композиции и прикладные искусства — мебель, обои, драпировки и т. д., имеющие большое значение в решении композиции интерьера.
В современной архитектуре с ее простыми формами и деталями заводского изготовления такие композиционные средства, как фактура и цвет, особенно важны. Не меньшую роль в композиции современного интерьера играет освещение.
В условиях массового индустриального строительства введение в архитектуру цвета открывает новые возможности композиционных решений отдельных зданий и ансамблей при сохранении стандартности архитектурных элементов.
Богатейшими цветовыми возможностями обладают многие новые строительные материалы — керамика, пластики, цветные бетоны, стекло, — которые могут оказать самое сильное влияние на развитие архитектуры.
Все композиционные средства, основные и второстепенные, участвуют в создании архитектурного произведения в органической взаимосвязи и применяются в соответствии с конкретными условиями — назначением здания, его ролью в ансамбле, применяемыми материалами и т. д.
Эти средства и приемы композиции не являются неизменными, они развиваются и видоизменяются вместе с развитием потребностей общества, внедрением новых конструкций, материалов и методов строительства.
Техники повторного использования кода и разбиения сложных объектов на составные
В этой статье я опишу различные техники повторного использования кода и разбиения сложных объектов на части, с которыми я столкнулся. Постараюсь объяснить, почему классическое наследование, а также некоторые другие популярные подходы не работают в сложных случаях, и какие есть альтернативы.
Возможно многих удивит, что в основе большинства подходов повторного использования кода и создания составных объектов лежат стандартные структуры данных – массив, список, словарь, дерево, граф.
Т.к. в последние годы я пишу на JavaScript и React, то они будут использоваться в некоторых примерах. Да и в целом, периодически я буду упоминать React и другие веб-технологии. Тем не менее, думаю, что значительная часть статьи должна быть понятна и полезна разработчикам из других стеков.
Для некоторых подходов я добавил схемы, чтобы показать, как организованы составляющие сложных объектов. Будет часто упоминаться агрегация (агрегирование/делегирование/включение) и композиция.
Чтобы разделить логику одного сложного объекта на составные части, существуют несколько механизмов:
- Разделение функционала на классы/объекты и смешивание их полей, методов в одном объекте.
- Вынесение части функционала в обертки и помещение в них основного объекта, либо вкладывание объектов один в другой с организацией списка вложенных объектов.
- Вынесение части функционала в отдельные объекты/функции и помещение их в основной объект.
- Разделение функционала объекта на независимые части и использование какого-то внешнего механизма для организации нужного поведения с использованием этих частей.
В статье же я разделил техники/паттерны в зависимости от получаемой структуры данных, используемой для хранения составляющих сложного объекта.
Объединение (смешивание) функционала нескольких объектов в одном
Смешивание и примеси (миксины)
Самый простой, но ненадежный способ повторного использования кода – объединить один объект с другим(и). Подходит лишь для простых случаев, т.к. высока вероятность ошибки из-за замещения одних полей другими с такими же именами. К тому же, так объект разрастается и может превратиться в антипаттерн God Object.
Существует паттерн примесь (mixin/миксина), в основе которого лежит смешивание.
Примесь – это объект, поля и методы которого смешиваются с полями и методами других объектов, расширяя функциональность объекта, но который не используется сам по себе.
Можно добавить несколько миксин к одному объекту/классу. Тогда это схоже с множественным наследованием.
Классическое наследование
Здесь описывается классическое наследование, а не то, как наследование классов устроено в JS.
Подразумеваю, что читатель уже знаком с понятиями «наследование» и «множественное наследование». В отличие от простого смешивания, в классическом наследовании есть ряд строгих правил и механизмов, которые позволяет избежать множество проблем. В основе классического наследования лежит все то же смешивание — члены нескольких объектов объединяются в один объект.
При наследовании происходит копирование членов родительского класса в класс-наследник. При создании экземпляра класса тоже происходит копирования членов класса. Я не исследовал детали этих механизмов, к тому же они явно отличаются в различных языках. Подробнее с этой темой можно ознакомиться в 4-ой главе книги «Вы не знаете JS: this и Прототипы Объектов».
Когда можно использовать наследование, а когда не стоит?
Наследования не стоит использовать в качестве основной техники для повторного использования кода для сложных объектов. Его можно использовать совместно с композицией для наследования отдельных частей сложного объекта, но не для самого сложного объекта. Например, для React компонентов наследование плохо, а для частей (вроде объектных аналогов custom hooks) из которых мог быть состоять компонент-класс, наследования вполне можно использовать. Но даже так, в первую очередь стоит рассматривать разбиение на большее число составляющих или применения других техник, вместо наследования.
При возможности появления сложной иерархии наследование (более 2-х уровней, где первый уровень иерархии – родитель, а второй уровень — наследники) тоже не следует использовать наследование.
Множественное наследование и интерфейсы
При использовании множественного наследования получаются довольно запутанные иерархии классов. Поэтому во многих языках отказались от множественного наследования реализации. Но множественное наследование по-прежнему применяют при наследовании абстракций в виде интерфейсов.
Интерфейсы есть, например, в Typescript. Реализация нескольких интерфейсов в одном классе отчасти похоже на наследование, но с их использованием «наследуется» только описание свойств и сигнатура методов интерфейса. Наследование реализации не происходит.
Интерфейсы следует понимать не как наследование, а как контракт, указывающий, что данный класс реализует такой-то интерфейс. Плохо, когда один класс реализует слишком много интерфейсов. Это означает, что-либо интерфейсы слишком сильно разбиты на части, либо у объекта слишком большая ответственность.
Композиция/агрегация с использованием списка
Прототипное наследование
При прототипном наследовании уже не происходит смешивания родительского объекта и его наследника. Вместо этого наследник ссылается на родительский объект (прототип).
При отсутствии свойства (поле, метод и т.д.) в объекте, происходит поиск этого свойства в цепочке прототипов. То есть часть функционала делегируется вложенному объекту, который тоже может делегировать функционал вложенному объекту внутри себя. И так далее по цепочке. Прототип на любом уровне цепочки может быть только один.
Стоит отметить, что в JavaScript операции записи/удаления работают непосредственно с объектом. Они не используют прототип (если это обычное свойство, а не сеттер). Если в объекте нет свойства для записи, то создается новое. Подробнее об этом.
Цепочка прототипов организована как стек (Last-In-First-Out или LIFO). Какой объект добавлен в цепочку последним (если считать от итогового объекта-контейнера), к тому обращение будет первым.
Также существует вариант, когда при создании нового объекта с прототипом, создается копия прототипа. В таком случае используется больше памяти (хотя это проблема разрешаема), но зато это позволяет избежать ошибок в других объектах из-за изменения прототипа конкретного объекта.
Паттерн Декоратор и аналоги
Декоратор (wrapper/обертка) позволяет динамически добавлять объекту новую функциональность, помещая его в объект-обертку. Обычно объект оборачивается одним декоратором, но иногда используется несколько декораторов и получается своего рода цепочка декораторов.
Цепочка декораторов устроена как стек (LIFO). Какой объект добавлен в цепочку последним (если считать от итогового объекта-контейнера), к тому обращение будет первым.
Цепочка декораторов похожа на цепочку прототипов, но с другими правилами работы с цепочкой. Оборачиваемый объект и декоратор должны иметь общий интерфейс.
На схеме ниже пример использования нескольких декораторов на одном объекте:

Как в случае с прототипами, зачастую можно подменять декораторы во время выполнения. Декоратор оборачивает только один объект. Если оборачивается несколько объектов, то это уже что-то другое.
HOF (higher order function) и HOC (Higher-Order Component) — паттерны с похожей идей. Они оборачивают функцию/компонент другой функцией/компонентом для расширения функционала.
HOF — функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата. Примером HOF в JS является функция bind, которая, не меняя переданную функцию, возвращает новую функцию с привязанным к ней с помощью замыкания значением. Другим примером HOF является карринг.
HOC — чистая функция, которая возвращает другой компонент (а он уже содержит в себе произвольную логику), который внутри себя «рендерит» переданный компонент. При этом сам переданный компонент не меняется, но в него могут быть переданы props.
Также стоит упомянуть композицию функций. Это тоже своего рода обертка. С помощью этой техники создаются цепочки вложенных функций:
const funcA = сompose(funcB, funcC, funcD);
или же менее читабельный вариант:
const funcA = ()=> < funcB( funcC( funcD() ) ) ; >;
То же самое можно получить такой записью:
function funcA() < function funcB() < function funcC() < function funcD() >> >
Недостатком последнего варианта является жесткая структура функций. Нельзя поменять их очередность или заменить одну из функций без создания новой аналогичной цепочки или ее части. funcC нельзя использовать без funcD, а funcB без funcC и без funcD. В первых же двух примерах – можно. Там функции независимы друг от друга.
Итого
Прототипное наследование и использование декораторов гибче, чем подходы со смешиванием.
Часто говорят: «предпочитайте композицию наследованию». Стоит учесть, что существует множество вариантов композиции с различной эффективностью в той или иной ситуации. Только от простой замены наследования на композицию, вряд ли получиться решить проблемы без появления новых проблем. Нужно еще выбрать подходящую замену.
Когда по аналогии с иерархией наследования используется несколько уровней вложения одних объектов в другие, получается иерархия вложенных объектов. Почти то же самое, что и наследование, только с возможностью подменять объекты в иерархии. Конечно, в случае декораторов этого обычно избегают и вместо иерархии получается цепочка. В цепочке декораторов выходит так, что каждый следующий используемый декоратор помимо своих членов классов должен реализовать члены всех остальных объектов в цепочке. В итоге, по аналогии с наследованием, снова получается раздутый объект с множеством полей и методов. На схеме выше был пример такого объекта — DecoratorC.
Зачастую при использовании нескольких декораторов на одном объекте не добавляют новые поля и методы, а лишь подменяют реализацию уже существующих членов объекта. Остается другой недостаток – из-за большой вложенности довольно сложно разобраться, что же делает итоговый составной объект, т.к. для этого надо пройтись по цепочке вложенных объектов.
Как видите, по-прежнему остаются довольно серьезные проблемы. Но, есть другие решения, о которых рассказано в следующих главах.
Композиция/агрегация с использованием одноуровневых структур данных (ссылка, массив ссылок, словарь)
Под одноуровневой структурой данных я подразумеваю структуру, элементы которой не ссылаются на другие элементы.
Паттерн стратегия
Паттерны декоратор и стратегия служат для одной цели – с помощью делегирования расширить функциональность объекта. Но делают они это по разному. Хорошо описана эта разница по ссылке: «Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».»
Паттерн Cтратегия описывает разные способы произвести одно и то же действие, позволяя динамически заменять эти способы в основном объекте (контексте).
На схеме ниже пара примеров связи стратегий с основным объектом.

К похожим способам (использование ссылки) расширения функционала объекта и повторного использования кода можно отнести события в HTML элементах и директивы в Angular и Vue.
// html // vue
Entity Component (EC)
Я не знаю, как называется данный паттерн. В книге Game Programming Patterns он называется просто «Компонент», а по ссылке его называют системой компонентов/сущностей. В статье же я буду называть его Entity Component (EС), чтобы не путать с подходом, который будет описан в следующей главе.

Сначала пройдемся по определением:
- Entity (сущность) – объект-контейнер, состоящий из компонентов c данными и логикой. В React и Vue аналогом Entity является компонент. В Entity не пишут пользовательскую логику. Для пользовательской логики используются компоненты. Компоненты могут храниться в динамическом массиве или словаре.
- Component – объект со своими данными и логикой, который можно добавлять в любую Entity. В React компонентах похожим аналогом являются custom hooks. И описываемые здесь компоненты и пользовательские хуки в React служат для одной цели – расширять функционал объекта, частью которого они являются.
Обычно Entity может содержать вложенные entities, тем самым образуя дерево entities. Не думаю, что это является неотъемлемой его частью, а скорее является смешиванием нескольких подходов.
Данный паттерн похож на паттерн стратегия. Если в объекте использовать динамический массив со стратегиями, организовать их добавление, удаление и получение определенной стратегии, то это будет похоже на Entity Component. Есть еще одно серьезное отличие — контейнер не реализует интерфейс компонентов или методы для обращения к методам компонентов. Контейнер только предоставляет доступ к компонентам и хранит их. Получается составной объект, который довольно своеобразно делегирует весь свой функционал вложенным объектом, на которые он ссылается. Тем самым EC избавляет от необходимости использования сложных иерархий объектов.
Плюсы EC
- Низкий порог вхождения, т.к. в основе используется простая одноуровневая структура данных.
- легко добавлять новую функциональность и использовать код повторно.
- можно изменять составной объект (Entity) в процессе выполнения, добавляя или удаляя его составляющие (компоненты)
Минусы
- для простых проектов является ненужным усложнением из-за разбиение объекта на контейнер и компоненты
В одной из своих следующих статей я опишу применение этого подхода для React компонентов. Тем самым я покажу, как избавиться от первых двух недостатков компонентов на классах, описанных в документации React-а:
Трудно повторно использовать логику состояний между компонентами.
Сложные компоненты становятся трудными для понимания.
Этот подход используется с самого начала выхода движка Unity3D для расширения функционала элементов (объектов) дерева сцены, включая UI элементы, где вы можете получше ознакомится с данным подходом. Но в таком случае придётся потратить не мало времени на изучение движка.
Итого
Паттерн стратегия сам по себе не очень мощный, но если развить его идею, можно получить довольно эффективные техники. К такой можно отнести Entity Component.
В случае использования EC может появиться новая проблема – при большом количестве компонентов, связанных между собой в одном объекте, становиться сложно разобраться в его работе. Выходом может стать некий компонент, который контролирует взаимодействия между компонентами в одной Entity или в группе вложенных Entities. Такой подход известен как паттерн Посредник (Mediator).
Но даже “посредника“ будет недостаточно для более сложных случаев. К тому же он не является универсальным. Для каждой Entity с множеством связанных компонентов придёться реализовывать новый тип “посредника”. Есть и другой выход. EC можно комбинировать с другими подходами на основе графов и деревьев, которые будут описаны позже.
Композиция/агрегация с вынесением логики вне объекта и его составляющих
Entity Component System (ECS)
Я не работал с этим подходом, но опишу то, как я его понял.
В ECS объект разбивается на 3 типа составляющих: сущность, компонент (один или несколько), система (общая для произвольного числа объектов). Этот подход похож на EC, но объект разбивается уже на 3 типа составляющих, а компонент содержит только данные.
Определения:
- Entity – его основное назначение, это идентифицировать объект в системе. Зачастую Entity является просто числовым идентификатором, с которым сопоставляется список связанных с ним компонентов. В других вариациях Entity также может брать на себя роль контейнера для компонентов. Как и в EC подходе, в Entity нельзя писать пользовательский код, только добавлять компоненты.
- Component — объект с определенными данными для Entity. Не содержит логики.
- System — в каждой системе описывается логика. Каждая система перебирает список компонентов определенных типов или компоненты определенных entities и выполняет логику с использованием данных в компонентах. Может извлекать компоненты из entities. Результатом выполнения системы будет обновление данных в компонентах. В некоторых случаях системы могут быть обычными функциями, получающими на вход нужные данные.
Также может быть некий объект-менеджер (или несколько менеджеров), который хранит все системы и объекты, а также периодически запускает все системы. Здесь уже реализация произвольная.
Пример простой ECS: Допустим есть несколько объектов, у которых есть идентификаторы. Несколько из этих объектов ссылаются на компоненты Position, в которых хранятся текущие координаты x, y, и на компонент Speed, который содержит текущую скорость. Есть система Movement, которая перебирает объекты, извлекает из них компоненты Position и Speed, вычисляет новую позицию и сохраняет новые значения x, y в компонент Position.
Как я уже говорил, реализации ECS могут отличаться. Например:
b) компоненты содержится в массивах/словарях. Entity является просто идентификатором, по которому определяется компонент, связанный с сущностью. Раз, два и три.
На схеме изображен первый вариант, когда entity ссылается на свои компоненты.

Плюсы ECS
- Слабое сцепление составляющих объекта, поэтому легко добавлять новую функциональность комбинирую по-разному составляющие.
- Проще тестировать, т.к. нужно тестировать только системы. Компоненты и сущности тестировать не нужно.
- Легко выполнять многопоточно.
- Более эффективное использование памяти, кэша и, следовательно, большая производительность.
- Легко реализовать сохранение всего приложения, т.к. данные отделены от функционала.
Минусы ECS
- Высокая сложность, не стандартный подход.
- для простых проектов является ненужным усложнением.
Так как я занимаюсь фронтенд разработкой, а она по большей части относится к разработки UI, то упомяну, что ECS используется в игре World of Tanks Blitz для разработки UI.
Итого
ECS является хорошей альтернативой созданию сложных иерархий наследования. В ECS можно создать иерархии наследования для компонентов или систем, но вряд ли от этого почувствуется выгода. Скорее, быстро почувствуются проблемы от таких попыток.
Как и в аналогичном случае с EС, системы в данном подходе можно комбинировать с подходами на основе графов и деревьев. В таком случае логика по-прежнему будет реализована в системах, а хранение данных в компонентах. Но, я не знаю, эффективно ли совмещение этих подходов на практике. В первую очередь нужно стремиться реализовывать системы попроще, а уже потом рассматривать комбинацию с другими подходами.
Композиция/агрегация с использованием графов
К данному способу повторного использования кода я отнес паттерн «машина состояний» (State machine/Finite state machine/конечный автомат).
Аналогом машины состояний простой является switch:
switсh (condition)
Недостатком является то, что он может сильно разрастить, а также у разработчика может не быть возможности добавить новые состояния в систему.
Для сложных случаев каждое состояние с логикой можно вынести в отдельный объект и переключаться между ними.
В более сложных случаях можно разделить на большее число составляющих: состояние, действие, переход, условие перехода, событие.
Также существуют иерархические машины состояний, где состояние может содержать вложенный граф состояний.
Я уже описывал паттерн “Машина состояний” и его составляющие, и вкратце писал о иерархической машине состояний в статье «Приемы при проектировании архитектуры игр» в главе «машина состояний».
Преимущества использования машины состояний:
Хорошо описано по этой ссылке.
Добавлю, что становится легче предусмотреть, обработать и протестировать все возможные случаи работы контекста (подсистемы), т.к. видны все его состояния и переходы. Особенно, если состояния являются просто объектами с данными и отделены от остальной логики и отображения.
Где при разработке UI можно использовать машину состояний?
Например, для логики сложной формы, у которой поведение и набор полей немного отличается в зависимости от роли пользователя и других параметров. Каждый объект-состояние может описывать состояние всех компонентов формы (активный, видимый, фиксированный текст элемента в таком-то состоянии и т.д.), отображение которых может отличаться в зависимости от роли пользователя и других параметров. Компоненты формы получают часть объекта-состояния, которая им нужна для своего отображения.
Другие примеры использования в UI:
state-machines-in-user-interfaces
xstate (библиотека для JS, которую можно использовать c React, Vue, Svelte)
react-automata (библиотека для React)
Машины состояний и разработка веб-приложений
Подходит ли State machine в качестве основного механизма повторного использования кода и разбиения сложных объектов на составные части?
Иногда он так и используется. Но, он мне кажется сложноватым и не всегда подходящим для использования в качестве основного. Зато он точно хорош в качестве дополнительного, когда нужно организовать взаимодействия между несколькими объектами или частями составного объекта.
Стоит учитывать, что граф может получиться слишком сложным. Если у вас обычный код получается запутанным, то и граф скорее всего получится запутанным. В сложном графе нужно стремиться уменьшать количество связей, группировать состояния.
Композиция/агрегация с использованием деревьев
Паттерн composite и другие древовидные структуры
Деревья часто встречается в разработке. Например, объекты в JavaScript могут содержать вложенные объекты, а те также могут содержать другие вложенные объекты, тем самым образую дерево. XML, JSON, HTML, DOM-дерево, паттерн Комповщик (Composite) – все это примеры древовидной композиции.
Дерево является графом, в котором между любыми 2-мя его узлами может быть только один путь. Благодаря этому, в нем гораздо проще разобраться, чем в графе получаемом с помощью машины состояний.
Behaviour tree
Интересным вариантом композиции является Behaviour tree (дерево поведения). Это организация логики программы (обычно AI) или ее частей в виде дерева.
В дереве поведения в качестве узлов выступают управляющие блоки — условие, цикл, последовательность действий, параллельное выполнение действий и блоки действий. В теории, могут быть реализованы и другие управляющие блоки, вроде switch case, асинхронного блока и других аналогов управляющих конструкций языка, но я не встречал подобного. Код действий для деревьев поведения пишет разработчик. Обычно решения для деревьев поведений содержат визуальный редактор для их создания и отладки.
Я уже описывал деревья поведений в прошлом в этой статье.
Более наглядный пример схемы готового дерева из плагина banana-tree

Дерево поведения можно рассматривать как аналог обычной функции с вложенными функциями. Я думаю, понятно, что схему выше можно перевести в функцию с вложенными функциями, условиями и циклами.
Если в функции написать слишком много кода или же в ней будет слишком много вложенных условий, то она будет нечитабельна и с ней будет тяжело работать. Аналогично и с деревьями поведения. Их следует делать как можно проще и с меньшей вложенностью блоков с циклами и условиями.
Деревья поведения позволяют создавать сложную логику с помощью комбинации более простых действий. Сложная логика тоже может быть оформлена в виде узла дерева (в виде действия или поддерева). Также деревья поведения предоставляют единый механизм для разработки в таком стиле. Этот подход мотивирует выносить функционал в отдельные настраиваемые объекты, зачастую предоставляет наглядное средство для отладки, упорядочивает и уменьшает связи между объектами, позволяет избежать жесткой зависимости составляющих.
Для простых случаев, как обычно, этот подход будет ненужным усложнением.
Смешанные подходы
Для более эффективной организации кода можно комбинировать некоторые подходы. Например, в качестве узлов машины состояний можно использовать деревья поведения.
Довольно многое можно отнести к смешанным подходам. Entity Component в Unity3D реализован так, что позволяет хранить не только компоненты, но и вложенные сущности. А для пользовательских компонентов можно использовать наследование в простых случаях, либо объединить компоненты с более продвинутыми техниками (паттерн mediator, машина состояний, дерево поведения и другие).
Примером смешивания подходов является анимационная система Mecanim в Unity3D, которая использует иерархическую машину состояний с деревьями смешивания (blend tree) для анимаций. Это относится не совсем к коду, но является хорошим примером комбинации подходов.
К смешанным подходам я отнес React компоненты с хуками, т.к. там довольно специфичный случай. С точки зрения разработчика, для повторного использования кода в компоненте используется дерево функций (хуков). С точки зрения организации данных в компоненте – компонент хранит список с данными для хуков. К тому же связь между хуками и компонентом устанавливается во время выполнения. Т.к. я разрабатываю на React, то решил включить частичное описание внутреннего устройство компонента с хуками в статью.
React hooks
Эта часть статьи для разработчиков, знакомых c React. Остальным многое в ней будет не понятно.
Особенность функциональных компонентов в React-е в том, что разработчик не указывает сам связь между компонентом и его хуками. Отсюда возникает вопрос, как React определяет к какому компоненту применить такой-то хук?
Как я понял, хуки при вызове добавляют к текущему обрабатываемому компоненту (точнее к fiber-ноде) свое состояние – объект, в котором могут быть указаны переданные сallback-и (в случае useEffect, useCallback), массив зависимостей, значения (в случае useState) и прочие данные (в случае useMemo, useRef, …).
А вызываются хуки при обходе дерева компонентов, т.е. когда вызывается функция-компонент. React-у известно, какой компонент он обходит в данный момент, поэтому при вызове функции-хука в компоненте, состояние хука добавляется (или обновляется при повторных вызовах) в очередь состояний хуков fiber-ноды. Fiber-нода – это внутреннее представление компонента.
Стоит отметить, что дерево fiber элементов не совсем соответствует структуре дерева компонентов. У Fiber-ноды только одна дочерняя нода, на которую указывает ссылка child. Вместо ссылки на вторую ноду, первая нода ссылается на вторую (соседнюю) с помощью ссылки sibling. К тому же, все дочерние ноды ссылаются на родительскую ноду с помощью ссылки return.
Также для оптимизации вызова эффектов (обновление DOM, другие сайд-эффекты) в fiber-нодах используются 2 ссылки (firstEffect, nextEffect), указывающие на первую fiber-ноду с эффектом и следующую ноду, у которой есть эффект. Таким образом, получается список нод с эффектами. Ноды без эффектов в нем отсутствуют. Подробнее об этом можно почитать по ссылкам в конце главы.
Вернемся к хукам. Структура сложного функционального компонента с несколькими вложенными custom hooks для разработчика выглядит как дерево функций. Но React хранит в памяти хуки компонента не как дерево, а как очередь. На схеме ниже изображен компонент с вложенными хукам, а под ним fiber-нода с очередью состояний этих же хуков.

На схеме в fiber-ноде отображены также поля, которые участвует в создании различных структур для оптимизации рендеринга. Они не будут рассмотрены в рамках статьи.
Чтобы просмотреть содержимое fiber-ноды, достаточно воспользоваться console.log и вставить туда JSX код, который возвращает компонент:
function MyComponent() < const jsxContent = (); console.log(jsxContent); return jsxContent; >
Корневую fiber-ноду можно просмотреть следующим образом:
const rootElement = document.getElementById('root'); ReactDOM.render(, rootElement); console.log(rootElement._reactRootContainer._internalRoot);
Пример компонента с хуками и отображение его fiber-ноды
import < useState, useContext, useEffect,useMemo, useCallback, useRef, createContext >from 'react'; import ReactDOM from 'react-dom'; const ContextExample = createContext(''); function ChildComponent() < useState('childComponentValue'); return ; > function useMyHook() < return useState('valueB'); >function ParentComponent() < const [valueA, setValueA] = useState('valueA'); useEffect(function myEffect() <>, [valueA]); useMemo(() => 'memoized ' + valueA, [valueA]); useCallback(function myCallback() <>, [valueA]); useRef('refValue'); useContext(ContextExample); useMyHook(); const jsxContent = ( ); console.log('component under the hood: ', jsxContent); return jsxContent; > const rootElement = document.getElementById('root'); ReactDOM.render( > , rootElement, );

Также есть интересная наработка: react-fiber-traverse
С более подробным описанием работы внутренних механизмов React на русском языке можно ознакомиться по ссылкам:
- Как Fiber в React использует связанный список для обхода дерева компонентов
- Fiber изнутри: подробный обзор нового алгоритма согласования в React
- Как происходит обновление свойств и состояния в React — подробное объяснение
- За кулисами системы React hooks
- Видео: Под капотом React hooks
У подхода с хуками на данный момент есть недостаток — фиксированное дерево функций (хуков) в компонентах. При стандартном использовании хуков, нельзя изменить логику уже написанного компонента или хуков, состоящих из других хуков. К тому же это мешает тестированию хуков по отдельности. В какой-то степени можно улучшить ситуацию композицией (compose) хуков. Например, существует такое решение. Или можно сделать, чтобы хуки задавались через props, по аналогии с директивами в Angular и Vue. Пример. Возможно существуют еще какие-нибудь решения.
Линейность кода и составляющих сложного объекта
Известно, что множество вложенные условий, callback-ов затрудняют читаемость кода:
Замена вложенных условных операторов граничным оператором
Качество кода (в статье упоминается линейный код)
Как писать чистый код (в статье упоминается линейность кода).
Я думаю, что наследование, большие цепочки и иерархии вложенных объектов могут привести к аналогичной ситуации, но для составляющих сложного объекта. Даже если объект расположен линейно в коде, внутри он может быть устроен так, что необходимо пройтись по множеству родительских классов или вложенных объектов, чтобы разобраться в его функционале. Я уже писал ранее про деревья поведения, что в них следует избегать большой вложенности. Так и в других случаях.
- Веб-разработка
- JavaScript
- Проектирование и рефакторинг
- ООП
- ReactJS
Предварительная композиция, вложение и предварительный рендеринг
Сведения о создании предварительных и вложенных композиций
Если требуется сгруппировать несколько слоев, которые уже имеются в композиции, можно выполнить предварительную композицию этих слоев. При предварительной композиции слои помещаются в новой композиции, которая заменяет слои в исходной композиции. Новая вложенная композиция становится источником одного слоя в исходной композиции. Новая композиция отображается в панели проекта и для нее имеется функция рендеринга, а также ее можно использовать в любой другой композиции. Можно вкладывать композиции друг в друга, аналогично добавлению элемента видеоряда к композиции. Предварительную композицию одного слоя можно использовать для применения свойств трансформирования к слою и изменения порядка рендеринга элементов композиции.
Вложение представляет собой включение одной композицию в другую. Вложенная композиция отображается как слой в той композиции, где она содержится.
Вложенная композиция иногда называется предварительной композицией, а иногда для нее используются сокращения прекомп. или предв. комп.. Когда предварительная композиция используется как элемент исходного видеоряда для слоя, этот слой называется слоем предварительной композиции.
Можно сказать, что во время рендеринга данные изображения и другая информация передается из каждой вложенной композиции в композицию, содержащую такой слой. По этой причине, вложенные композиции иногда считаются расположенными вверх по потоку по отношению к композициям, их содержащим, а включающие композиции расположены вниз по потоку по отношению ко вложенным композициям, которые они содержат. Набор композиций, соединенных при помощи вложения, называется сетью композиций. Можно перемещаться по сети композиций с помощью навигатора композиции и ее мини-представления. (См. раздел Открытие вложенных композиций и перемещение между ними.)
Предварительные композиции в After Effects аналогичны смарт-объектам в Adobe Photoshop.
Использование предварительных и вложенных композиций
Предварительные композиции и вложения полезны для управления сложными композициями и их организацией. Предварительные композиции и вложения позволяют выполнять следующие операции.
- Применение сложных изменений ко всей композиции. Можно создать композицию, которая содержит несколько слоев, вложить данную композицию в общую композицию, анимировать и применять эффекты ко вложенной композиции так, что все слои будут изменяться идентичным образом на протяжении одного и того же периода времени.
- Повторное использование всех создаваемых ресурсов. Можно создавать анимацию в собственной композиции, а затем перетаскивать эту композицию в другие композиции неограниченное число раз, если это необходимо.
- Одношаговое обновление. При внесении изменений во вложенную композицию данные изменения влияют на каждую композицию, в которой она используется, аналогично изменениям, которые, после внесения в исходный видеоряд, повлияют на каждую композицию, в которой они используются.
- Изменение порядка рендеринга слоя по умолчанию. Можно задать в After Effects рендеринг трансформации (например, поворота) перед рендерингом эффектов так, чтобы эффект применялся к повернутому видеоряду.
- Добавление другого набора свойств преобразования в слой. Слой, который представляет композицию, имеет свои собственные свойства, в дополнение к свойствам слоев, которые он содержит. Это позволяет применить дополнительный набор преобразований к слою или набору слоев.
Установки и настройки композиции, которые влияют на вложенные композиции
Поскольку предварительная композиции представляет собой слой, можно управлять его поведением с помощью переключателей слоев и композиции на панели «Таймлайн». Можно выбрать, будут ли изменения, распространяются ли изменения переключателей вышестоящей композиции на вложенную. Чтобы изменения переключателей не влияли на вложенные композиции, выберите «Правка» > «Установки» > «Общие» (Windows) или «After Effects» > «Установки» > «Общие» (Mac OS), а затем снимите флажок «Изменять вложенные композиции».
На вкладке «Дополнительно» диалогового окна композиции («Композиции» > «Настройки композиции»), выберите «Сохранить разрешение при выполнении вложения» или «Сохранить частоту кадров при выполнении вложения или в очереди рендеринга», чтобы композиция сохраняла собственные разрешение или частоту кадров и не наследовала эти параметры из вышестоящей композиции. Например, если вы специально использовали низкую частоту кадров в композиции для создания ручной анимации с эффектом дрожания, необходимо сохранить частоту кадров для этой композиции при ее вложении. Аналогично, результаты ротоскопии могут выглядеть некорректно после изменения частоты кадра или разрешения. Этот параметр следует использовать вместо этого эффекта «Время постеризации», который менее эффективен.
Примечание.
На своем веб-сайте redefinery Джефф Алмасол (Jeff Almasol) предлагает сценарий, который делает работу с функциями «Сохранить разрешение при выполнении вложения» или «Сохранить частоту кадров при выполнении вложения или в очереди рендеринга» более удобной.
Изменение текущего времени в одной панели обновляет текущее в остальных панелях, связанных с данной композицией. По умолчанию, текущее время также обновляется для всех композиций, связанных с данной при помощи встраивания. Чтобы в композициях, связанных методом вложения, не обновлялось текущее время при изменении текущего времени в одной композиции, снимите флажок «Синхронизировать время всех связанных элементов» с помощью команды «Правка» > «Установки» > «Общие» (Windows) или «After Effects» > «Установки» > «Общие» (Mac OS).
Ресурсы в Интернете, посвященные предварительной композиции и вложениям
Крис и Триш Мейер (Trish и Chris Meyer) предлагают полезные советы по настройке иерархии композиций таким образом, чтобы упростить внесение изменений в проект в этой статье на веб-сайте ProVideo Coalition.
Посетите эту страницу веб-сайтаaescripts размещен сценарий Un-Precompose, который удаляет слои из предварительной композиции.слой предварительной композиции,.
Посетите эту страницу веб-сайтаaescripts представлен сценарий Zorro-The Layer Tagger, позволяющий сгруппировать слои в композиции с использованием тегов, без применения предварительной композиции.
Предварительная композиция слоев
При предварительной композиции слои помещаются в новую композицию (иногда называемую предварительной композицией), которая заменяет слои в исходной композиции. Предварительную композицию одного слоя можно использовать для применения свойств трансформирования к слою и изменения порядка рендеринга элементов композиции.
Выделите слои на панели шкалы времени и выберите «Слой» > «Предварительная композиция» или нажмите CTRL+SHIFT+C (Windows) или COMMAND+SHIFT+C (Mac OS).
Выберите один из следующих вариантов.
Оставить все атрибуты в
Сохраняет все свойства и ключевые кадры слоя, прошедшего предварительную композицию, в изначальной композиции, примененных к новому слою, представляющего предварительную композицию. Размер кадра новой композиции совпадает с размером выделенного слоя. Этот параметр недоступен, если выбрано более одного слоя, текстовый слой или слой-фигура.
Переместить все атрибуты в новую композицию
Перемещает свойства и ключевые кадры слоев предварительной композиции на один уровень дальше от корневой композиции в иерархии композиции. При использовании этого параметра изменения, примененные к свойствам слоев, сохраняются в отдельных слоях в пределах предварительной композиции. Размер кадра новой композиции совпадает с размером кадра исходной композиции.
Эффекты могут включать маски и эффекты других слоев
Эффекты, которые используют слои в качестве входных данных, например «Настроить подложку» и «Карта смещения», направлены на маски и эффекты слоя входных данных. Эти слои можно использовать без предварительной композиции, чтобы на них мог ссылаться эффект.
Элемент управления похож на функцию меню «Вид» внизу панели просмотра «Слой», которая позволяет выполнять рендеринг слоя из различных положений в порядке рендеринга: из его источника, из его масок или из его отдельных эффектов.
Для эффектов со свойствами слоя откройте меню «Параметр входа» справа от выбора слоя и выберите целевой слой входных данных, например:
- Источник. направлен только на источник слоя. Маски и эффекты игнорируются.
- Маски. направлен на слой, после которого применяются маски. Эффекты игнорируются.
- Эффекты и маски. направлен на слой, после которого применяются маски и эффекты.
Открытие вложенных композиций и перемещение между ними
Вложенные композиции иногда считаются расположенными вверх по потоку по отношению к композициям, их содержащим, а включающие композиции расположены вниз по потоку по отношению ко вложенным композициям, которые они содержат. Корневая композиция расположена в потоке ниже всего; композиция с максимальной глубиной вложения — выше всего. Путь потока композиции представляет собой цепочку композиций, которые связаны друг с другом путем включения или вложения. Сеть композиций — весь набор композиций, которые связаны друг с другом путем вложения.
Вложенную композицию (предварительную композицию) можно открыть в After Effects несколькими способами.
- Двойным щелчком записи композиции на панели «Проект».
- Двойным щелчком слоя предварительной композиции на панели «Временная шкала». Двойной щелчок при нажатой клавише ALT (Windows) или OPTION (Mac OS) открывает слой предварительной композиции как слой на панели слоев.
Примечание.
При двойном щелчке по слою предварительной композиции при активном инструменте рисования или кисти для ротоскопии данный слой открывается на панели «Слой».
- Чтобы открыть последнюю активную композицию в той же сети композиций как текущую активную композицию, нажмите SHIFT+ESC.
- Используйте навигатор композиции.
- Используйте графическое мини-представление композиции.
Навигатор композиции
Навигатор композиции — строка вдоль верхней границы панели «Композиция», в которой отображается композиция, активная в данном средстве просмотра, по отношению к другим композициям в той же сети композиций. Отображаются последние активные композиции в пути потока композиции, активной в настоящее время.

A. Активная (текущая) композиция B. Стрелка для открытия графического мини-представления композиции C. Кнопка меню панели D. Многоточие
Стрелки между наименованиями композиций указывают направление, в котором передается пиксельная информация в рамках данного пути потока. По умолчанию, композиции, расположенные вниз по потоку, показаны в навигаторе композиций слева, а расположенные вверх по потоку — справа. Это значение по умолчанию задается параметром «Переместить справа налево» в меню панели «Композиция». Для отображения композиций в другом порядке, выберите «Переместить слева направо». Данная настройка является глобальной; она применяется ко всем композициям и к графическому мини-представлению композиции.
Имена композиций, расположенных вниз по потоку, затемнены, чтобы подчеркнуть, что их содержимое не используется или не отображается в активной композиции.
![]()
- Чтобы отобразить или скрыть строку навигатора композиции, выберите «Показать навигатор композиции» в меню панели «Композиция».
- Для активации любой композиции, отображаемую на панели навигатора композиции, щелкните наименование композиции.
- Если путь потока слишком длинный для отображения на панели «Композиция», то слева или справа от края панели навигатора композиции появится кнопка многоточия . Чтобы временно отобразить весь путь потока, нажмите кнопку многоточия.
Примечание.
Для прокрутки по длинному пути потока, поместите указатель над кнопкой композиции в навигаторе композиции и вращайте колесо прокрутки мыши.
Графическое мини-представление композиции
Графическое мини-представление композиции является промежуточным элементом управления, который можно использовать для быстрой навигации в сети композиций. При открытии графического мини-представления композиции в нем отображаются композиции, расположенные вверх и вниз по потоку по отношению к выбранной.
Цвета на графическом мини-представлении композиции основаны на цветах меток, присвоенных композициям в панели проектов. Если композиция используется несколько раз в одной композиции, несколько экземпляров вложенной композиции отображаются как одна запись с числом, указывающим количество экземпляров, в скобках.
Чтобы открыть графическое мини-представление композиции, выполните одно из предложенных ниже действий.

A. Индикатор того, что композиция не переходит в другие композиции B. Направление потока C. Активная (текущая) композиция
- Нажмите клавишу «Tab», когда активна панель «Композиция», «Слой» или «Таймлайн».
![]()
- Щелкните стрелку справа от имени композиции в панели навигатора композиции.
- Выберите графическое мини-представление композиции в меню «Композиция», меню панели «Композиция» или меню панели «Временная шкала».
- Нажмите кнопку «Графическое мини-представление композиции» в верхней части панели «Временная шкала».
![]()
Как и в случае с навигатором композиции, можно отображать направление потока слева направо или справа налево. Стрелки указывают направление потока. Если рядом с композицией расположен значок , а не стрелка, то либо в нее не переходит ни одна композиция, либо она не переходит ни в одну композицию.
Композиции, расположенные вверх по потоку в графическом мини-представлении композиции, сортируются сверху вниз либо в алфавитном порядке, либо в порядке слоев. Для переключения между порядками сортировки, нажмите клавишу S при открытом мини-представлении композиции. При сортировке по слоям, композиция, используемая несколько раз, сортируется с учетом ее самого верхнего экземпляра в порядке размещения. Композиции, расположенные вниз по потоку, всегда будут отображаться в алфавитном порядке.
![]()
Для навигации и выбора композиций в графическом мини-представлении композиции используйте клавиши со стрелками или щелкните стрелку или кнопки с обеих сторон композиции. Для активации выбранной композиции нажмите клавишу пробела или ВВОД (Windows) или клавишу RETURN (Mac OS). Чтобы закрыть графическое мини-представление композиции, не предпринимая никаких действий, нажмите клавишу ESC, затем кратко нажмите SHIFT или щелкните за пределами графического мини-представления композиции.
Рич Янг (Rich Young) предлагает дополнительную информацию о панели графического представления и графическом мини-представлении композиции на веб-сайте After Effects Portal.
Предварительный рендеринг вложенной композиции
Рендеринг сложной вложенной композиции как для предпросмотра, так и для конечного вывода может занять немало времени. При наличии вложенных композиций, работа с которыми в дальнейшем не предполагается, можно сэкономить временные затраты на рендеринг за счет предварительного рендеринга вложенной композиции в фильм и замены композиции получившимся фильмом. Можно по-прежнему изменять исходную вложенную композицию, поскольку она сохраняется на панели «Проект». Если исходная вложенная композиция существенно изменится, выполните операцию рендеринга для нее повторно.
Предварительный рендеринг вложенной композиции особенно полезен, если она используется в проекте несколько раз.
Примечание.
Применяйте параметры окончательного вывода при предварительном рендеринге вложенной композиции.
Выберите композицию на панели «Проект» или «Композиция».
Выберите «Композиция» > «Предварительный рендеринг».
Команда предварительного рендеринга добавляет композицию в очередь рендеринга и определяет действие «Импорт и замена использования» для замены композиции на фильм, прошедший рендеринг.
В панели «Очередь рендеринга» настройте необходимые параметры, затем нажмите кнопку «Рендеринг» для запуска рендеринга композиции.
Примечание.
Альтернатива замены композиции на фильм — использование прошедшего рендеринг фильма в качестве прокси для вложенной композиции.
Порядок рендеринга и свертывания трансформаций
Композиция состоит из слоев, расположенных друг поверх друга на панели «Таймлайн». При рендеринге композиции как для предпросмотра, так и для окончательного вывода сначала выполняется рендеринг нижнего слоя. В пределах каждого растрового (не векторного) слоя элементы применяются в следующем порядке: маски, эффекты, трансформации и стили слоя. Для векторных слоев с непрерывной растеризацией порядок рендеринга по умолчанию включает маски, трансформации, а затем эффекты.
Трансформации — изменения в свойствах, сгруппированных в категории «Трансформации»на панели «Временная шкала», включая опорную точку, положение, масштаб, поворот и непрозрачность. То, что отображается на панели «Слой», представляет собой результат рендеринга до выполнения трансформации.
Примечание.
Для дополнительного контроля времени выполнения трансформаций, можно применить эффект «Преобразовать» и изменить его порядок по отношению к другим эффектам.
В группе эффектов или масок элементы обрабатываются сверху вниз. Например, если вы применяете эффект «Окружность», а затем эффект «Увеличение», то круг будет увеличен. Однако при перетаскивании эффекта «Увеличение» над (до) эффектом «Окружность» на панели «Элементы управления эффектами» или «Временная шкала» круг отрисовывается после увеличения, следовательно, не увеличивается.
После рендеринга слоя начинается рендеринг следующего слоя. Слой, рендеринг которого завершен, может использоваться в качестве исходных данных для рендеринга расположенного выше слоя, например для определения результата режима смешения.
Если композиция содержит другие вложенные композиции, то рендеринг вложенных композиций выполняется перед другими слоями в содержащей композиции.
Примечание.
Некоторые эффекты игнорируют маски слоя, к которому они были применены. Чтобы применить такой эффект на слое, где использована маска, выполните предварительную композицию слоя с наложенной маской, а затем примените эффект к данному слою. (См. раздел Сведения о предварительной и вложенной композициях.)
Свертывание трансформаций
![]()
Если переключатель «Свернуть трансформации» выбран для вложенной композиции, то трансформации для вложенных композиций не выполняются, пока не будет закончен рендеринг масок и эффектов вышестоящих композиций. Такой порядок рендеринга позволяет объединить — или свернуть вложенные и вышестоящие композиции и выполнить их обработку. Тот же принцип применим к векторным слоям, для которых не выполняется постоянная растеризация.
Примечание.
Вместо переключателя «Свернуть трансформации» у векторных слоев есть переключатель «Непрерывная растеризация». Векторные слои содержат в качестве исходного видеоряда слои-фигуры, текстовые слои и слои с векторными графическими файлами. Текстовые слои и слои форм всегда непрерывно растрируются.
Свертывание трансформаций, например, может сохранить разрешение, когда слой во вложенной композиции уменьшается в два раза, а сама вложенная композиция увеличивается в два раза в вышестоящей композиции. В этом случае вместо выполнения двух трансформаций и потери данных изображений в процессе можно выполнить одну трансформацию с нулевым эффектом, так как трансформации по отдельности отменяют друг друга.
Если трансформации не сворачиваются, то для вложенной композиции, содержащей 3D-слои выполняется рендеринг в 2D-изображение с 3D-сортировкой с использованием критериев композиции по умолчанию. Такой рендеринг предотвращает пересечение вложенной композиции с 3D-слоями, при котором она будет отбрасывать тени на 3D-слои, а тени от 3D-слоев в вышестоящей композиции будут падать на них. Вложенная композиция также не контролируется камерами и источниками света вышестоящих композиций.
Если трансформации сворачиваются, то 3D-свойства слоев во вложенной композиции будут открыты для вышестоящей композиции. Таким образом, вложенная композиция может пересекаться с 3D-слоями, отбрасывать тени на них, а тени 3D-слоев вышестоящей композиции могут падать на нее. Камера и источники света вышестоящей композиции также могут управлять вложенной композицией.
По сути, свернутая трансформация для вложенной композиции предписывает After Effects не сглаживать и не кадрировать слои в предварительной композиции. Поскольку корректирующий слой работает с комбинацией всех слоев под ним в одной композиции, корректирующий слой во вложенной композиции со свернутыми трансформациями приведет к сглаживанию и кадрированию слоев, чего обычно можно избежать при свертывании трансформаций.
При применении закрытой маски (в любом режиме, кроме «Нет»), стиля слоя или эффекта ко вложенной композиции со свернутыми трансформациями сначала выполняется рендеринг слоев во вложенной композиции, затем применяются маски и эффекты, а затем результат размещается в основной композиции. Этот порядок рендеринга означает, что режимы смешения вложенных слоев не применяются к любому низлежащему слою в основной композиции, 3D-слои выше и ниже свернутого слоя не могут пересекаться с ним отбрасывать на него тени, а свернутый слой не может отбрасывать свои тени на них.
Ресурсы в Интернете
Крис и Триш Мейер (Chris и Trish Meyer) рассказывают о свертывании трансформаций и непрерывной растеризации в этой статье на веб-сайте ProVideo Coalition.