Введение в объектно-ориентированный дизайн с Java

Тимур Машнин, 2022

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

Оглавление

* * *

Приведённый ознакомительный фрагмент книги Введение в объектно-ориентированный дизайн с Java предоставлен нашим книжным партнёром — компанией ЛитРес.

Купить и скачать полную версию книги в форматах FB2, ePub, MOBI, TXT, HTML, RTF и других

Принципы ООД (Объектно-ориентированного дизайна)

Если вы хотите построить дом, вы не забьёте и гвоздя без проекта.

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

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

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

На протяжении многих лет люди пробовали множество подходов для упрощения проектирования.

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

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

Эта стратегия отображает процессы обработки данных в задаче на вызовы процедур.

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

И эти процедуры реализуются на определенном языке программирования.

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

Например, любое существительное в описании задачи может быть важным объектом.

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

Но даже здесь вы все равно не переходите сразу от задачи к написанию кода.

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

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

Проектная деятельность происходит итеративно и непрерывно.

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

И эти модели полезны на протяжении всего процесса проектирования.

Первоначально основное внимание должно быть сосредоточено на объектах сущностей entity из пространства задачи.

По мере появления решения вы вводите объекты управления control, которые принимают события и координируют действия. Вы также вводите пограничные объекты boundary, которые подключаются к службам вне вашей системы.

Модели часто выражаются визуально с помощью унифицированного языка моделирования или UML.

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

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

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

Например, разделить их на более мелкие части и посмотреть на общие черты, которые можно обрабатывать последовательно.

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

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

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

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

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

И один из принципов проектирования в объектно-ориентированном моделировании, — это абстракция.

Абстракция — один из основных способов, с помощью которых люди справляются со сложностью задачи.

Абстракция — это идея упрощения концепции в области задачи до ее сути в определенном контексте.

Абстракция позволяет лучше понять концепцию, разбив ее на упрощенное описание, которое игнорирует несущественные детали.

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

И граница между существенными и несущественными деталями с точки зрения дизайна называется барьером абстракции.

И задачей дизайна является выделение полного и достаточного набора абстракций.

Например, мы могли бы создать абстракцию для еды.

В контексте здоровья ее пищевая ценность, а не ее стоимость, будет частью упрощенного описания пищи.

Хорошая абстракция подчеркивает основы, необходимые для концепции, и устраняет детали, которые не являются существенными.

Также абстракция концепции должна иметь смысл для цели концепции.

Эта идея применяет правило наименьшего удивления.

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

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

В объектно-ориентированном моделировании абстракция относится непосредственно к понятию класса.

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

Класс похож на шаблон для экземпляров концепции.

Объект, созданный из класса, затем имеет существенные детали для представления экземпляра некоторого понятия.

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

Давайте возьмем понятие человека. Каковы основные характеристики человека?

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

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

Например, если вы создаете приложение для вождения, вы должны описать человека в контексте водителя.

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

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

Прежде чем мы начнем создавать абстракцию, нам нужен контекст для нее.

Контекст имеет решающее значение при формировании абстракции.

После определения контекста и абстракции, мы определяет характеристики или атрибуты абстракции.

И в дополнение к атрибутам абстракция должна описывать базовое поведение концепции.

Всякий раз, когда мы создаем абстракцию, нам нужно помнить о контексте.

Если контекст изменяется, тогда может измениться и абстракция. А затем могут измениться ее атрибуты и поведение.

Инкапсуляция является фундаментальным принципом в объектно-ориентированном моделировании и программировании.

Есть много вещей, которые вы можете представить, как объекты.

Например, вы можете представить курс как объект.

Объект курса может иметь значения атрибутов, такие как определенное количество учащихся, стоимость и предварительные условия, а также конкретные поведения, связанные с этими значениями атрибутов.

И класс курса определяет основные атрибуты и поведение всех объектов курса.

Инкапсуляция включает в себя три идеи.

