Краткий обзор объектно-ориентированного проектирования программного обеспечения

1656638542 kratkij obzor obektno orientirovannogo proektirovaniya programmnogo obespecheniya

Станислав Козловский

Демонстрируется путем выполнения занятий ролевой игры

1*DuIQNSG6UjcEXpskd4_dEQ
Цеппелин Ричарда Райта

Введение

Большинство современных языков программирования поддерживают и поощряют объектно-ориентированное программирование (ООП). Несмотря на то, что в последнее время мы наблюдаем небольшой отход от этого, поскольку люди начинают использовать языки, которые не сильно под влиянием ООП (таких как Go, Rust, Elixir, Elm, Scala), большинство по-прежнему имеют объекты. Принципы проектирования, которые мы собираемся описать здесь, также применяются к не-ООП языкам.

Чтобы добиться успеха в написании четкого, высококачественного кода, который можно поддерживать и расширять, вам нужно знать о принципах проектирования, доказавших свою эффективность в течение десятилетий опыта.

Раскрытие информации: Пример, который мы собираемся рассмотреть, будет в Python. Примеры существуют, чтобы подтвердить мнение, и могут быть неаккуратны в других, очевидных случаях.

Типы объектов

Поскольку мы будем моделировать наш код вокруг объектов, было бы полезно различать их разные обязанности и вариации.

Существует три вида объектов:

1. Entity Object

Этот объект, как правило, соответствует определенной сущности реального мира в проблемном пространстве. Скажем, мы создаем ролевую игру (RPG), объект сущности будет нашим простым Hero класс:

Эти объекты обычно содержат свойства о себе (например health или mana) и их можно изменять с помощью определенных правил.

2. Объект управления

Объекты управления (иногда их также называют Менеджер объектов) отвечают за координацию других объектов. Это объекты, которые КОНТРОЛЬ и использовать другие предметы. Прекрасным примером в нашей аналогии с RPG может быть Fight класс, управляющий двумя героями и заставляющий их сражаться.

Инкапсуляция логики боя в таком классе дает вам многочисленные преимущества, одно из которых – легкое расширение действия. Вы можете очень легко передать неигровой тип персонажа (NPC), чтобы герой мог сразиться, если он предоставляет тот же API. Вы также можете легко унаследовать класс и переопределить некоторые функциональные возможности в соответствии с вашими потребностями.

3. Пограничный объект

Это объекты, которые находятся на грани вашей системы. Любой объект, получающий входные данные от другой системы или создающий исходные данные для другой системы, независимо от того, является ли эта система пользователем, Интернетом или базой данных, можно классифицировать как предельный объект.

Эти граничные объекты отвечают за перевод информации в нашу систему и из нее. В примере, где мы используем команды пользователя, нам понадобится предельный объект, чтобы перевести ввод с клавиатуры (например, пробел) в распознаваемое событие домена (например, скачок символа).

Бонус: объект ценности

Объекты значения представляют собой простое значение в домене. Они неизменны и не обладают идентичностью.

Если бы мы включили их в нашу игру, a Money или Damage класс отлично подойдет. Указанные объекты позволяют нам легко различать, находить и налаживать связанные функции, в то время как наивный подход к использованию примитивного типа — массива целых чисел — нет.

Их можно отнести к подкатегории Entity объектов.

Основные принципы проектирования

Принципы проектирования – это правила разработки программного обеспечения, которые на протяжении многих лет доказали свою ценность. Строгое их соблюдение поможет вам убедиться, что ваше программное обеспечение имеет самое высокое качество.

Абстракция

Абстракция – это идея упрощения концепции к ее основному в определенном контексте. Это позволяет лучше понять концепцию, сводя ее к упрощенной версии.

Приведенные выше примеры иллюстрируют абстракцию – посмотрите, как Fight класс структурирован. Способ его использования максимально прост – вы предоставляете ему два героя в качестве аргументов в экземпляре и вызываете fight() метод. Ни больше, ни меньше.

