Шаблоны проектирования ☀️ в C#. ООП. Тестирование. Коллекции.

Основы объектно-ориентированного программирования, а также шаблоны программирования в C# с примерами их реализации, тестирование, объянение принципов и решение задач LetCode


Project maintained by Dvurechensky-Docs Hosted on GitHub Pages — Theme by mattgraham

Typing SVG

Static Badge

🔱 Шаблоны проектирования. ООП. Тестирование. Коллекции. 🔱

🏷️ Основные типы в C#

Примеры

Тип Определение Ключевые моменты
class 🔎 Ссылка на объект, который может содержать поля, методы, свойства, события. - Ссылка хранится в куче.
- Поддерживает наследование и полиморфизм.
- Может иметь конструкторы и деструкторы.
abstract class 🔎 Класс, который не может быть инстанцирован напрямую и может содержать абстрактные методы. - Используется как базовый класс.
- Может содержать как абстрактные, так и обычные методы.
- Подклассы должны реализовать абстрактные методы.
struct 🔎 Значимый тип, который хранится на стеке и обычно используется для небольших объектов. - Не поддерживает наследование от других struct.
- Может реализовывать интерфейсы.
- Используется для легковесных объектов.
interface 🔎 Контракт, определяющий набор методов, свойств и событий, которые должен реализовать класс или struct. - Не содержит реализации (кроме default методов в C# 8+).
- Позволяет реализовать полиморфизм.
- Поддерживает множественную реализацию.
record 🔎 Ссылка или значимый тип с неизменяемыми свойствами и встроенной поддержкой сравнения по значению. - Идеален для DTO и immutable объектов.
- Поддерживает паттерн deconstruction.
- Можно использовать с record class и record struct.

🏛 Принципы ООП

Примеры

Принцип Определение Ключевые моменты / Примеры
💢 Encapsulation (Инкапсуляция) 🔎 Сокрытие внутреннего состояния объекта и предоставление доступа через контролируемые методы (getter/setter). - Защищает данные от некорректного изменения.
- Примеры: private fields + public методы.
- Позволяет менять реализацию, не ломая API.
💢 Abstraction (Абстракция) 🔎 Выделение существенных характеристик объекта, скрывая детали реализации. - Интерфейсы, абстрактные классы.
- Клиент работает только с абстракцией, не зная конкретной реализации.
💢 Inheritance (Наследование) 🔎 Механизм создания нового класса на основе существующего, унаследовав его свойства и методы. - Позволяет повторно использовать код.
- Обеспечивает иерархию типов.
- Важно не нарушать Liskov Substitution Principle.
💢 Polymorphism (Полиморфизм) 🔎 Возможность объектов разных классов использовать один и тот же интерфейс, реагируя на одинаковые методы по-разному. - Compile-time (overloading), runtime (overriding).
- Позволяет писать гибкий и расширяемый код.
- Пример: метод Draw() у Circle и Rectangle.
💢 Composition over Inheritance (Композиция вместо наследования) 🔎 Предпочтение включения объектов в качестве полей, а не наследования, для повторного использования функционала. - Улучшает гибкость.
- Позволяет менять поведение во время выполнения.
- Часто используется с паттернами (Strategy, Decorator).
💢 SOLID (обобщение) 🔎 Пять принципов, обеспечивающих устойчивую архитектуру: SRP, OCP, LSP, ISP, DIP. - SRP – один класс = одна ответственность.
- OCP – открыты для расширения, закрыты для изменения.
- LSP – наследники заменяют базовый класс без слома.
- ISP – интерфейсы узкоспециализированные.
- DIP – зависимость на абстракциях, а не на деталях.
💢 DRY (Don’t Repeat Yourself - Не повторяйся) 🔎 Избегать дублирования кода, данных или логики. - Любой элемент системы должен иметь единственное, недвусмысленное представление.
- Дублирование ведёт к багам и трудной поддержке.
- Используются функции, классы, наследование, утилитарные методы.
💢 KISS (Keep It Simple, Stupid - Будь проще, глупый) 🔎 Старайтесь делать решения простыми и понятными. - Сложность усложняет поддержку и тестирование.
- Минимизировать лишние абстракции.
- Простые решения легче рефакторить и расширять.
💢 YAGNI (You Aren’t Gonna Need It - Тебе это не понадобится) 🔎 Не добавляй функциональность, пока она реально не нужна. - Предотвращает перегрузку кода.
- Избегает ненужных абстракций.
- Часто сочетается с KISS.
💢 LoD (Law of Demeter - Принцип минимальной связи) 🔎 Объект должен взаимодействовать только с непосредственными «друзьями», а не с объектами объектов. - Сокращает связанность модулей.
- Пример: a.b.c() → лучше a.c() через делегирование.
- Улучшает поддерживаемость и тестируемость.
💢 POLA (Principle of Least Astonishment – Принцип минимального удивления) 🔎 Поведение системы должно быть предсказуемым и логичным для разработчиков и пользователей. - API должен работать так, как ожидает пользователь.
- Нестандартные названия методов → нарушение POLA.
- Собес: «Почему важно?» → уменьшает баги и кривой код.
💢 TDA (Tell, Don’t Ask - Скажи, а не спрашивай) 🔎 Говорите объекту, что делать, а не вытаскивайте его состояние и решайте что с ним делать снаружи. - account.Deposit(100) лучше, чем balance = account.GetBalance(); balance += 100;
- Уменьшает нарушаемость инкапсуляции.
💢 SoC 🔎 Каждый модуль / класс должен решать только одну задачу. - Архитектура MVC / MVVM.
- Меньше багов, легче рефакторить.
- Собес: «Как проверяешь соблюдение?» → через тесты и код-ревью.
💢 FF/FE (Fail Fast / Fail Early - Быстрый / Ранний провал) 🔎 Система должна обнаруживать ошибки как можно раньше. - Проверки аргументов (ArgumentNullException).
- Собес: «Зачем?» → уменьшает последствия ошибок и упрощает отладку.
💢 CQS (Command Query Separation - Разделение команд и запросов) 🔎 Команда и запрос разделены. - Метод либо изменяет состояние (command), либо возвращает данные (query), но не одновременно.

🐬 Принципы SOLID

Название Краткое описание
Single responsibility
💢 Принцип единственной ответственности
🔎 Каждый модуль или класс должен иметь единую ответственность и выполнять её полностью. Через делегирование достигается чистота ответственности. SRP работает на уровне причин изменения, а не просто функций.
Open-closed
💢 Принцип открытости/закрытости
🔎 Программные сущности должны быть открыты для расширения, но закрыты для изменения: новые функциональные возможности добавляются через расширение, не изменяя существующий проверенный код. Применяется через абстракции, интерфейсы, наследование, паттерны проектирования.
Liskov substitution
💢 Принцип подстановки Барбары Лисков
🔎 Объекты наследника должны полностью заменять объекты базового класса, не нарушая ожидаемое поведение программы, включая предусловия, постусловия и инварианты.
Interface Segregation
💢 Принцип разделения интерфейса
🔎 Интерфейсы должны быть узкоспециализированными, а клиенты не должны зависеть от методов, которые они не используют.
Dependency inversion
💢 Принцип инверсии зависисмостей
🔎 Высокоуровневые модули не должны зависеть от модулей низкого уровня; оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.

🐋 SRP (SOLID) vs SoC (Global) пояснение

Принцип Уровень применения Основная идея
SRP Класс / модуль Один класс = одна причина для изменения
SoC Архитектура / слой / система Разделяем ответственность на независимые компоненты и слои

⌛ Паттерны C#

✨ Другие

🌐 Behavioral - Поведенческие · 11 🌐

Паттерн Краткое определение
💢 ChainOfResponsibility - Цепочка обязанностей 🔎 Избегает связывание отправителя запроса с его получателем, давая возможность обработать запрос более чем одному объекту.
💢 Command - Команда 🔎 Инкапсулирует запрос в виде объекта, позволяя передавать их клиентам в качестве параметров, ставить в очередь, логировать, а также поддерживать отмену операций.
💢 Interpreter - Интерпретатор 🔎 Получая формальный язык, определяет представление его грамматики и интерпретатор, использующий это представление для обработки выражений языка.
💢 Iterator - Итератор 🔎 Предоставляет способ последовательного доступа к множеству, независимо от его внутреннего устройства.
💢 Mediator - Посредник 🔎 Определяет объект, инкапсулирующий способ взаимодействия объектов. Обеспечивает слабую связь, избавляя их от необходимости ссылаться друг на друга, и даёт возможность независимо изменять их взаимодействие.
💢 Memento - Хранитель 🔎 Не нарушая инкапсуляцию, определяет и сохраняет внутреннее состояние объекта и позволяет позже восстановить объект в этом состоянии.
💢 Observer - Наблюдатель 🔎 Определяет зависимость один ко многим между объектами так, что когда один меняет своё состояние, все зависимые объекты оповещаются и обновляются автоматически.
💢 State - Состояние 🔎 Позволяет объекту изменять своё поведение в зависимости от внутреннего состояния.
💢 Strategy - Стратегия 🔎 Определяет группу алгоритмов, инкапсулирует их и делает взаимозаменяемыми. Позволяет изменять алгоритм независимо от клиентов, его использующих.
💢 TemplateMethod - Шаблонный метод 🔎 Определяет алгоритм, некоторые этапы которого делегируются подклассам. Позволяет подклассам переопределить эти этапы, не меняя структуру алгоритма.
💢 Visitor - Посетитель 🔎 Представляет собой операцию, которая будет выполнена над объектами группы классов. Даёт возможность определить новую операцию без изменения кода классов, над которыми эта операция производится.

💡 Creational - Порождающие · 6 💡

Паттерн Краткое определение
💢 AbstractFactory - Абстрактная фабрика 🔎 Предоставляет интерфейс для создания групп связанных или зависимых объектов, не указывая их конкретный класс.
💢 Builder - Строитель 🔎 Разделяет создание сложного объекта и его инициализацию так, что одинаковый процесс построения может создавать объекты с разным состоянием.
💢 FactoryMethod - Фабричный метод 🔎 Определяет интерфейс для создания объекта, но позволяет подклассам решать, какой класс создавать. Позволяет делегировать создание класса объектам класса.
💢 FluentBuilder - Гибкий (плавный, текучий) строитель 🔎 Позволяет упростить процесс создания сложных объектов с помощью методов-цепочек, которые наделяют объект определенным качеством.
💢 Prototype - Прототип 🔎 Определяет несколько видов объектов, чтобы при создании использовать объект-прототип и создавать новые объекты, копируя прототип (техника клонирования объектов).
💢 Singleton - Одиночка 🔎 Гарантирует, что класс имеет только один экземпляр, и представляет глобальную точку доступа к нему.

🏩 Structural - Структурные · 7 🏩

Паттерн Краткое определение
💢 Adapter - Адаптер 🔎 Конвертирует интерфейс класса в другой интерфейс, ожидаемый клиентом. Позволяет классам с разными интерфейсами работать вместе.
💢 Bridge - Мост 🔎 Разделяет абстракцию и реализацию так, чтобы они могли изменяться независимо друг от друга.
💢 Composite - Компоновщик 🔎 Компонует объекты в древовидную структуру по принципу “часть-целое”, представляя их в виде иерархии. Позволяет клиенту одинаково обращаться как к отдельному, так и к целому поддереву.
💢 Decorator - Декоратор 🔎 Динамически предоставляет объекту дополнительные возможности. Гибкая альтернатива наследованию для расширения функциональности.
💢 Facade - Фасад 🔎 Предоставляет единый интерфейс к группе интерфейсов подсистемы. Делает систему проще для использования.
💢 Flyweight - Приспособленец 🔎 Благодаря совместному использованию поддерживает эффективную работу с большим количеством объектов (оптимизация памяти).
💢 Proxy - Заместитель 🔎 Предоставляет объект-заместитель другого объекта для контроля доступа к нему.

⌛ Типы коллекций ✨ Dictionary ✨

Код

Коллекция Назначение / Описание Особенности
💢 Dictionary<TKey, TValue> 🔎 Хранит пары ключ-значение, обеспечивает быстрый доступ по ключу. - Ключи уникальны.
- Быстрый поиск, вставка и удаление (обычно O(1)).
- Нет гарантии порядка элементов.
💢 SortedDictionary<TKey, TValue> 🔎 Хранит пары ключ-значение, упорядоченные по ключу. - Ключи уникальны.
- Элементы автоматически сортируются.
- Операции вставки и поиска медленнее, чем в Dictionary.
💢 ConcurrentDictionary<TKey, TValue> 🔎 Потокобезопасная коллекция для многопоточного доступа. - Безопасна для параллельного чтения и записи.
- Методы: TryAdd, TryRemove, GetOrAdd.
- Используется в многопоточных приложениях.
💢 ReadOnlyDictionary<TKey, TValue> 🔎 Обёртка над Dictionary, предоставляющая только чтение. - Изменять коллекцию нельзя.
- Используется для безопасной передачи наружу.
💢 ListDictionary 🔎 Маленький словарь (до ~10 элементов), реализованный как связанный список. - Оптимален для небольшого количества элементов.
- Последовательный поиск (O(n)).
- Используется в старых .NET приложениях.
💢 HybridDictionary 🔎 Комбинация ListDictionary и Hashtable. - Маленькие коллекции хранятся как ListDictionary.
- При росте коллекции автоматически преобразуется в Hashtable.
- Оптимизация под маленькие и средние словари.

🧪 Unit-тестирование – краткая шпаргалка

Пример

Термин / Инструмент Описание / Назначение
💢 Unit-тест Автоматический тест для отдельного модуля или метода, проверяет его корректность независимо от других частей системы.
💢 Arrange-Act-Assert (AAA) Структура теста:
- Arrange – подготовка данных и состояния.
- Act – выполнение тестируемого метода.
- Assert – проверка результата.
💢 Mock / Stub Заглушки для зависимостей:
- Mock – проверяет, что методы вызваны.
- Stub – возвращает фиксированные данные.
💢 Frameworks Инструменты для тестирования:
- NUnit, xUnit, MSTest – популярные фреймворки для C#.
💢 Плюсы Unit-тестов - Быстрая проверка кода.
- Легче отлавливать баги.
- Позволяет безопасно рефакторить.
💢 Минусы / ограничения - Не тестируют интеграцию с другими модулями.
- Требуют поддержки и актуальности тестов.
💢 TDD (Test-Driven Development) Подход разработки через тесты:
1. Пишем тест.
2. Реализуем функционал.
3. Рефакторим код.

🦖 Решение LetCode задач

✨Dvurechensky✨