Проектирование в программной инженерии — это процесс создания архитектуры системы. Оно включает в себя выбор структур данных, алгоритмов, интерфейсов и других элементов, которые обеспечивают функциональность и производительность программного продукта.
- Архитектурное проектирование: Определение общей структуры системы, ее компонентов и их взаимодействий.
- Проектирование компонентов: Разработка отдельных модулей или компонентов системы.
- Проектирование интерфейсов: Определение способов взаимодействия между компонентами системы.
- Простота: Проект должен быть максимально простым и понятным.
- Модульность: Система должна состоять из отдельных, легко заменяемых модулей.
- Расширяемость: Легкость добавления нового функционала.
- Повторное использование: Возможность использования компонентов в других проектах.
OOA - объектно-ориентированный анализ. Если страшной терминологией, то это методология, при которой требования к системе воспринимаются с точки зрения классов и объектов, выявленных в предметной области.
Если человеческими словами, то это о том, что бы понять какие у нас вообще будут объекты, будут ли они между собой взаимодействовать, и если будут, то какие и как
ООД - объектно-ориентированный дизайн. Опять же, если страшными терминами, то это методология проектирования, соединяющая в себе процесс объектной декомпозиции и приемы представления логической и физической, а также статической и динамической моделей проектируемой системы.
Если человеческими словами, то это о том, что бы продумать какие паттерны проектирования мы можем применить для того что бы наши объекты взаимодействовали максимально грамотно/быстро. Как добиться того, что бы у нас появились те самые
простота,модульность,расширяемостьиповторное использование.
ООА - это оценка того, какие проблемы должна решать система и какие сущности у нас вообще существуют (допустим, при проектировании интернет-магазина нужно понимать, что у нас будут сущности пользователя, товара, заказа и т. д.)
ООД - это проектирование необходимых классов и того, как они будут взаимодействовать. (Понимание, что заказ будет совершать пользователь, при этом заказ может быть розничным и оптовым, и для того, чтобы их просчитать, необходима различная логика, какие паттерны мы можем применить (об этом немного ниже))
ООП в этой схеме - это конкретная реализация того, что было продумано на этапе ООД, основываясь на принципах ООП (садимся и пишем код и тесты).
Вне зависимости от того, что вы разрабатываете, всегда можно применять два принципа:
KISS = Keep it simple, stupid (Чем проще, тем лучше! Если всё можно описать двумя классами, в которых 3 метода, то не надо описывать 10 классов с 30-ю методами.)
DRY = Don't repeat yourself (Не повторяйся! Если ты используешь один и тот же код в разных местах, сделай из него функцию или метод)
Существует очень много различных подходов к разработке. Разбирать их детально, мы не будем.
Но посмотрим на два распространенных подхода Data-driven development и Test-driven development.
Это классический подход к разработке приложений, где во главе угла стоят данные.
Все действия описаны на схеме
- Планируем что-то сделать
- Анализируем полученный на этапе планирования запрос
- Проектируем проект
- Имплементируем (пишем код)
- Пишем тесты
- Поддерживаем приложение
Test-driven development - это подход к разработке, когда тесты пишутся до кода. Смысл в том, что если проектирование было проведено правильно, то ты заранее знаешь, какие в твоей системе будут действия, и как система должна реагировать на разные действия. Тогда можно написать тесты, которые будут отвечать требованиям проектирования, и только после этого писать код, который будет соответствовать уже написанным тестам.
В этом случае шаги 4 и 5 меняются местами. Такой подход встречается реже, но так тоже делают, поэтому не удивляемся когда видим такие термины.
SOLID - это свод пяти основных принципов ООП, введенный Майклом Фэзерсом в начале нулевых. Эти принципы — часть общей стратегии гибкой и адаптивной разработки, их соблюдение облегчает расширение и поддержку проекта.
SOLID принципы советуют, как проектировать модули.
Цель принципов — проектировать модули, которые:
- способствуют изменениям
- легко понимаемы
- повторно используемы
A module should be responsible to one, and only one, actor.
Старая формулировка: A module should have one, and only one, reason to change.
Принцип единой ответственности
Часто ее трактовали следующим образом: Модуль должен иметь только одну обязанность.
Каждый класс должен решать одну конкретную задачу. Программист не должен одновременно быть уборщицей и поваром.
Один класс одна ответственность!
A software artifact should be open for extension but closed for modification.
Принцип открытости/закрытости гласит, что программные сущности должны быть открыты для расширения, но закрыты для
модификации. Это означает, что мы можем добавлять новый функционал, не изменяя существующий код.
Принцип подстановки Барбары Лисков - класс потомок должен иметь возможность заменить родителя.
Если мы из отвертки, сделали класс мультитул, значит что мультитул тоже должен уметь закручивать шурупы.
Если класс родитель делал кофе, а класс потомок начал продавать наркотики, то что-то у нас пошло не так.
Make fine grained interfaces that are client specific.
Приницип разделения интерфесов - Представте себе кофемашину, у которой всего одна кнопка(интерфейс) и что-бы сделать
капучино нужно нажать на нее 4 раза. А что бы латте, надо зажать кнопку два раза по 5 секунд. Удобно таким пользоваться?
Я думаю что катастрофически нет.
Если есть метод(функция), который при разных входных данных ведёт себя по-разному, то лучше написать несколько методов( функций).
Чем меньше нагружены функции/методы, тем проще их поддерживать и тестировать.
Depend on abstractions, not on concretions.
Принцип инверсии зависимостей. Если страшными словами, то модули верхних уровней не должны зависеть от модулей нижних
уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны
зависеть от абстракций.
Если нормальным языком. В вашей реализации должно быть как можно меньше деталей до момента когда эти детали необходимы.
Возьмем пример с бургером. Если ваш базовый класс бургера содержит сразу все возможные начинки, соусы и все виды мяса, причем по 5 штук, а что бы сделать конкретный бургер, нужно убрать все лишнее, будет ли удобно пользоваться таким классом?
Я думаю что нет. В этом и суть, детали появляются только в тот момент когда они нужны. Но на более верхнем уровне вполне может быть описана абстракция. В бургере могут быть, соус, мясо, овощи, сыр итд. Но это не значит что они сразу там должны быть.
Принципы SOLID стремятся свести изменение модулей к их добавлению и удалению.
Принципы SOLID способствуют откладыванию принятия технических решений и разделению труда программистов.
Таким образом:
- Принцип единственной ответственности (Single responsibility)
На каждый объект должна быть возложена одна единственная обязанность
Для этого проверяем, сколько у нас есть причин для изменения класса — если больше одной, то следует разбить данный класс.
- Принцип открытости/закрытости (Open-closed)
Программные сущности должны быть открыты для расширения, но закрыты для модификации
Для этого представляем наш класс как «черный ящик» и смотрим, можем ли в таком случае изменить его поведение.
- Принцип подстановки Барбары Лисков (Liskov substitution)
Объекты в программе могут быть заменены их наследниками без изменения свойств программы
Для этого проверяем, не усилили ли мы предусловия и не ослабили ли постусловия. Если это произошло — то принцип не соблюдается.
- Принцип разделения интерфейса (Interface segregation)
Много специализированных интерфейсов лучше, чем один универсальный
Проверяем, насколько много интерфейс содержит методов и насколько разные функции накладываются на эти методы, и если необходимо — разбиваем интерфейсы.
- Принцип инверсии зависимостей (Dependency Invertion)
Зависимости должны строиться относительно абстракций, а не деталей
Проверяем, зависят ли классы от каких-то других классов (непосредственно инстанцируют объекты других классов и т. д.) и если эта зависимость имеет место, заменяем на зависимость от абстракции.
На самом деле, паттерн - это просто любая шаблонная конструкция, которую можно использовать несколько раз. И вы даже знаете несколько паттернов, только не знаете, что это паттерны.
Например, декоратор, итератор, генератор (нет, не все паттерны заканчиваются на -ратор). Но это те вещи которые вы уже
видели. Декоратор, это реализация паттерна. Если вы запускали цикл фор, то вы запускали паттерн итератор. А если у вас
там был написан range то вы использовали паттерн генератор.
Паттернов существует просто огромное количество, настолько огромное, что существуют сотни книг по паттернам проектирования. Тут можно посмотреть на многие из них, но далеко не на все.
Паттерн проектирования — это часто встречающееся решение определённой проблемы при проектировании архитектуры программ.
В отличие от готовых функций или библиотек паттерн нельзя просто взять и скопировать в программу. Паттерн представляет собой не какой-то конкретный код, а общую концепцию решения той или иной проблемы, которую нужно будет ещё подстроить под нужды вашей программы.
Паттерны часто путают с алгоритмами, ведь оба понятия описывают типовые решения каких-то известных проблем. Но если алгоритм — это чёткий набор действий, то паттерн — это высокоуровневое описание решения, реализация которого может отличаться в двух разных программах.
Если привести аналогии, то алгоритм — это кулинарный рецепт с чёткими шагами, а паттерн — инженерный чертёж, на котором нарисовано решение, но не конкретные шаги его реализации.
Описания паттернов обычно очень формальны и чаще всего состоят из таких пунктов:
- проблема, которую решает паттерн;
- мотивация к решению проблемы способом, который предлагает паттерн;
- структура классов, составляющих решение;
- пример на одном из языков программирования;
- особенности реализации в различных контекстах;
- связи с другими паттернами.
Такой формализм в описании позволил создать обширный каталог паттернов, проверив каждый из них на состоятельность.
Вы можете вполне успешно работать, не зная ни одного паттерна. Более того, вы могли уже не раз реализовать какой-то из паттернов, даже не подозревая об этом.
Но осознанное владение инструментом как раз и отличает профессионала от любителя. Вы можете забить гвоздь молотком, а можете и дрелью, если сильно постараетесь. Но профессионал знает, что главная фишка дрели совсем не в этом. Итак, зачем же знать паттерны?
-
Проверенные решения. Вы тратите меньше времени, используя готовые решения, вместо повторного изобретения велосипеда. До некоторых решений вы смогли бы додуматься и сами, но многие могут быть для вас открытием. -
Стандартизация кода. Вы делаете меньше просчётов при проектировании, используя типовые унифицированные решения, так как все скрытые проблемы в них уже давно найдены. -
Общий программистский словарь. Вы произносите название паттерна, вместо того, чтобы час объяснять другим программистам, какой крутой дизайн вы придумали и какие классы для этого нужны.
Паттерны отличаются по уровню сложности, детализации и охвату проектируемой системы. Проводя аналогию со строительством, вы можете повысить безопасность перекрёстка, поставив светофор, а можете заменить перекрёсток целой автомобильной развязкой с подземными переходами.
Самые низкоуровневые и простые паттерны — идиомы. Они не универсальны, поскольку применимы только в рамках одного языка программирования.
Самые универсальные — архитектурные паттерны, которые можно реализовать практически на любом языке. Они нужны для проектирования всей программы, а не отдельных её элементов.
-
Порождающиепаттерны беспокоятся о гибком создании объектов без внесения в программу лишних зависимостей. -
Структурныепаттерны показывают различные способы построения связей между объектами. -
Поведенческиепаттерны заботятся об эффективной коммуникации между объектами
Тут шикарный сайт с описанием некоторых паттернов и их реализации на разных языках программирования.
На сайте указанном выше, вы можете найти примеры, описания, варианты реализации на различных языках программирования. И лучше и детальнее я не напишу, так что рекомендую детально ознакомится с сайтом. Но давайте все таки обсудим некоторые паттерны на уровне идеи, зачем нужны и где вообще применимы.
Паттерн Одиночка гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
- Управление подключением к базе данных, чтобы избежать множества подключений и, как следствие, экономить ресурсы.
- Логирование, чтобы обеспечить централизованное управление логами.
- Настройки приложения, чтобы убедиться, что все части программы используют одни и те же параметры конфигурации.
Паттерн Фабричный метод предоставляет интерфейс для создания объектов в суперклассе, но позволяет подклассам изменять тип создаваемых объектов.
- В приложениях, где точные классы объектов могут меняться в зависимости от контекста или конфигурации.
- При необходимости создания множества различных объектов, которые имеют общий интерфейс.
Абстрактная фабрика предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов без указания их конкретных классов.
- В системах, где необходимо создавать группы связанных объектов (например, наборы GUI-элементов для различных платформ).
- В кроссплатформенных приложениях для создания объектов, зависящих от операционной системы.
Паттерн Строитель позволяет создавать сложные объекты поэтапно. Он отделяет конструирование объекта от его представления.
- В приложениях, где необходима пошаговая конфигурация сложных объектов (например, составление документов, создание отчетов).
- Для создания объектов с большим числом опциональных параметров.
Паттерн Прототип позволяет копировать объекты, не прибегая к их непосредственному созданию через конструктор.
- В системах, где создание объектов требует значительных затрат ресурсов (например, клонирование объектов в графических редакторах).
- В играх для клонирования игровых объектов с определенными характеристиками.
Паттерн Адаптер позволяет объектам с несовместимыми интерфейсами работать вместе. Он преобразует интерфейс одного класса в интерфейс, который ожидает клиент.
- Для интеграции старого кода в новые системы без изменения самого старого кода.
- Для работы с классами, интерфейсы которых отличаются от необходимых.
Паттерн Мост разделяет абстракцию и реализацию, позволяя им изменяться независимо друг от друга.
- В приложениях, где необходимо изменить реализацию абстракции во время выполнения.
- Для разработки кроссплатформенных GUI-приложений.
Паттерн Компоновщик позволяет объединять объекты в древовидные структуры для представления иерархий часть-целое. Клиенты могут одинаково работать как с отдельными объектами, так и с их комбинациями.
- В графических приложениях для построения сложных сцен из простых объектов.
- В системах управления файлами для работы с файловыми системами (директории и файлы).
Паттерн Декоратор позволяет динамически добавлять новые функциональности к объектам, не изменяя их классов.
- В текстовых редакторах для добавления форматирования к тексту.
- В веб-приложениях для добавления новых возможностей к существующим объектам запроса и ответа.
Паттерн Стратегия определяет семейство алгоритмов, инкапсулирует их и делает их взаимозаменяемыми. Паттерн позволяет изменять алгоритмы независимо от клиентов, которые их используют.
- В системах, где необходимо изменять алгоритмы сортировки или поиска.
- В играх для реализации различных стратегий поведения персонажей.
На этом материал заканчивается, давайте переходить ко второму модулю
Важно! Перед следующим занятием обязательно выполните все что описано по вот этой ссылке