Абстракция в вашем коде должна соответствовать правилу малейшей неожиданности. Ваша абстракция не должна никого удивлять ненужным и несвязанным поведением/свойствами. Иными словами, это должно быть интуитивно понятным.

Обратите внимание, что наш Hero#take_damage() функция не делает что-то неожиданное, например удаление нашего персонажа после смерти. Но мы можем ожидать, что это убьет нашего персонажа, если его здоровье станет ниже нуля.

Инкапсуляция

Инкапсуляцию можно рассматривать как помещение чего-либо внутрь капсулы – вы ограничиваете ее влияние внешнему миру. В программном обеспечении ограничение доступа к внутренним объектам и свойствам способствует целостности данных.

Инкапсуляция блокирует внутреннюю логику и облегчает управление вашими классами, поскольку вы знаете, какая часть используется другими системами, а какая нет. Это означает, что вы можете легко переделать внутреннюю логику, сохраняя публичные части и быть уверенным, что вы ничего не сломали. Как побочный эффект, работа с инкапсулированной функциональностью извне становится более простой, поскольку у вас меньше вещей, о которых нужно думать.

В большинстве языков это делается с помощью так называемых модификаторов доступа (private, protected и т.п.). Python не является лучшим примером этого, поскольку ему не хватает таких явных модификаторов, встроенных в среду выполнения, но мы используем конвенции для решения этой проблемы. The _ префикс к переменным/методам обозначает их как частные.

К примеру, представьте, что мы изменим свой Fight#_run_attack метод для возвращения логической переменной, указывающей, закончилась ли борьба, а не вызывает исключительную ситуацию. Мы будем знать, что единственный код, который мы могли взломать, находится внутри Fight класса, поскольку мы сделали метод частным.

Помните, что код меняется чаще, чем пишется заново. Возможность изменять свой код с более четкими и минимальными последствиями — это гибкость, которую вы хотите как разработчик.

разложение

Декомпозиция – это действие расщепления объекта на несколько отдельных меньших частей. Указанные части легче понять, поддерживать и программировать.

Представьте, что мы хотели включить больше функций RPG, таких как баффы, инвентарь, снаряжение и атрибуты персонажей в дополнение к нашей Hero:

Я предполагаю, что вы можете сказать, что этот код становится довольно беспорядочным. Наши Hero объект делает слишком многое одновременно, и в результате этого код становится достаточно хрупким.

К примеру, одно очко выносливости стоит 5 здоровья. Если мы когда-нибудь захотим изменить это в будущем, чтобы оно стоило 6 здоровью, нам нужно будет изменить реализацию в нескольких местах.

Ответ состоит в том, чтобы разложить Hero объект на несколько меньших объектов, каждый из которых содержит определенную функциональность.

1*etyn_SN7_v4zqGDbeazJVA
Более чистая архитектура

Теперь, после декомпозиции функциональности нашего объекта Hero на HeroAttributes, HeroInventory, HeroEquipmentи HeroBuff объектов, добавление будущей функциональности будет более легким, более инкапсулированным и лучше абстрагированным. Вы можете сказать, что наш код намного чище и понятнее того, что он делает.

Существует три типа декомпозиционных соотношений:

  • объединение — Определение свободной связи между двумя компонентами. Оба компонента не зависят друг от друга, но могут работать вместе.

пример: Hero и а Zone объект.

  • агрегация — Определяет слабую связь «есть» между целым и его частями. Считается слабым, потому что части могут существовать без целого.

пример: HeroInventory и Item.
А HeroInventory может иметь много Items и ан Item может принадлежать любому HeroInventory(к примеру, предметы торговли).

  • состав — Прочная связь есть, где целое и часть не могут существовать друг без друга. Части нельзя разделять, поскольку целое зависит от этих частей.

пример: Hero и HeroAttributes.
Это атрибуты Героя – вы не можете изменить их владельца.

Обобщение

Обобщение может быть важнейшим принципом проектирования – это процесс извлечения общих характеристик и сочетания их в одном месте. Все мы знаем о концепции функций и наследовании классов — оба они своего рода обобщения.

