Что такое компоновщик в программировании
Паттерн Компоновщик (Composite) объединяет группы объектов в древовидную структуру по принципу «часть-целое и позволяет клиенту одинаково работать как с отдельными объектами, так и с группой объектов.
Образно реализацию паттерна можно представить в виде меню, которое имеет различные пункты. Эти пункты могут содержать подменю, в которых, в свою очередь, также имеются пункты. То есть пункт меню служит с одной стороны частью меню, а с другой стороны еще одним меню. В итоге мы однообразно можем работать как с пунктом меню, так и со всем меню в целом.
Когда использовать компоновщик?
- Когда объекты должны быть реализованы в виде иерархической древовидной структуры
- Когда клиенты единообразно должны управлять как целыми объектами, так и их составными частями. То есть целое и его части должны реализовать один и тот же интерфейс
С помощью UML шаблон компоновщик можно представить следующим образом:
Формальное определение паттерна на C# могло бы выглядеть так:
class Client < public void Main() < Component root = new Composite("Root"); Component leaf = new Leaf("Leaf"); Composite subtree = new Composite("Subtree"); root.Add(leaf); root.Add(subtree); root.Display(); >> abstract class Component < protected string name; public Component(string name) < this.name = name; >public abstract void Display(); public abstract void Add(Component c); public abstract void Remove(Component c); > class Composite : Component < Listchildren = new List(); public Composite(string name) : base(name) <> public override void Add(Component component) < children.Add(component); >public override void Remove(Component component) < children.Remove(component); >public override void Display() < Console.WriteLine(name); foreach (Component component in children) < component.Display(); >> > class Leaf : Component < public Leaf(string name) : base(name) <>public override void Display() < Console.WriteLine(name); >public override void Add(Component component) < throw new NotImplementedException(); >public override void Remove(Component component) < throw new NotImplementedException(); >>
Участники
- Component : определяет интерфейс для всех компонентов в древовидной структуре
- Composite : представляет компонент, который может содержать другие компоненты и реализует механизм для их добавления и удаления
- Leaf : представляет отдельный компонент, который не может содержать другие компоненты
- Client : клиент, который использует компоненты
Рассмотрим простейший пример. Допустим, нам надо создать объект файловой системы. Файловую систему составляют папки и файлы. Каждая папка также может включать в себя папки и файлы. То есть получается древовидная иерархическая структура, где с вложенными папками нам надо работать также, как и с папками, которые их содержат. Для реализации данной задачи и воспользуемся паттерном Компоновщик:
class Program < static void Main(string[] args) < Component fileSystem = new Directory("Файловая система"); // определяем новый диск Component diskC = new Directory("Диск С"); // новые файлы Component pngFile = new File("12345.png"); Component docxFile = new File("Document.docx"); // добавляем файлы на диск С diskC.Add(pngFile); diskC.Add(docxFile); // добавляем диск С в файловую систему fileSystem.Add(diskC); // выводим все данные fileSystem.Print(); Console.WriteLine(); // удаляем с диска С файл diskC.Remove(pngFile); // создаем новую папку Component docsFolder = new Directory("Мои Документы"); // добавляем в нее файлы Component txtFile = new File("readme.txt"); Component csFile = new File("Program.cs"); docsFolder.Add(txtFile); docsFolder.Add(csFile); diskC.Add(docsFolder); fileSystem.Print(); Console.Read(); >> abstract class Component < protected string name; public Component(string name) < this.name = name; >public virtual void Add(Component component)<> public virtual void Remove(Component component) < >public virtual void Print() < Console.WriteLine(name); >> class Directory :Component < private Listcomponents = new List(); public Directory(string name) : base(name) < >public override void Add(Component component) < components.Add(component); >public override void Remove(Component component) < components.Remove(component); >public override void Print() < Console.WriteLine("Узел " + name); Console.WriteLine("Подузлы:"); for(int i=0; i> > class File : Component < public File(string name) : base(name) <>>
В итоге подобная система обладает неплохой гибкостью: если мы захотим добавить новый вид компонентов, нам достаточно унаследовать новый класс от Component.
И также применяя компоновщик, мы легко можем обойти все узлы древовидной структуры.
0.5 – Введение в компилятор, компоновщик (линкер) и библиотеки
Чтобы скомпилировать программу на C++, мы используем компилятор C++, который последовательно просматривает каждый файл исходного кода ( .cpp ) в вашей программе и выполняет две важные задачи:
Сначала он проверяет ваш код, чтобы убедиться, что он соответствует правилам языка C++. В противном случае компилятор выдаст вам ошибку (и номер соответствующей строки), чтобы помочь точно определить, что нужно исправить. Процесс компиляции будет прерван, пока ошибка не будет исправлена.
Во-вторых, он переводит исходный код C++ в файл машинного кода, называемый объектным файлом. Объектные файлы обычно имеют имена name.o или name.obj , где name совпадает с именем файла .cpp , из которого он был создан.
Если бы в вашей программе было бы 3 файла .cpp , компилятор сгенерировал бы 3 объектных файла:
Компиляторы C++ доступны для многих операционных систем. Мы скоро обсудим установку компилятора, поэтому сейчас нет необходимости останавливаться на этом.
Шаг 5. Компоновка (линковка) объектных файлов и библиотек
После того, как компилятор создал один или несколько объектных файлов, включается другая программа, называемая компоновщиком (линкером). Работа компоновщика состоит из трех частей:
Во-первых, взять все объектные файлы, сгенерированные компилятором, и объединить их в единую исполняемую программу.
Во-вторых, помимо возможности связывать объектные файлы, компоновщик (линкер) также может связывать файлы библиотек. Файл библиотеки – это набор предварительно скомпилированного кода, который был «упакован» для повторного использования в других программах.
Ядро языка C++ на самом деле довольно небольшое и лаконичное (и вы узнаете многое о нем в последующих статьях). Однако C++ также поставляется с обширной библиотекой, называемой стандартной библиотекой C++ (обычно сокращенно «стандартная библиотека», или STL), которая предоставляет дополнительные функции, которые вы можете использовать в своих программах. Одна из наиболее часто используемых частей стандартной библиотеки C++ – это библиотека iostream , которая содержит функции для печати текста на мониторе и получения от пользователя ввода с клавиатуры. Почти каждая написанная программа на C++ в той или иной форме использует стандартную библиотеку, поэтому она часто подключается к вашим программам. Большинство компоновщиков автоматически подключают стандартную библиотеку, как только вы используете какую-либо ее часть, так что, как правило, вам не о чем беспокоиться.
Вы также можете при желании выполнить линковку с другими библиотеками. Например, если вы собрались написать программу, которая воспроизводит звук, вы, вероятно, не захотите писать свой собственный код для чтения звуковых файлов с диска, проверки их правильности или выяснения, как маршрутизировать звуковые данные к операционной системе или оборудованию для воспроизведения через динамик – это потребует много работы! Вместо этого вы, вероятно, загрузили бы библиотеку, которая уже знала, как это сделать, и использовали бы ее. О том, как связывать библиотеки (и создавать свои собственные!), мы поговорим в приложении.
В-третьих, компоновщик обеспечивает правильное разрешение всех межфайловых зависимостей. Например, если вы определяете что-то в одном файле .cpp , а затем используете это в другом файле .cpp , компоновщик соединит их вместе. Если компоновщик не может связать ссылку с чем-то с ее определением, вы получите ошибку компоновщика, и процесс линковки будет прерван.
Как только компоновщик завершит линковку всех объектных файлов и библиотек (при условии, что всё идет хорошо), вы получите исполняемый файл, который затем можно будет запустить!
Для продвинутых читателей
Для сложных проектов в некоторых средах разработки используется make-файл (makefile), который представляет собой файл, описывающий, как собрать программу (например, какие файлы компилировать и связывать, или обрабатывать какими-либо другими способами). О том, как писать и поддерживать make-файлы, написаны целые книги, и они могут быть невероятно мощным инструментом. Однако, поскольку make-файлы не являются частью ядра языка C++, и вам не нужно их использовать для продолжения изучения, мы не будем обсуждать их в рамках данной серии статей.
Шаги 6 и 7. Тестирование и отладка
Это самое интересное (надеюсь)! Вы можете запустить исполняемый файл и посмотреть, выдаст ли он ожидаемый результат!
Если ваша программа работает, но работает некорректно, то пора немного ее отладить, чтобы выяснить, что не так. Мы обсудим, как тестировать ваши программы и как их отлаживать, более подробно в ближайшее время.
Интегрированные среды разработки (IDE)
Обратите внимание, что шаги 3, 4, 5 и 7 включают в себя использование программного обеспечения (редактор, компилятор, компоновщик, отладчик). Хотя для каждого из этих действий вы можете использовать отдельные программы, программный пакет, известный как интегрированная среда разработки (IDE), объединяет все эти функции вместе. Мы обсудим IDE и установим одну из них в следующем разделе.
Компоновщик
Компоновщик (также реда́ктор свя́зей, линкер — от англ. link editor, linker ) — программа, которая производит компоновку: принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль.
Для связывания модулей компоновщик использует таблицы имён, созданные компилятором в каждом из объектных модулей. Такие имена могут быть двух типов:
- Определённые или экспортируемые имена — функции и переменные, определённые в данном модуле и предоставляемые для использования другим модулям;
- Неопределённые или импортируемые имена — функции и переменные, на которые ссылается модуль, но не определяет их внутри себя.
Работа компоновщика заключается в том, чтобы в каждом модуле определить и связать ссылки на неопределённые имена. Для каждого импортируемого имени находится его определение в других модулях, упоминание имени заменяется на его адрес.
Компоновщик обычно не выполняет проверку типов и количества параметров процедур и функций. Если надо объединить объектные модули программ, написанные на языках со строгой типизацией, то необходимые проверки должны быть выполнены дополнительной утилитой перед запуском редактора связей.
См. также
- Portable Executable
- Executable and Linking Format
Литература
- John R. Levine, Linkers and Loaders — Morgan-Kauffman, 1999, — ISBN 1-55860-496-0
- Дополнить статью (статья слишком короткая либо содержит лишь словарное определение).
- Найти и оформить в виде сносок ссылки на авторитетные источники, подтверждающие написанное.
- Проставив сноски, внести более точные указания на источники.
- Библиотеки программ
- Теория компиляторов
Wikimedia Foundation . 2010 .