Как следует из названия, речь идет о создании своего рода капсулы. Капсула содержит что-то внутри.

И некоторое из этого что-то вы можете получить снаружи, а некоторое вы не можете.

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

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

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

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

И вы определяете класс для данного типа объекта.

Абстракция помогает определить, какие атрибуты и поведение имеют отношение к концепции в некотором контексте.

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

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

Вы обнаружите, что программирование проще, когда данные и код, который управляет этими данными, расположены в одном месте.

Данные объекта должны содержать только то, что подходит для этого объекта.

Помимо атрибутов, класс также определяет поведение через методы.

Для объекта класса методы управляют значениями атрибутов или данными в объекте для достижения фактического поведения.

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

И инкапсуляция помогает с целостностью данных.

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

И на практике вы часто представляете внешний доступ ко всем атрибутам через определенные методы.

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

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

Кроме того, инкапсуляция может обеспечить конфиденциальность информации.

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

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

Инкапсуляция помогает с изменениями программного обеспечения.

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

Пользователям, использующим класс, не нужно заботиться о том, как реализация фактически работает за интерфейсом.

В программировании такого рода подход обычно называют черным ящиком.

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

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

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

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

Она поддерживает модульность и простоту работы с программным обеспечением.

Декомпозиция берет целую вещь и делит ее на разные части.

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

Разложение позволяет вам разложить проблему на части, которые легче понять и решить.

Разделяя вещь на разные части, вы можете более легко разделить обязанности этой вещи.

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

Это связывает целое с несколькими различными частями.

Иногда целое делегирует конкретные обязанности своим частям.

Например, холодильник делегирует замораживание пищи и хранение этой пищи в морозильной камере.

Так как разложение позволяет создавать четко определенные части, вполне естественно, что эти части являются отдельными.

Целое может иметь фиксированное или динамическое число частей определенного типа.

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

Например, холодильник имеет фиксированное количество морозильников, только один.

Это не меняется со временем, но иногда есть части с динамическим числом.

Объект может получить новые экземпляры объектов частей за время его существования.

Например, холодильник может иметь динамическое количество полок с течением времени.

И сама часть может также служить целым, содержащим дополнительные составные части.

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

Например, холодильник и морозильник имеют одинаковый срок службы.

И одно не может существовать без другого.

Если вы откажетесь от холодильника, вы также избавитесь от морозильной камеры.

Но срок жизни также может быть не связан.

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

Также вы можете иметь целые вещи, которые имеют общие части в одно и то же время.

Например, человек, у которого есть дочь в одной семье, а также супруга в другой семье.

Эти две семьи считаются отдельными целыми, но они одновременно имеют одну и ту же общую часть.

Однако иногда совместное использование невозможно.

Например, пищевой продукт в холодильнике не может одновременно находиться в духовке.

В целом, разложение помогает разбить задачу на более мелкие части.

И сложная вещь может быть составлена из отдельных более простых частей.

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

Идея объектно-ориентированного моделирования и программирования заключается в создании компьютерного представления концепций в пространстве задачи.

И принцип проектирования, называемый обобщением, помогает сократить избыточность при решении задачи.

Многие виды поведения в реальном мире действуют посредством повторяющихся действий.

И мы можем моделировать поведение с помощью методов.

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

Например, возьмите код создания и инициализации массива.

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

Методы — это способ применения одного и того же поведения к другому набору данных.

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

Мы можем обобщать действия в метод и просто передавать другой набор данных через аргументы.

Так где же мы можем применить обобщение?

Если мы можем повторно использовать код внутри метода и метод внутри класса, то можем ли мы повторно использовать код класса?

Можем ли мы обобщить классы?

Обобщение является одним из основных принципов объектно-ориентированного моделирования и программирования.

Но здесь обобщение достигается иначе, чем обобщение с помощью методов.

Обобщение в ООП может быть выполнено классами через наследование.