Сравнение может прояснить ситуацию: пока абстракция уменьшает сложность, скрывая ненужные детали, обобщение уменьшает сложность путем замены нескольких сущностей, выполняющих подобные функции, одной конструкцией.

В приведенном примере мы обобщили наш общий Hero и NPC функциональность классов у общего предка под названием Entity. Это всегда достигается путём наследования.

Здесь вместо того, чтобы иметь наш NPC и Hero классы реализуют все методы дважды и нарушают принцип DRY, мы снизили сложность, переместив их общую функциональность в базовый класс.

Как предупреждение – не переусердствуйте с наследственностью. Многие опытные люди рекомендуют отдавать предпочтение композиции, а не наследованию.

Наследованием часто злоупотребляют программисты-любители, вероятно потому, что это одна из первых техник ООП, которую они постигают из-за ее простоты.

Композиция

Композиция – это принцип объединения нескольких объектов в более сложный. Практически говоря, это создание экземпляров объектов и использование их функциональности вместо прямого наследования.

Объект, использующий композицию, можно назвать a сложенный объект. Важно, что этот композит проще суммы аналогов. При объединении нескольких классов в один мы хотим повысить уровень абстракции и сделать объект более простым.

API составленного объекта должно скрывать его внутренние компоненты и взаимодействие между ними. Подумайте о механических часах, они имеют три стрелки для показа времени и одну ручку для настройки, но внутри содержит десятки подвижных и взаимосвязанных частей.

Как я уже сказал, композиция лучше перед наследованием, а это значит, что вы должны стремиться переместить общую функциональность в отдельный объект, который затем используют классы, а не хранить его в базовом классе, который вы унаследовали.

Давайте проиллюстрируем возможную проблему с чрезмерным наследованием функций:

Мы просто добавили движения в нашу игру.

Как мы узнали, вместо дублирования кода мы использовали обобщение для размещения move_right и move_left функции в Entity класс.

Ладно, а что если мы захотим ввести в игру маунтов?

1*dK8x5H7sJF-3px7cbJ46Jw
хорошее крепление 🙂

Верховые животные также должны двигаться влево и вправо, но не обладают способностью атаковать. Подумайте – может и здоровья не иметь!

Я знаю, каково ваше решение:

Просто переместите move логику в отдельности MoveableEntity или MoveableObject класс, имеющий только эту функциональность. The Mount класс может унаследовать это.

Тогда что мы делаем, если нам нужны верха, которые имеют здоровье, но не могут атаковать? Больше деления на подклассы? Надеюсь, вы видите, как наша иерархия классов начинает становиться сложной, хотя наша бизнес-логика все еще достаточно проста.

Несколько лучшим подходом было бы абстрагировать логику движения в a Movement класса (или другого лучшего названия) и создайте его в классах, которым это может пригодиться. Это прекрасно упаковает функциональность и сделает ее многократной для всех видов объектов, не ограничиваясь ими Entity.

Ура, склад!

Оговорки относительно критического мышления

Несмотря на то, что эти принципы проектирования были сформированы десятилетиями опыта, все равно важно, чтобы вы умели критически мыслить, прежде чем слепо применять принцип к своему коду.

Как и все вещи, слишком многое может быть плохой вещью. Иногда принципы могут зайти слишком далеко, вы можете стать слишком умными с ними и в конечном итоге получить что-то, с чем действительно труднее работать.

Как инженер, вашей главной чертой является критически оценивать лучший подход для вашей уникальной ситуации, а не слепо соблюдать и применять произвольные правила.

Сплоченность, сцепление и разделение интересов

Сплоченность

Сплоченность представляет собой четкость обязанностей в модуле или другими словами – его сложность.

Если ваш класс выполняет одно задание и ничего другого или имеет четкую цель, этот класс имеет высокая сплоченность. С другой стороны, если он несколько неясен в том, что он делает, или имеет более одной цели — он имеет низкая сплоченность.

