From 5df05f703a3106f7faddfc0df406025a5347a7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=9C=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 21 Jul 2025 15:52:36 +0700 Subject: [PATCH] =?UTF-8?q?qosd:=20=D1=81=D0=B4=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BD=D0=B0=D0=B1=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D0=B0=D0=B1=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=BD=D0=BE=D0=BC=D1=83=20=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + CMakeLists.txt | 23 +++++ Doxyfile | 20 ++++ Makefile | 13 +++ include/Logger.h | 92 ++++++++++++++++++ include/ifaces/ILink.h | 21 +++++ include/ifaces/ILinkMixin.h | 18 ++++ include/ifaces/INode.h | 17 ++++ include/links/BaseLink.h | 43 +++++++++ include/links/LeafLink.h | 12 +++ include/links/NotImplementedLink.h | 27 ++++++ include/links/OneToManyLink.h | 17 ++++ include/links/OneToOneLink.h | 21 +++++ include/mixins/BaseLinkMixin.h | 50 ++++++++++ include/mixins/HierarchicalLinkMixin.h | 48 ++++++++++ include/mixins/LazyLinkMixin.h | 39 ++++++++ include/nodes/BaseNode.h | 22 +++++ include/nodes/ComplexNode.h | 19 ++++ include/nodes/SimpleNode.h | 20 ++++ src/main.cpp | 124 +++++++++++++++++++++++++ 20 files changed, 650 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Doxyfile create mode 100644 Makefile create mode 100644 include/Logger.h create mode 100644 include/ifaces/ILink.h create mode 100644 include/ifaces/ILinkMixin.h create mode 100644 include/ifaces/INode.h create mode 100644 include/links/BaseLink.h create mode 100644 include/links/LeafLink.h create mode 100644 include/links/NotImplementedLink.h create mode 100644 include/links/OneToManyLink.h create mode 100644 include/links/OneToOneLink.h create mode 100644 include/mixins/BaseLinkMixin.h create mode 100644 include/mixins/HierarchicalLinkMixin.h create mode 100644 include/mixins/LazyLinkMixin.h create mode 100644 include/nodes/BaseNode.h create mode 100644 include/nodes/ComplexNode.h create mode 100644 include/nodes/SimpleNode.h create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7a8f0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.swp +build +.vscode +doc diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0f7ab4d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +# CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(ModernTree LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Находим все исходные файлы в директории src +file(GLOB_RECURSE SOURCES "src/*.cpp") + +# Создаем исполняемый файл из исходников +add_executable(tree_app ${SOURCES}) + +# Указываем, что директория include является публичной для нашего проекта +# Это позволит использовать #include "ifaces/INode.h" и т.д. +target_include_directories(tree_app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Сообщение для удобства +message(STATUS "Project configured. To build, run: cmake --build .") +message(STATUS "To run the application: ./tree_app") diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..a234636 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,20 @@ +# Doxyfile 1.9.8 +# Основной конфиг для генерации документации Doxygen + +PROJECT_NAME = "links project" +OUTPUT_DIRECTORY = doc +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = Russian +INPUT = src include +FILE_PATTERNS = *.h *.cpp +RECURSIVE = YES +EXTRACT_ALL = YES +GENERATE_LATEX = NO +GENERATE_HTML = YES +GENERATE_MAN = NO +GENERATE_RTF = NO +GENERATE_XML = NO +QUIET = NO +WARN_IF_UNDOCUMENTED = YES + +# Можно добавить дополнительные настройки по необходимости \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a30f06f --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all: build run + +clean: + rm -rf build/ + +build: + cmake -B build + +compile: build + cmake --build build + +run: compile + ./build/tree_app diff --git a/include/Logger.h b/include/Logger.h new file mode 100644 index 0000000..9262cd0 --- /dev/null +++ b/include/Logger.h @@ -0,0 +1,92 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +/// \brief Класс для логирования сообщений по категориям и уровням серьезности. +/// Позволяет вести логирование с разными уровнями и подавлять вывод по категориям. +class Logger { +public: + /// \brief Уровни серьезности сообщений для логгера. + enum class Severity { + Debug, ///< Отладочная информация + Info, ///< Информационные сообщения + Warning, ///< Предупреждения + Error ///< Ошибки + }; + + // Получить логгер по категории (создаёт, если нет) + static Logger& get(const std::string& category) { + std::lock_guard lock(registry_mutex_); + auto it = registry_.find(category); + if (it == registry_.end()) { + auto logger = std::unique_ptr(new Logger(category)); + auto [inserted_it, _] = registry_.emplace(category, std::move(logger)); + return *(inserted_it->second); + } + return *(it->second); + } + + // Настроить suppression для категории + static void suppressCategory(const std::string& category) { + get(category).suppressed_ = true; + } + static void unsuppressCategory(const std::string& category) { + get(category).suppressed_ = false; + } + static void setMinSeverity(const std::string& category, Severity level) { + get(category).min_severity_ = level; + } + static Severity getMinSeverity(const std::string& category) { + return get(category).min_severity_; + } + static bool isSuppressed(const std::string& category) { + return get(category).suppressed_; + } + + void log(const std::string& message, Severity severity = Severity::Info) const { + if (suppressed_) return; + if (severity < min_severity_) return; + std::cout << "[" << category_ << "] " << severityToString(severity) << ": " << message << std::endl << std::flush; + } + + // Удобные методы для логирования по уровню + void dbg(const std::string& message) const { log(message, Severity::Debug); } + void info(const std::string& message) const { log(message, Severity::Info); } + void warn(const std::string& message) const { log(message, Severity::Warning); } + void err(const std::string& message) const { log(message, Severity::Error); } + + static std::string severityToString(Severity severity) { + switch (severity) { + case Severity::Debug: return "DEBUG"; + case Severity::Info: return "INFO"; + case Severity::Warning: return "WARNING"; + case Severity::Error: return "ERROR"; + default: return "UNKNOWN"; + } + } + + const std::string& category() const { return category_; } + Severity minSeverity() const { return min_severity_; } + void setMinSeverity(Severity s) { min_severity_ = s; } + bool suppressed() const { return suppressed_; } + void setSuppressed(bool s) { suppressed_ = s; } + +private: + explicit Logger(std::string category) + : category_(std::move(category)), min_severity_(Severity::Debug), suppressed_(false) {} + + std::string category_; + Severity min_severity_; + bool suppressed_; + + static std::unordered_map> registry_; + static std::mutex registry_mutex_; +}; + +// Определения статических членов +inline std::unordered_map> Logger::registry_{}; +inline std::mutex Logger::registry_mutex_; diff --git a/include/ifaces/ILink.h b/include/ifaces/ILink.h new file mode 100644 index 0000000..7684640 --- /dev/null +++ b/include/ifaces/ILink.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include "ifaces/ILinkMixin.h" + +/// \brief Интерфейс для классов-связей между элементами. +/// \tparam TElem Тип элемента, между которыми устанавливается связь. +template +class ILink { +public: + using ElemPtr = std::shared_ptr; + + virtual ~ILink() = default; + virtual ElemPtr getNode() const = 0; + virtual ElemPtr getParent() const = 0; + virtual void setParent(const ElemPtr& parent) = 0; + virtual const std::vector& getChildren() const = 0; + virtual void addChild(const ElemPtr& child) = 0; + virtual void removeChild(const ElemPtr& child) = 0; + virtual void replaceChild(const ElemPtr& oldChild, const ElemPtr& newChild) = 0; +}; diff --git a/include/ifaces/ILinkMixin.h b/include/ifaces/ILinkMixin.h new file mode 100644 index 0000000..67f3b6c --- /dev/null +++ b/include/ifaces/ILinkMixin.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include "ifaces/ILink.h" + +/// \brief Интерфейс для классов, поддерживающих связь с дочерними элементами через ILink. +/// \tparam TElem Тип дочернего элемента. +template +class ILinkMixin { +public: + using LinkPtr = std::shared_ptr>; + using ElemPtr = std::shared_ptr; + + virtual ~ILinkMixin() = default; + virtual void linkChild(const ElemPtr& child) = 0; + virtual void unlinkParent() = 0; + virtual const std::vector& children() = 0; + virtual LinkPtr getLink() = 0; +}; diff --git a/include/ifaces/INode.h b/include/ifaces/INode.h new file mode 100644 index 0000000..bf83adb --- /dev/null +++ b/include/ifaces/INode.h @@ -0,0 +1,17 @@ +#pragma once +#include "ifaces/ILinkMixin.h" +#include + +class INode; + +/// \brief Умный указатель на INode. +using NodePtr = std::shared_ptr; + +/// \brief Интерфейс для узлов дерева. +/// Определяет базовые методы для работы с именем и типом узла. +class INode : public virtual ILinkMixin { +public: + ~INode() = default; + virtual const std::string& name() const = 0; + virtual const std::string& kind() const = 0; +}; diff --git a/include/links/BaseLink.h b/include/links/BaseLink.h new file mode 100644 index 0000000..2f9bfce --- /dev/null +++ b/include/links/BaseLink.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include + +#include "ifaces/ILink.h" +#include "Logger.h" + +/// \brief Базовая реализация интерфейса ILink для хранения связей между элементами. +/// \tparam TElem Тип элемента, между которыми устанавливается связь. +template +class BaseLink : public ILink { +public: + using ElemPtr = std::shared_ptr; + + BaseLink(ElemPtr node) : owner_node_(node) {} + ElemPtr getNode() const override { return owner_node_.lock(); } + ElemPtr getParent() const override { return parent_.lock(); } + void setParent(const ElemPtr& parent) override { parent_ = parent; } + const std::vector& getChildren() const override { return children_; } + + void addChild(const ElemPtr& child) override { + this->children_.push_back(child); + } + + void removeChild(const ElemPtr& child) override { + children_.erase(std::remove(children_.begin(), children_.end(), child), children_.end()); + } + + void replaceChild(const ElemPtr& oldChild, const ElemPtr& newChild) override { + auto it = std::find(children_.begin(), children_.end(), oldChild); + if (it != children_.end()) { + *it = newChild; + } + } + + ~BaseLink() override { + Logger::get("Link").dbg("--- Destructor called for: BaseLink"); + } +protected: + std::vector children_; + std::weak_ptr owner_node_; + std::weak_ptr parent_; +}; diff --git a/include/links/LeafLink.h b/include/links/LeafLink.h new file mode 100644 index 0000000..1f2a95d --- /dev/null +++ b/include/links/LeafLink.h @@ -0,0 +1,12 @@ +#pragma once +#include "links/BaseLink.h" +#include + +/// \brief Связь для листового узла, не допускающая дочерних элементов. +class LeafLink : public BaseLink { +public: + using BaseLink::BaseLink; + void addChild(const NodePtr&) override { + throw std::logic_error("LeafLink cannot have children"); + } +}; diff --git a/include/links/NotImplementedLink.h b/include/links/NotImplementedLink.h new file mode 100644 index 0000000..f7d9549 --- /dev/null +++ b/include/links/NotImplementedLink.h @@ -0,0 +1,27 @@ +#pragma once +#include + +#include "ifaces/ILink.h" +#include "Logger.h" + +/// \brief Заглушка для не реализованной связи между элементами. +/// Все методы возвращают пустые значения или не выполняют действий. +/// \tparam TElem Тип элемента. +template +class NotImplementedLink : public ILink { + using ElemPtr = std::shared_ptr>; +public: + NotImplementedLink(ElemPtr node) {} + ElemPtr getNode() const override { return nullptr; } + ElemPtr getParent() const override { return nullptr; } + void setParent(const ElemPtr& parent) override { } + const std::vector& getChildren() const override { return empty_; } + void addChild(const ElemPtr& child) override { } + void removeChild(const ElemPtr& child) override { } + + ~NotImplementedLink() override { + Logger::get("Link").dbg("--- Destructor called for: NotImplementedLink"); + } +private: + std::vector empty_; +}; diff --git a/include/links/OneToManyLink.h b/include/links/OneToManyLink.h new file mode 100644 index 0000000..8b11f44 --- /dev/null +++ b/include/links/OneToManyLink.h @@ -0,0 +1,17 @@ +#pragma once +#include "links/BaseLink.h" + +/// \brief Связь "один-ко-многим" между элементами одного типа. +/// Каждый дочерний элемент должен быть того же типа, что и родитель. +/// \tparam TElem Тип элемента. +template +class OneToManyLink : public BaseLink { +public: + OneToManyLink(std::shared_ptr e) : BaseLink(e) {} + void addChild(const std::shared_ptr& child) override { + /* Each child must be exactly the same type as parent */ + if (typeid(*child) != typeid(*this->owner_node_.lock())) + throw std::logic_error("Foundling child"); + BaseLink::addChild(child); + } +}; diff --git a/include/links/OneToOneLink.h b/include/links/OneToOneLink.h new file mode 100644 index 0000000..e6bc8fb --- /dev/null +++ b/include/links/OneToOneLink.h @@ -0,0 +1,21 @@ +#pragma once +#include "links/BaseLink.h" +#include + +/// \brief Связь "один-к-одному" между элементами. +/// Позволяет добавить только одного дочернего элемента. +/// \tparam TElem Тип элемента. +template +class OneToOneLink : public BaseLink { +public: + using ElemPtr = std::shared_ptr; + + OneToOneLink(ElemPtr e) : BaseLink(e) {} + + void addChild(const ElemPtr& child) override { + if (!this->children_.empty()) + throw std::logic_error("Too many children"); + + BaseLink::addChild(child); + } +}; diff --git a/include/mixins/BaseLinkMixin.h b/include/mixins/BaseLinkMixin.h new file mode 100644 index 0000000..67ba4f6 --- /dev/null +++ b/include/mixins/BaseLinkMixin.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include + +#include "ifaces/ILinkMixin.h" +#include "ifaces/INode.h" +#include "Logger.h" + +/// \brief Базовый миксин для реализации ILinkMixin для INode. +/// Предоставляет базовую реализацию методов для работы с дочерними узлами и родителем. +class BaseLinkMixin : public virtual ILinkMixin, + public std::enable_shared_from_this> { + using ElemPtr = std::shared_ptr; + +public: + ~BaseLinkMixin() override { + Logger::get("Mixin").dbg("--- Destructor called for: BaseLinkMixin"); + } + + void linkChild(const ElemPtr& child) override { + getLink()->addChild(child); + + auto childLink = child->getLink(); + childLink->setParent(getNode()); + } + + void unlinkParent() override { + auto link = getLink(); + + ElemPtr parent = link->getParent(); + if (!parent) + throw std::logic_error("Have no parent!"); + + auto parentLink = parent->getLink(); + + parentLink->removeChild(getNode()); + getLink()->setParent(nullptr); + } + + + const std::vector& children() override { + auto link = getLink(); + return link->getChildren(); + } + +protected: + ElemPtr getNode() { + return std::dynamic_pointer_cast(shared_from_this()); + } +}; diff --git a/include/mixins/HierarchicalLinkMixin.h b/include/mixins/HierarchicalLinkMixin.h new file mode 100644 index 0000000..07c84a1 --- /dev/null +++ b/include/mixins/HierarchicalLinkMixin.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include "mixins/LazyLinkMixin.h" +#include +#include "links/OneToManyLink.h" +#include "links/OneToOneLink.h" +#include "Logger.h" + +/// \brief Миксин для иерархических связей между элементами. +/// Автоматически выбирает тип связи (один-ко-многим или один-к-одному) в зависимости от типа дочернего элемента. +/// \tparam TElem Тип дочернего элемента. +template +class HierarchicalLinkMixin : public LazyLinkMixin> { + using LinkPtr = std::shared_ptr>; + using ElemPtr = std::shared_ptr; + +public: + ~HierarchicalLinkMixin() override { + Logger::get("Mixin").dbg("--- Destructor called for: HierarchicalLinkMixin"); + } + + void linkChild(const ElemPtr& child) override { + hierarchicalInit(child); + LazyLinkMixin>::linkChild(child); + } + +protected: + void hierarchicalInit(const ElemPtr& child) { + Logger::get("Mixin").dbg("--- hierarchicalInit called"); + if (this->link_ && !this->link_->getChildren().empty()) + return; + + LinkPtr newLink; + + if (typeid(*child) == typeid(*this)) { + Logger::get("Mixin").dbg("--- Mutate to OneToMany"); + newLink = std::make_shared>(this->getNode()); + } else { + Logger::get("Mixin").dbg("--- Mutate to OneToOne"); + newLink = std::make_shared>(this->getNode()); + } + + if (newLink && this->link_) + newLink->setParent(this->link_->getParent()); + + this->link_ = newLink; + } +}; diff --git a/include/mixins/LazyLinkMixin.h b/include/mixins/LazyLinkMixin.h new file mode 100644 index 0000000..3186da5 --- /dev/null +++ b/include/mixins/LazyLinkMixin.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include "mixins/BaseLinkMixin.h" +#include +#include "Logger.h" + +/// \brief Миксин для ленивой инициализации связи (link) с дочерними элементами. +/// \tparam TLink Тип используемой связи (link). +template +class LazyLinkMixin : public BaseLinkMixin { +public: + void unlinkParent() override { + /* No link -- no parent, who'll unlinked? */ + if (!this->link_) + throw std::logic_error("Link isn't inited!"); + + BaseLinkMixin::unlinkParent(); + } + + LinkPtr getLink() override { + lazyInit(); + return this->link_; + } + + LazyLinkMixin() {} + + ~LazyLinkMixin() override { + Logger::get("Mixin").dbg("--- Destructor called for: LazyLinkMixin"); + } + +protected: + void lazyInit() { + if (!link_) { + link_ = std::make_shared( + BaseLinkMixin::getNode()); + } + } + LinkPtr link_; +}; diff --git a/include/nodes/BaseNode.h b/include/nodes/BaseNode.h new file mode 100644 index 0000000..0eae52d --- /dev/null +++ b/include/nodes/BaseNode.h @@ -0,0 +1,22 @@ +#pragma once +#include + +#include "ifaces/INode.h" +#include "Logger.h" + +/// \brief Базовый класс для всех узлов дерева. +/// Содержит имя и тип узла, реализует интерфейс INode. +class BaseNode : public virtual INode { +public: + BaseNode(std::string name) : name_(std::move(name)) { + Logger::get("Node").dbg(std::string("--- Base constructor called for: ") + name_); + } + const std::string& name() const override { return name_; } + const std::string& kind() const override { return kind_; } + ~BaseNode() { + Logger::get("Node").dbg(std::string("--- Base destructor called for: ") + name_); + } +protected: + std::string name_; + std::string kind_; +}; diff --git a/include/nodes/ComplexNode.h b/include/nodes/ComplexNode.h new file mode 100644 index 0000000..f611332 --- /dev/null +++ b/include/nodes/ComplexNode.h @@ -0,0 +1,19 @@ +#pragma once +#include + +#include "nodes/BaseNode.h" +#include "mixins/HierarchicalLinkMixin.h" +#include "Logger.h" + +/// \brief Класс сложного (составного) узла дерева. +/// Может содержать несколько дочерних ComplexNode и один SimpleNode. +class ComplexNode : public BaseNode, + virtual public HierarchicalLinkMixin { +public: + ~ComplexNode() { + Logger::get("Node").dbg(std::string("--- Complex destructor called for: ") + name_); + } + ComplexNode(std::string name) : BaseNode(std::move(name)) { + Logger::get("Node").dbg(std::string("--- Complex constructor called for: ") + name_); + } +}; diff --git a/include/nodes/SimpleNode.h b/include/nodes/SimpleNode.h new file mode 100644 index 0000000..8f920b0 --- /dev/null +++ b/include/nodes/SimpleNode.h @@ -0,0 +1,20 @@ +#pragma once +#include + +#include "nodes/BaseNode.h" +#include "mixins/LazyLinkMixin.h" +#include "links/OneToOneLink.h" +#include "Logger.h" + +/// \brief Класс простого (листового) узла дерева. +/// Может содержать только одного дочернего ComplexNode. +class SimpleNode : public BaseNode, + virtual public LazyLinkMixin> { +public: + ~SimpleNode() { + Logger::get("Node").dbg(std::string("--- Simple destructor called for: ") + name_); + } + SimpleNode(std::string name) : BaseNode(std::move(name)) { + Logger::get("Node").dbg(std::string("--- Simple constructor called for: ") + name_); + } +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..78b46eb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,124 @@ +#include "nodes/SimpleNode.h" +#include "nodes/ComplexNode.h" +#include +#include "Logger.h" + +void printTree(const NodePtr& node, int indent = 0) { + if (!node) { + Logger::get("MAIN").err("No node"); + return; + } + + for (int i = 0; i < indent; ++i) std::cout << " "; + std::cout << node->name() << "\n"; + + for (const auto& child : node->children()) + printTree(child, indent + 1); +} + +int main() { + Logger::setMinSeverity("MAIN", Logger::Severity::Debug); + Logger::suppressCategory("Node"); + Logger::suppressCategory("Link"); + Logger::suppressCategory("Mixin"); + auto& logger = Logger::get("MAIN"); + logger.info("Entering main scope..."); + + { + auto root = std::make_shared("ComplexRoot"); + auto child1 = std::make_shared("ComplexChild1"); + + root->linkChild(child1); + + std::cout << "\nInit tree:\n"; + printTree(root); + std::cout << "\n"; + + { + auto child2 = std::make_shared("ComplexChild2"); + root->linkChild(child2); + + auto subchild2 = std::make_shared("SimpleSubChild2"); + child2->linkChild(subchild2); + + auto child3 = std::make_shared("ComplexChild3"); + root->linkChild(child3); + + auto subchild3 = std::make_shared("SimpleSubChild3"); + child3->linkChild(subchild3); + + { + // Негативный сценарий 1: попытка добавить второй SimpleNode к ComplexNode + try { + auto anotherSimple = std::make_shared("ShouldFail"); + child2->linkChild(anotherSimple); + logger.err("[ERROR] Не должно было получиться добавить второй SimpleNode к ComplexNode!"); + } catch (const std::logic_error& e) { + logger.warn(std::string("[Ожидаемое исключение] ") + e.what()); + } + + // Негативный сценарий 2: попытка добавить ComplexNode к SimpleNode + // Это допустимо: SimpleNode может иметь ComplexNode в качестве единственного ребёнка + auto goodComplex = std::make_shared("GoodComplex"); + subchild2->linkChild(goodComplex); + logger.info("[OK] ComplexNode успешно добавлен к SimpleNode как единственный ребёнок."); + + // Негативный сценарий: попытка добавить второго ребёнка к SimpleNode + try { + auto anotherSimple = std::make_shared("ShouldFail2"); + subchild2->linkChild(anotherSimple); + logger.err("[ERROR] Не должно было получиться добавить второго ребёнка к SimpleNode!"); + } catch (const std::logic_error& e) { + logger.warn(std::string("[Ожидаемое исключение] ") + e.what()); + } + + // Негативный сценарий: попытка добавить SimpleNode в ComplexNode, который уже содержит несколько ComplexNode-дочерних + try { + auto badSimple = std::make_shared("BadSimple"); + root->linkChild(badSimple); + logger.err("[ERROR] Не должно было получиться добавить SimpleNode в ComplexNode с несколькими ComplexNode-дочерними!"); + } catch (const std::logic_error& e) { + logger.warn(std::string("[Ожидаемое исключение] ") + e.what()); + } + } + + std::cout << "\nAnother tree:\n"; + printTree(root); + std::cout << "\n"; + + logger.info("Unlinking ComplexChild2...\n"); + child2->unlinkParent(); + + std::cout << "\nTree after unlink:\n"; + printTree(root); + std::cout << "\n"; + + logger.info("Put refs of ComplexChild2, ComplexChild3 and its children"); + } + + std::cout << "\nTree after scope out:\n"; + printTree(root); + std::cout << "\n"; + + logger.info("Unlinking ComplexChild1 and ComplexChild3...\n"); + child1->unlinkParent(); + for (auto child : root->getLink()->getChildren()) { + NodePtr childNode = std::dynamic_pointer_cast(child); + if (childNode->name() == "ComplexChild3") { + childNode->unlinkParent(); + break; + } + } + + auto child4 = std::make_shared("SimpleChild4"); + root->linkChild(child4); + + std::cout << "\nTree flush and link SimpleChild4:\n"; + printTree(root); + std::cout << "\n"; + } + + logger.info("Exited main scope. All smart pointers destroyed."); + + return 0; +}