В обобщении мы принимаем повторяющиеся, общие характеристики двух или более классов и переносим их в другой класс.

В частности, вы можете иметь два класса, родительский класс и дочерний класс.

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

Вы размещаете общие атрибуты и поведение в своем родительском классе.

Может быть несколько дочерних классов, которые наследуются от родительского класса, и все они получат эти общие атрибуты и поведение.

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

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

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

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

Через наследование все подклассы класса будут обладать атрибутами и поведением суперкласса.

Наследование и методы иллюстрируют принцип обобщения в проектировании.

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

Это делает код более многоразовым, потому что разные классы или методы могут совместно использовать одни и те же блоки кода.

Системы упрощаются, потому что у нас нет повторяющегося кода.

Обобщение помогает создать программное обеспечение, которое будет легче расширять, проще применять изменения и упрощает его поддержку.

Обобщение и наследование являются одной из наиболее сложных тем в объектно-ориентированном программировании и моделировании.

Наследование — это мощный инструмент проектирования, который помогает создавать понятные, многоразовые и поддерживаемые программные системы.

Однако неправильное наследование может привести к плохому коду.

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

Итак, как мы можем понять, злоупотребляем ли мы наследованием?

Есть несколько моментов, о которых нужно знать, когда рассматривается наследование.

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

Если ответ «да», тогда вы неправильно используете наследование.

Это является признаком неправильного использования, потому что нет никаких оснований для существования подклассов, так как суперкласса уже достаточно.

Скажем, вы проектируете ресторан пиццы. И вам нужно смоделировать все различные варианты пиццы, которые есть у ресторана в меню.

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

И класс пиццы может обобщен.

Это кажется разумным, но давайте посмотрим, почему это является неправильным использованием наследования.

Несмотря на то, что пицца pepperoni — более специфическая пицца, она не очень отличается от суперкласса.

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

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

Второй признак ненадлежащего использования обобщения — если вы нарушаете Принцип Замещения Лискова.

Принцип гласит, что подкласс может заменить суперкласс, тогда и только тогда, когда подкласс не изменяет функциональность суперкласса.

Как этот принцип может быть нарушен через наследование?

Давайте посмотрим на этот пример.

Это наш обобщенный класс животных, и он знает, как есть, гулять и бегать.

Теперь, как мы можем ввести подкласс, который нарушит принцип замещения Лискова?

Что, если у нас есть этот тип животных?

Кит не знает, как гулять и бегать.

Гулять и бегать — это поведение наземных животных.

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

Пример плохого наследования можно увидеть и в библиотеке коллекций Java.

Вы когда-нибудь использовали класс стека в Java?

Стек имеет небольшое количество четко определенных поведений, таких как peak, pop и push.

Но класс стека наследуется от класса вектора.

Это означает, что класс стека может возвращать элемент по указанному индексу, извлекать индекс элемента и даже вставлять элемент по определенному индексу.

И это не является поведением стека, но из-за плохого использования наследования это поведение разрешено.

Если наследование не соответствует вашим потребностям, подумайте, подходит ли декомпозиция.

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

Смартфон имеет характеристики телефона и камеры.

И для нас не имеет смысла наследовать от телефона, а затем добавлять методы камеры в подкласс смартфон.

Здесь мы должны использовать декомпозицию для извлечения ответственностей камеры и размещения их в классе смартфона.

Тогда смартфон будет косвенно обеспечивать функциональность камеры в телефоне.

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

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

И наследование — это просто один из способов помочь вам достичь этой цели.

И важно понимать, что этот метод полезен только при правильном использовании.

Оглавление

* * *

Приведённый ознакомительный фрагмент книги Введение в объектно-ориентированный дизайн с Java предоставлен нашим книжным партнёром — компанией ЛитРес.

Купить и скачать полную версию книги в форматах FB2, ePub, MOBI, TXT, HTML, RTF и других

Смотрите также

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