Вы хотите, чтобы в ваших классах была высокая сплоченность. У них должна быть только одна ответственность, и если вы поймаете, что у них больше, возможно, пора разделить их.

Сцепление

Связывание фиксирует сложность подключения разных классов. Вы хотите, чтобы ваши классы имели как можно меньше и более простые связи с другими классами, чтобы вы могли заменить их в будущих событиях (например, изменение веб-фреймворков). Цель – мать слабое сцепление.

Во многих языках это достигается интенсивным использованием интерфейсов — они отвлекаются от конкретного класса, который обрабатывает логику, и представляют своего рода уровень адаптера, к которому любой класс может подключиться.

Обособление интересов

Распределение проблем (SoC) – это идея, согласно которой программная система должна быть разделена на части, не пересекающиеся по функциональности. Или, как следует из названия, «беспокойство» — общий срок по всему, что обеспечивает решение проблемы должны быть разделены в разные места.

Хорошим примером этого является веб-страница – она имеет три уровня (информация, презентация и поведение), разделенные на три места (HTML, CSS и JavaScript соответственно).

Если посмотреть еще раз на RPG Hero К примеру, вы увидите, что в самом начале было много проблем (применение бафов, вычисление ущерба от атаки, обработка инвентаря, снаряжение предметов, управление атрибутами). Мы разделили эти проблемы разложение в больше сплоченный классы, которые абстрактный и инкапсулировать их детали. Наши Hero Класс теперь действует как составной объект и гораздо проще, чем раньше.

Расплачиваться

Применение таких принципов может выглядеть слишком сложным для такого маленького куска кода. Правда состоит в том, что а должны для любого программного проекта, который вы планируете разрабатывать и поддерживать в будущем. Написание такого кода имеет несколько накладных расходов в самом начале, но окупается многократно в долгосрочной перспективе.

Эти принципы гарантируют, что наша система больше:

  • Расширяемый: Высокая сплоченность облегчает внедрение новых модулей, не беспокоясь о несвязанных функциях. Низкое сцепление означает, что новый модуль имеет меньшее количество материалов для подключения, поэтому его легче реализовать.
  • Ремонтопригодный: Низкое сцепление гарантирует, что изменение в одном модуле не повлияет на другие. Высокая сплоченность гарантирует, что изменение системных требований потребует изменения как можно меньшего количества классов.
  • Многократное использование: Высокая сплоченность гарантирует, что функциональность модуля полна и четко определена. Низкое сцепление делает модуль менее зависимым от остальной системы, облегчая его повторное использование в другом программном обеспечении.

Резюме

Мы начали с представления некоторых основных типов высокого уровня (Entity, Boundary и Control).

Затем мы научились ключевым принципам структурирования указанных объектов (абстракция, обобщение, композиция, декомпозиция и инкапсуляция).

Для дальнейших действий мы представили два показателя качества программного обеспечения (соединение и сплоченность) и узнали преимущества применения этих принципов.

Я надеюсь, что эта статья предоставила полезный обзор некоторых принципов проектирования. Если вы хотите продолжить обучение в этой области, вот некоторые ресурсы, которые я бы порекомендовал.

Последующая литература

Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения – пожалуй, самая влиятельная книга в этой области. Немного устаревшие примеры (C++ 98) но шаблоны и идеи остаются очень актуальными.

Развитие объектно-ориентированного программного обеспечения с помощью тестов – отличная книга, которая показывает, как практически применять принципы, изложенные в этой статье (и другие), работая над проектом.

Эффективный дизайн программного обеспечения – первоклассный блог, содержащий гораздо больше, чем только идеи дизайна.

Специализация по разработке и архитектуре программного обеспечения – отличная серия из 4 видеокурсов, которые научат вас эффективному дизайну на протяжении всего его применения в проекте, охватывающем все четыре курса.

Если этот обзор был для вас информативным, пожалуйста, подумайте о том, чтобы поздравить его столько, сколько, по вашему мнению, он заслуживает, чтобы больше людей могли встретить его и получить от него пользу.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *