Обзор паттернов хранения деревьев в реляционных БД / Хабр
Всем привет! Меня зовут Пантелеев Александр и я бэкенд-разработчик в компании Bimeister.
Постараюсь описать исчерпывающе, кратко и понятно суть основных паттернов хранения деревьев в реляционных базах данных. Надеюсь, что статья будет полезна тем, кто до сего момента не сталкивался с такими паттернами, и станет отправной точкой в их понимании.
В этой статье не будет терминов реляционной алгебры или базы данных: таких как атрибут, домен и т. д. Также не будет привязки к какой-либо СУБД, какому-либо SQL или пользовательскому коду.
Всего существует 4 общепринятых паттерна хранения деревьев:
Adjacency List;
Nested Sets;
Closure Table;
Materialized Path.
Кратко рассмотрим каждый из них.
Adjacency List
Описание
Это самый простой и интуитивный вариант хранения. Каждому элементу сопоставляется его свойство — его родительский элемент. Если родительский элемент не задан, то он считается корневым элементом.
Когда связь сопоставления элемента и родительского элемента хранится отдельно от элемента, Adjacency List можно рассматривать как частный случай Closure Table со связями 1 уровня.
Преимущества
Лёгкость реализации, а также простота вставки, удаления и перемещения элементов в дереве.
Недостатки
Можно получить только непосредственные дочерние элементы. Чтобы получить все дочерние элементы, необходимо выполнить рекурсивный запрос либо производить множественные запросы.
Примеры
Рисунок 1.Элемент | Родительский элемент |
A | — |
B | A |
C | B |
D | C |
E | B |
F | B |
G | A |
H | G |
I | A |
Рассмотрим элемент «B»:
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
Родительский элемент равен «B»
Nested Sets
Описание
Каждому элементу сопоставляются свойства: левый и правый индекс, на основе которых будет производиться выборка дочерних элементов. Также, но необязательно, элемент может дополняться свойством уровень для указания желаемого уровня вложенности выбираемого элемента относительно корня или родительского элемента.
Запрос получения дочерних элементов строится на том факте, что для любого дочернего элемента выполняются условия:
При создании и обновлении дерева левые и правые индексы элементов дерева, при его обходе в глубину, заполняются по определённым правилам.
Преимущества
Возможность получения дочерних элементов любых уровней вложенности с помощью простого одиночного запроса.
Недостатки
При использовании целочисленных типов для левого и правого индекса и уровня необходимо пересчитывать индексы всех связанных элементов в следующих случаях:
при вставке элементов;
при удалении элементов;
при изменении родительского элемента.
Пример
Рисунок 2.Элемент | Левый индекс | Правый индекс | Уровень |
A | 1 | 18 | 0 |
B | 2 | 11 | 1 |
C | 3 | 6 | 2 |
D | 4 | 5 | 3 |
E | 7 | 8 | 2 |
F | 9 | 10 | 2 |
G | 12 | 15 | 1 |
H | 13 | 14 | 2 |
I | 16 | 17 | 1 |
Рассмотрим элемент «B». Его значения свойств:
левый индекс = 2;
правый индекс = 11;
уровень = 1.
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
левый индекс больше 2 И правый индекс меньше 11
Чтобы получить его непосредственные дочерние элементы, нам необходимо добавить к условию ограничение на уровень:
левый индекс больше 2 И правый индекс меньше 11 И уровень = 1
Чтобы получить дочерние элементы вместе с родительским элементом, нам необходимо ослабить условия индексов:
левый индекс больше или равен 2 И правый индекс меньше или равен 11
Closure Table
Описание
Суть этого паттерна заключается в том, что мы сопоставляем каждому элементу множество связей со всеми его дочерними элементами или сопоставляем каждому элементу множество связей со всеми его родительскими элементами. Также, но необязательно, связь может содержать свойство Уровень. Уровень задаёт расстояние между элементами в дереве.
Если в запросе получения дочерних или родительских элементов по элементу необходимо получать в результате сам элемент, то нужно добавлять связь элемента самого на себя — то есть со значением уровня связи 0.
Преимущества
Возможность получения дочерних элементов любых уровней вложенности с помощью простого одиночного запроса.
Возможность получения родительских элементов любых уровней с их иерархией относительно дочернего элемента с помощью простого одиночного запроса.
Недостатки
При вставке и удалении элементов из дерева, а также при перемещении элементов в дереве необходимо пересчитывать все связи, в которых этот элемент участвует.
Пример
Рисунок 3.Родительский элемент | Дочерний элемент | Уровень |
A | A | 0 |
A | B | 1 |
A | C | 2 |
A | E | 2 |
A | D | 3 |
B | B | 0 |
B | C | 1 |
B | E | 1 |
B | D | 2 |
C | C | 0 |
C | D | 1 |
E | E | 0 |
D | D | 0 |
Рассмотрим элемент «B»:
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
родительский элемент равен «B»
Чтобы получить его непосредственные дочерние элементы, нам необходимо добавить к условию ограничение на уровень:
родительский элемент равен «B» И уровень = 1
Чтобы получить дочерние элементы вместе с родительскими, нам необходимо ослабить условия индексов:
родительский элемент равен «B» И уровень = 0
Чтобы получить все его родительские элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
дочерний элемент равен «B»
Materialized Path
Описание
Каждому элементу сопоставляется свойство — его путь, который является последовательностью родительских элементов заданного элемента, отсортированных по уровням. В общем случае, чтобы формировать гибкие запросы, тип реализации свойства путь должен поддерживать сопоставление по шаблону в каком-либо виде. При денормализации пути в отдельную таблицу получается разновидность Closue Table.
Условия запросов на получение элементов заключается в применении предиката над свойством путь.
Преимущества
Возможность получения дочерних элементов любых уровней вложенности.
Возможность получения родительских элементов любых уровней с их иерархией относительно дочернего элемента.
Лёгкость вставки элемента.
Лёгкость удаления элемента.
Недостатки
Сложность изменения родителя для существующего элемента. Для всех дочерних элементов необходимо пересчитать новый путь.
Операции со свойством путь обычно происходят долго.
Пример
Рисунок 4.Элемент | Путь |
A |
|
B | A |
C | A B |
D | A B C |
E | A B |
Рассмотрим элемент «B»:
Чтобы получить все его дочерние элементы, нам необходимо выбрать элементы, удовлетворяющие условию:
путь содержит «B»
Чтобы получить его непосредственные дочерние элементы, нужно указать позицию, в которой содержится элемент. В примере путь отсортирован так, что последняя часть пути — это непосредственный родительский элемент:
последняя часть пути равна «B»
Заключение
Мы кратко рассмотрели основные паттерны хранения деревьев в реляционной базе данных. Их основные достоинства и недоставки, а также на примерах рассмотрели основные запросы к ним. В этой статье не были рассмотрены алгоритмы построения и заполнения метаданных деревьев, то есть операции добавления, обновления и удаления элементов.
Компоновщик
/ Паттерны проектирования / Структурные паттерны
Также известен как: Дерево, Composite
Суть паттернаКомпоновщик — это структурный паттерн проектирования, который позволяет сгруппировать множество объектов в древовидную структуру, а затем работать с ней так, как будто это единичный объект.
ПроблемаПаттерн Компоновщик имеет смысл только тогда, когда основная модель вашей программы может быть структурирована в виде дерева.
Например, есть два объекта: Продукт
и Коробка
. Коробка
может содержать несколько Продуктов
и других Коробок
поменьше. Те, в свою очередь, тоже содержат либо
, либо Коробки
и так далее.
Теперь предположим, ваши Продукты
и Коробки
могут быть частью заказов. Каждый заказ может содержать как простые Продукты
без упаковки, так и составные Коробки
. Ваша задача состоит в том, чтобы узнать цену всего заказа.
Заказ может состоять из различных продуктов, упакованных в собственные коробки.
Если решать задачу в лоб, то вам потребуется открыть все коробки заказа, перебрать все продукты и посчитать их суммарную стоимость. Но это слишком хлопотно, так как типы коробок и их содержимое могут быть вам неизвестны.
Компоновщик предлагает рассматривать Продукт
и Коробку
через единый интерфейс с общим методом получения стоимости.
Продукт
просто вернёт свою цену. Коробка
спросит цену каждого предмета внутри себя и вернёт сумму результатов. Если одним из внутренних предметов окажется коробка поменьше, она тоже будет перебирать своё содержимое, и так далее, пока не будут посчитаны все составные части.
Компоновщик рекурсивно запускает действие по всем элементам дерева — от корня к листьям.
Для вас, клиента, главное, что теперь не нужно ничего знать о структуре заказов. Вы вызываете метод получения цены, он возвращает цифру, а вы не тонете в горах картона и скотча.
Пример армейской структуры.
Армии большинства государств могут быть представлены в виде перевёрнутых деревьев. На нижнем уровне у вас есть солдаты, затем взводы, затем полки, а затем целые армии. Приказы отдаются сверху и спускаются вниз по структуре командования, пока не доходят до конкретного солдата.
СтруктураКомпонент
Лист — это простой компонент дерева, не имеющий ответвлений.
Из-за того, что им некому больше передавать выполнение, классы листьев будут содержать большую часть полезного кода.
Контейнер (или композит) — это составной компонент дерева. Он содержит набор дочерних компонентов, но ничего не знает об их типах. Это могут быть как простые компоненты-листья, так и другие компоненты-контейнеры. Но это не является проблемой, если все дочерние компоненты следуют единому интерфейсу.
Методы контейнера переадресуют основную работу своим дочерним компонентам, хотя и могут добавлять что-то своё к результату.
Клиент работает с деревом через общий интерфейс компонентов.
Благодаря этому, клиенту не важно, что перед ним находится — простой или составной компонент дерева.
В этом примере Компоновщик помогает реализовать вложенные геометрические фигуры.
Пример редактора геометрических фигур.
Класс CompoundGraphic
может содержать любое количество подфигур, включая такие же контейнеры, как он сам. Контейнер реализует те же методы, что и простые фигуры. Но, вместо непосредственного действия, он передаёт вызовы всем вложенным компонентам, используя рекурсию. Затем он как бы «суммирует» результаты всех вложенных фигур.
Клиентский код работает со всеми фигурами через общий интерфейс фигур и не знает, что перед ним — простая фигура или составная. Это позволяет клиентскому коду работать с деревьями объектов любой сложности, не привязываясь к конкретным классам объектов, формирующих дерево.
// Общий интерфейс компонентов.
interface Graphic is
method move(x, y)
method draw()
// Простой компонент.
class Dot implements Graphic is
field x, y
constructor Dot(x, y) { ... }
method move(x, y) is
this.x += x, this.y += y
method draw() is
// Нарисовать точку в координате X, Y.
// Компоненты могут расширять другие компоненты.
class Circle extends Dot is
field radius
constructor Circle(x, y, radius) { ... }
method draw() is
// Нарисовать окружность в координате X, Y и радиусом R.
// Контейнер содержит операции добавления/удаления дочерних
// компонентов. Все стандартные операции интерфейса компонентов
// он делегирует каждому из дочерних компонентов.
class CompoundGraphic implements Graphic is
field children: array of Graphic
method add(child: Graphic) is
// Добавить компонент в список дочерних.
method remove(child: Graphic) is
// Убрать компонент из списка дочерних.
method move(x, y) is
foreach (child in children) do
child. move(x, y)
method draw() is
// 1. Для каждого дочернего компонента:
// - Отрисовать компонент.
// - Определить координаты максимальной границы.
// 2. Нарисовать пунктирную границу вокруг всей области.
// Приложение работает единообразно как с единичными
// компонентами, так и с целыми группами компонентов.
class ImageEditor is
field all: CompoundGraphic
method load() is
all = new CompoundGraphic()
all.add(new Dot(1, 2))
all.add(new Circle(5, 3, 10))
// ...
// Группировка выбранных компонентов в один сложный
// компонент.
method groupSelected(components: array of Graphic) is
group = new CompoundGraphic()
foreach (component in components) do
group.add(component)
all.remove(component)
all.add(group)
// Все компоненты будут отрисованы.
all.draw()
Когда вам нужно представить древовидную структуру объектов.
Паттерн Компоновщик предлагает хранить в составных объектах ссылки на другие простые или составные объекты. Те, в свою очередь, тоже могут хранить свои вложенные объекты и так далее. В итоге вы можете строить сложную древовидную структуру данных, используя всего две основные разновидности объектов.
Когда клиенты должны единообразно трактовать простые и составные объекты.
Благодаря тому, что простые и составные объекты реализуют общий интерфейс, клиенту безразлично, с каким именно объектом ему предстоит работать.
Шаги реализацииУбедитесь, что вашу бизнес-логику можно представить как древовидную структуру. Попытайтесь разбить её на простые компоненты и контейнеры. Помните, что контейнеры могут содержать как простые компоненты, так и другие вложенные контейнеры.
Создайте общий интерфейс компонентов, который объединит операции контейнеров и простых компонентов дерева. Интерфейс будет удачным, если вы сможете использовать его, чтобы взаимозаменять простые и составные компоненты без потери смысла.
Создайте класс компонентов-листьев, не имеющих дальнейших ответвлений. Имейте в виду, что программа может содержать несколько таких классов.
Создайте класс компонентов-контейнеров и добавьте в него массив для хранения ссылок на вложенные компоненты. Этот массив должен быть способен содержать как простые, так и составные компоненты, поэтому убедитесь, что он объявлен с типом интерфейса компонентов.
Реализуйте в контейнере методы интерфейса компонентов, помня о том, что контейнеры должны делегировать основную работу своим дочерним компонентам.
Добавьте операции добавления и удаления дочерних компонентов в класс контейнеров.
Имейте в виду, что методы добавления/удаления дочерних компонентов можно поместить и в интерфейс компонентов. Да, это нарушит принцип разделения интерфейса
- Упрощает архитектуру клиента при работе со сложным деревом компонентов.
- Облегчает добавление новых видов компонентов.
- Создаёт слишком общий дизайн классов.
Строитель позволяет пошагово сооружать дерево Компоновщика.
Цепочку обязанностей часто используют вместе с Компоновщиком. В этом случае запрос передаётся от дочерних компонентов к их родителям.
Вы можете обходить дерево Компоновщика, используя Итератор.
Вы можете выполнить какое-то действие над всем деревом Компоновщика при помощи Посетителя.
Компоновщик часто совмещают с Легковесом, чтобы реализовать общие ветки дерева и сэкономить при этом память.
Компоновщик и Декоратор имеют похожие структуры классов из-за того, что оба построены на рекурсивной вложенности. Она позволяет связать в одну структуру бесконечное количество объектов.
Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей.
Но они могут и сотрудничать: Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей дерева компонентов.
Архитектура, построенная на Компоновщиках и Декораторах, часто может быть улучшена за счёт внедрения Прототипа. Он позволяет клонировать сложные структуры объектов, а не собирать их заново.
шаблонов новогодних елок всех форм и размеров
По
Стейси Фишер
Стейси Фишер
Стейси Фишер — эксперт по рукоделию, шитью и экономным находкам. Она делится своими знаниями о творениях своими руками, поиске халявы и составлении бюджета. Стейси была гостьей на «Шоу доктора Оз» и была представлена в журнале Woman’s World Magazine. У нее почти два десятилетия опыта написания и редактирования.
Узнайте больше о The Spruce Crafts’ Редакционный процесс
Обновлено 21.05.21
Ель / Марица Патринос
Эти шаблоны рождественских елок для печати представляют собой пустые изображения рождественских елок, которые вы можете распечатать, а затем попросить ребенка вырезать и украсить. Вы найдете широкий выбор форм и стилей новогодних елок, которые подойдут практически для любого ремесленного проекта.
Эти шаблоны новогодних елок можно распечатать на обычной бумаге для принтера или на картоне. Вы даже можете изменить его, распечатав их на бумаге разных цветов.
Если вы ищете более подробные шаблоны елки, посетите мой список страниц раскраски рождественской елки. Здесь вы найдете все виды новогодних елок, и одна из них может идеально подойти для проекта, над которым вы работаете.
Поощряйте своих детей использовать свое воображение, чтобы украсить эти шаблоны рождественской елки, используя маркеры, цветные карандаши, краски, блестки, перья, вырезки из журналов или что-нибудь еще, что сделает их рождественскую елку уникальной.
01 08
Давайте сделаем поздравительные открытки
За одну распечатку вы получите два размера этого замечательного шаблона рождественской елки от Let’s Make Greeting Cards. Включены инструкции о том, как использовать шаблоны елки для изготовления рождественских поздравительных открыток ручной работы.
Дуэт шаблонов рождественской елки из Let’s Make Greeting Cards
02 от 08
Первая палитра
В этих шаблонах рождественской елки First Palette есть как меньшие — они печатаются по четыре на страницу, так и большие — по одному на страницу. Это обычные елочки с заостренными или закругленными углами и плоским дном.
Шаблоны новогодних елок с заостренными углами из первой палитры
03 от 08
Бесплатные схемы аппликаций
Free Applique Patterns предлагает вам шаблон рождественской елки, который предполагается использовать для аппликации, которую вы, конечно, можете сделать, но на самом деле это универсальный шаблон, который можно использовать для любого запланированного вами праздничного проекта.
Есть также высокие деревья, тонкие деревья, низкие деревья, круглые деревья, деревья со звездами, деревья со снегом и многие другие виды шаблонов новогодних елок. Прокрутите ту же страницу вниз, и вы также найдете бесплатные шаблоны для ангелов, имбирных человечков, чулок, украшений, колокольчиков, варежек и многого другого.
Рождественская елка со звездой Шаблон из бесплатных шаблонов для аппликаций
04 от 08
Северный полюс Рождество
Этот шаблон рождественской елки от North Pole Christmas представляет собой несовершенную рождественскую елку с большой кучей снега на вершине. Здесь есть и другие рождественские шаблоны, в которых представлены все разные категории.
Шаблоны новогодних елок всех форм и размеров из Рождества Северного полюса
05 от 08
Вселенная Узоров
Вот бесплатный шаблон рождественской елки от Pattern Universe с закругленными краями, коротким стволом и большой звездой наверху. Этот бесплатный шаблон загружается в виде одностраничного PDF-файла, который вы можете сохранить или распечатать бесплатно. Это дерево печатается большим и занимает всю страницу.
Большая выкройка рождественской елки от Pattern Universe
06 от 08
Первая школа
Вот единый шаблон рождественской елки от First School, который можно распечатать прямо из браузера. Этот шаблон рождественской елки представляет собой многослойное дерево в простой подставке. Это хороший большой размер и печатает один на странице.
Шаблон рождественской елки в горшке из первой школы
07 от 08
Ремесленный еженедельник
У Craft Weekly есть бесплатный шаблон рождественской елки, который включает в себя шаблон елки, звезду и верхушку елки. После того, как вы распечатаете шаблоны, соберите дерево, чтобы сделать простое трехмерное дерево, которое вы можете использовать на открытке или в качестве простого украшения.
3D-шаблон рождественской елки от Craft Weekly
08 от 08
Один маленький проект
В One Little Project есть отличная коллекция бесплатных шаблонов новогодних елок. Всего существует 8 различных форм елки. Каждое дерево немного отличается, что дает вам большую гибкость в вашем проекте. Вы также найдете идеи о том, как использовать эти шаблоны рождественских елок.
Шаблоны новогодних елок от One Little Project
Tree Quilt Pattern — Etsy.de
Etsy больше не поддерживает старые версии вашего веб-браузера, чтобы обеспечить безопасность пользовательских данных. Пожалуйста, обновите до последней версии.
Воспользуйтесь всеми возможностями нашего сайта, включив JavaScript.