В чем разница между связями «Обобщение», «Реализация» и «Имплементация» на диаграмме классов UML?
Спецификацию UML касательно этого вопроса я понимаю следующим образом:
— связь generalization возникает между классами, когда один класс (наследник) основывается на другом классе (родителе). Судя по всему, здесь родительский класс обязательно должен быть конкретным классом (т.е. можно создавать его экземпляры).
— связь realization возникает между элементами диаграммы классов, когда один класс реализует поведение, определяемое другим классом. Скорее всего здесь речь идет о реализации поведения, определяемого интерфейсом или абстрактным классом.
— связь implementation — это подтип связи realization. Судя по всему, на одном конце связи — конкретный класс, а на другом — интерфейс (interface в Java, чисто виртуальный класс в C++).
Вопрос первый: насколько верна моя трактовка спецификации?
Вопрос второй: можно ли считать realization подтипом связи generalization?
Вопрос третий: если язык поддерживает множественное наследование (C++ например) и класс наследуется от двух конкретных классов — какая в таком случае связь между наследником и суперклассами?
Семантическую разницу между интерфейсом и абстрактным классом я понимаю. Родительский абстрактный класс определяет сущность объекта, а реализация классом некоторого интерфейса — лишь один из аспектов поведения класса (либо действий, которые можно выполнять над этим классом).
Вопрос четвертый: может ли быть такое, что в одном случае наследование от абстрактного класса порождает связь generalization, а в другом случае — realization.
Трактовка не совсем верна, я сейчас изложу свое мнение, а уж сравнить наши взгляды задача не из простых)) Основная проблема различие понятий в UML и ООП.
В целом по определениям: Со стороны клиента у каждого конкретного класса присутствуют: интерфейс (который состоит из методов, свойств и прочих языковых штук) и абстракция (это некое поведение, которое подразумевается при взаимодействии с его интерфейсом).
Generalization. Когда у классов совпадает поведение, получается можно свести их интерфейсы к единому — обобщить. Диаграмма обобщения показывает сходство некоторого аспекта поведения и абсолютное сходство интерфейса.
Realization. Когда часть поведения объекта выносится в отдельный класс — это называется реализацией. В программировании такой прием обычно называют делегированием, но UML похоже считает иначе.
Implementation. В терминологии UML это как раз означает декомпозицию некого объекта на составные части. В программировании этот термин означает совсем другое…
Абсолютное сходство интерфейса в Generalization звучит убедительно, а главное очень хочется в это верить. Но напрягает момент, что зачастую в классах-наследниках появляются публичные методы, которых не было у родителя, а в коде можно встретить понижающее приведение типа (downcasting).
Implementation как декомпозиция объекта на составляющие мне тоже нравится. Только не понял, почему у тебя implementation в UML конфликтует с implementation в программировании. Если судить по синтаксису, например, Java или PHP, то для того, чтобы показать, что класс реализует интерфейс, используется именно ключевое слово implement. Один класс в свою очередь может реализовывать множество интерфейсов (например, IComparable, IMovable и IDrawable). Получается, что такой класс — это объединение решения нескольких задач внутри одного класса, интерфейс которого состоит из совокупности всех интерфейсов, им реализуемых ResultingClassInterface =
В мануале IBM по UML про связь implementation написано следующее: The implementation relationship specifies that the realizing classifier must conform to the contract that the provided interface specifies. Это выполняется by design во всех языках программирования, поддерживающих интерфейсы в таком виде, как они понимаются например в Java, PHP или C#.
В том же самом мануале про связь realization написано вот что: You can model the following situations using realization relationships:
A component is realized by a set of classifiers that provide its implementation. Вот это уже действительно похоже на то, что класс предоставляет своим клиентам абстракцию, складывающуюся из сервисов классов, с которыми он находится в отношении realization плюс возможно его собственные публичные методы. При этом не гарантируется, что новый класс будет на 100 % повторять интерфейсы классов, чью реализацию он представляет. Следовательно, никто не запрещает нам realization провернуть за счет делегирования и обойтись вовсе без наследования в этом случае. При этом внешний вид связи (пунктирная линия с пустым треугольником на конце) ничем не отличается от вида связи implementation и напоминает связь generalization, что меня вводит в заблуждение. Делегирование же в свою очередь обычно на диаграммах представляется связью association (собирательно название для agregation или composition). Рискну предположить, что связь realization должна использоваться вместо association при делегировании, когда ВСЕ методы класса, входящего в связь с одной стороны (service provider), имеют свое отражение в публичных методах класса на другой стороне связи (т.е. отношение между методами биективное).
Ну и пока не ясен для меня вопрос, как следует поступать, когда в приложении существует множество разношерстных по своему назначению классов + один интерфейс, который большинство из них (но не все!) должны реализовывать. Причем реализация эта в 99% случаев должна быть по умолчанию одинакова. Писать в каждом из классов реализацию — ад. Отнаследовать все эти класса системы от базового класс MyObject implements IInterface, в котором будет представлена реализация интерфейса по умолчания (а-ля C# или Java) — вроде перебор. И как при этом должна выглядеть диаграмма классов UML?
Сие есть очень занимательно… никогда так глубоко не рыл, и вот…
Даже по-русски «realization» и «implementation» переводятся как реализация — это я вроде знал, а в литературе такого разделения не встречал нигде. Поиск «realization» в вики ничего тоже не дел, что уже настораживает. А вот про implementation написано, что это есть realization 
«Implementation is the realization of an application, or execution of a plan, idea, model, design, specification, standard, algorithm, or policy.»
Ладно, посчитаю, что это одно и то же.
Тогда имеем, что generalization — это просто наследование, не важно, от абстрактного класса или нет.
Implementation — реализация интерфейса (или чисто абстрактного класса).
Итого, про первый вопрос я не знаю, что сказать, но высказал свое мнение.
По второму — можно с некоторым уточнением, что это будет реализован (realized, implemnted) уже реальный метод, который в интерфейсе просто не мог быть реализован просто потому, что это интерфейс.
По третьему — тут будет просто несколько связей, в зависимости от типа — например если реализуется несколько интерфейсов — то будет несколько связей «implementation», то же касается и generalization.
По четвертому — если власс расширяет абстрактный и сам ничего не реализует (т.е. тоже является абстрактным), то тут просто не может быть реализации (implementation, realization) — т.е. тут чисто generalization. А если что-то реально реализуется — то тогда можно говорить о implementation (realization).
Имплементация программирование что это
Краткий справочник по ООП
Данный справочник призван помочь в освоении ООП. Вся информация приведена в простом и понятном виде.
Краткий словарь терминов
Прежде чем приступать к изучению ООП и ответу на вопрос зачем оно нужно, сначала надо изучить самые основные парадигмы и понять разницу между ними.
Парадигмы делятся на 2 типа: императивное и декларативное.
В общем и целом, декларативное программирование идёт от человека к машине, тогда как императивное — от машины к человеку.
Основные парадигмы, которые включает в себя императивное:
Процедурное программирование
Объектно-ориентированное программирование
Данная парадигма будет рассмотрена позднее более подробно
Основные парадигмы, которые включает в себя декларативное:
Функциональное программирование
Что такое ООП? Зачем оно нужно? Как оно используется?
Как проектировать программы с ООП?
Как и приступая к написанию кода большинства программ, для начала следует разобраться с архитектурой программы, продумать все алгоритмы, а не сразу начинать писать код, треубется полностью понять как должна работать будущая программа.
Стоит помнить о такой вещи в ООП, как принцип единственной ответственности. Каждый класс должен быть ответственен только за одну вещь и отвечать за конкретную цель. Потому что при изменении кода в одном месте, это может затронуть совершенно дргуие функции, изменение которых пользователь может совершенно не ожидать, так как они преследуют совершенно другие цели, но из-за того, что все реализовано в одном классе, мы рискуем получить ошибку. При чем, принцип единственной ответственности предпологает, что вся важнейшая реализация будет инкапсулирована.
Программа должна быть разбита на отдельные модули, каждый модуль отвечает за отдельные вещи. При чем, они должны быть связаны минимально, то есть изменение кода в одном классе должно быть относительно безболезненным, чтобы потом не пришлось переделывать всю программу.
Примеры паттернов:
В данном примере был создан итератор для обхода элементов вектора. Пользователь имеет возможность обойти все элементы вектора, не зная его реализации.
Базовые принципы ООП
Создание класса
В чем различия между классом и структурой?
Отличия между классом и объектом
Модификаторы доступа позволяют настраивать область видимости в программе конкретных методов/переменных, описанных в классе.
Применение модификаторов к членам класса
Применение модификаторов к унаследованным классам
Все описанные модификаторы доступа применимы также и к модификаторам доступа наследственных классов:
Как видно из примера, функция print(), вызванная из объекта типа Derived, в любом случае вызывает функцию родительского класса. Это происходит потому, что функция print() вызывается в зависимости от типа указателя, а не объекта. Виртуальные функции же решают эту проблему и позволяют вызывать методы на основе типа объекта, что известно как Runtime polymorphism.
Объявив функцию print() как виртуальную:
Чистая виртульная функция
Таблица виртуальных функций
Статические поля класса
Для чего требуется следующая инициализация переменной?
Так как статическая переменная не является частью конкретных объектов, а принадлежит самому классу, то и инициализируется она при начале работы программы. Также, если класс со статическим полем определен в хедере, то при включении хедера во множественных местах, переменная будет объявляться более одного раза, что запрещено. Поэтому ее определение требуется вынести за область видимости класса.
Также следует отметить, что при инициализации доступ к статической переменной может быть получен независимо от ее модификаторов доступа.
Еще одной важной особенностью является то, что статическая переменная хранит свое состояние от начала работы программы и до конца.
Статические методы класса
Следует отметить, что статический метод может получить доступ только к таким же static полям класса, но не non-static переменным.
Также определение метода может быть вынесено за область видимости класса. Пример:
Scope resolution operator ::
Оператор :: позволяет получать доступ к области видимости какого-либо класса, и, соответственно, к переменным/методам в нем.
Что такое конструктор?
Существует 2 типа конструкторов:
Стандартный конструктор будет автоматически генерироваться компилятором, если не были объявленны никакие другие специализированные конструкторы. В противном случае необходимо имплементировать стандартный конструктор самому пользователю. Без стандартного конструктора невозможно создание дочерних классов или объектов данного класса в дочерних классах.
Parameterized constructor, в отличие от стандартного конструктора, имеет параметры в своем объявлении и позволяет инициализировать поля класса переданными параметрами.
Вызвать конструктом можно двумя способами:
Существует значимое отличие явного вызова от неявного при использовании стандартного конструктора. Если конструктор не имплементирован, и происходит явный вызов, то все значения будут инициализированы нулями. Однако, при использовании неявного вызова компилятор будет использовать стандартный конструктор, который не инициализирует значения нулями, присваивая случайные значения.
Initializer list требуется для того, чтобы инициализировать переменные класса.
Применение Initializer list
Существует огромная разница между инициализацией членов класса с помощью initializer list и присваиванием значений в теле конструктора. Initializer list принимает аргументы и сразу же инициализирует необходимые данные, в отличие от присваивания в теле конструктора, где сначала компилятором создается копия переданных данных, только после этого происходит присваивание. Следовательно, использование первого метода влияет положительно на скорость выполнения программ.
Всегда следует придерживаться такого метода инициализации членов класса.
Также существует несколько исключений, где применим только initializer list:
Что такое деструктор?
Деструктор вызовется, только если объект был полностью инициализирован без ошибок и без исключений.
Пример простого деструктора:
Виртуальный деструктор требуется для полного корректного уничтожения объекта, где содержится хотя бы 1 виртуальный метод. Уничтожение объекта по указателю типа родительского класса является неопределенным поведением, так как происходит вызов деструктора по типу указателя, а не типу объекта (это описано подробнее в главе о Виртуальных функциях. Для того, чтобы полностью удалить объект, требуется добавить виртуальные деструкторы во все унаследованные классы.
Связь с инкапсуляцией
Разделение класса на описание и имплементацию
Класс также можно разделить на описание и реализацию и подключить его в виде библиотеки. Пример:
Содержимое файла Foo.h. Описание класса.
Содержимое файла Foo.cpp. Имплементация класса.
Программа с классом Base, реализованным в виде библиотеки:
Достоинства данного метода:
Действия в случае исключения:
Если при инициализации объекта произошла ошибка или исключение, то может произойти утечка памяти, так как некоторые выделенные в динамической памяти члены класса не будут высвобождены. Во избежание этого следует использовать умные указатели (smart pointers) для их инициализации.
Пример программы, в которой происходит утечка памяти из-за исключения:
Valgrind:
Для того, чтобы исправить утечку памяти, следует использовать smart pointer, который автоматически вызывает соответствующий типу указателя оператор высвобождения памяти, когда указатель выходит из области видимости и соответственно при завершении программы ошибкой.
Пример программы, использующей smart pointer:
Valgrind:
Утечка исчезла, ошибка исправлена.
Пример автоматического высвобождения памяти:
Valgrind:
Существует 2 наиболее используемых умных указателя: unique_ptr и shared_ptr;
Отличия между unique_ptr и shared_ptr
Используя unique_ptr, пользователь может создать только один указатель на какой-либо объект.
Для того, чтобы это стало возможным, требуется создать shared_ptr, отличие которого в том, что он поддерживает множественное создание указателей на объект.
Java — разница между extends и implements на примерах
После продолжительного программирования на C++ переходить на Java бывает болезненно. С одной стороны прославленный сборщик мусора, а с другой огромное множество принципиальных различий в подходах к программированию. Об одном таком отличии я сегодня расскажу подробнее.
Речь пойдет о наследовании в Java. В отличии от C++, где наследование могло быть множественным, здесь это не совсем так. Кроме того, привычный синтаксис через «:» заменился на целых два ключевых слова: extends и implements. Начну с первого.
Ключевое слово extends в Java
Действие ключевого слова в точности совпадает с его переводом, один класс расширяет другой, что является классическим наследованием. Правила видимости полей и методов сохранились: private доступны только в самом классе, protected в самом классе и во всех наследниках, к public методам и полям можно обращаться откуда угодно. Главное отличие от «сишного» наследования в том, что можно расширять только один класс. Я сейчас не буду рассуждать о том, насколько это удобно, скажу только, что со множественным наследованием в C++ постоянно творилась какая-то каша.
Небольшой пример наследования с помощью ключевого слова extends. Напишем класс Door, который будет описывать характеристики двери, мы можем создать объект этого класса и работать с ним, как с «просто дверью». С другой стороны напишем еще два класса: IronDoor и WoodDoor, которые будут расширять класс Door(== наследуются от класса Door), т.е. добавят свои характеристики к базовым.
Ключевое слово implements в Java
С ключевым словом implements связано чуть больше хитростей. Слово «имплементировать» можно понимать, как «реализовывать», а в тот самый момент, когда возникает слово «реализовывать», где-то недалеко появляются интерфейсы. Так вот конструкция public class Door implements Openable означает, что класс дверь реализует интерфейс «открывающийся». Следовательно класс должен переопределить все методы интерфейса. Главная фишка в том, что можно реализовывать сколь угодно много интерфейсов.
Зачем это нужно? Самый простой пример, который приходит в голову, два интерфейса: Openable и Closeble. В первом метод open, и метод close во втором. Они помогут научить нашу дверь закрываться и открываться.
В классах-потомках двери(железная и деревянная двери) тоже появятся методы открыть/закрыть, реализованные в классе Door. Но никто нам не запрещает их переопределить.
Заключение
Итак, главное отличие в том, что extends используется для наследования от класса в прямом смысле этого слова, а implements позволяет «реализовать интерфейс». На первый взгляд это кажется лишним, неудобным и непонятным, но стоит пару раз использовать по назначению и все встает на свои места. На сегодня у меня все, спасибо за внимание!
Что такое Implementation Plan, или Как планировать реализацию при разработке
Будучи Full Stack Engineer в компании Railsware, я отношусь к той категории людей, которые считают, что правильное планирование рабочего процесса — это половина успеха. Поэтому я хочу поделиться способом, который мы используем при планировании работы над user stories в рамках каждого спринта. Мы называем его Implementation Plan.
Что такое Implementation Plan
Итак, Implementation Plan — это детальный конкретизированный план, прописанный в формате чеклиста. Он составляется разработчиками перед началом работы над каждой user story.
Другими словами, это пошаговая инструкция к выполнению каждой из задач, прописанная инженером для себя или для других разработчиков.
Будем честными, зачастую инженеры ленятся добавлять еще один шаг в привычные процессы, особенно если речь идет о планировании. На это нужны веские причины. На мой взгляд, Implementation Plan дает команде следующие преимущества:
Более подробно на плюсах такого подхода я остановлюсь в конце статьи.
В чём суть подхода
Процесс выглядит следующим образом. В первую очередь мы определяем, какие из user stories реализуем в следующем спринте. Далее задача инженера — составить пошаговый Implementation Plan для реализации каждой из них. Обычно он включает в себя:
`add ’user_id’ parameter to required params of ’api/shares_controller.rb’`
Важно прописывать все шаги максимально конкретно: так, запись «написать тесты» сама по себе будет достаточно бесполезной.
Для удобства чтения и навигации мы структурируем чеклист по заголовкам:
Несколько важных моментов, которые стоит учитывать:
Для создания самого чеклиста можно использовать любой удобный сервис. Мы обычно работаем с Trello, Smart Checklist адд-оном для Jira или Clubhouse.
Насколько детальным должен быть Implementation Plan
Наша главная задача — структурировать и упростить процесс разработки.
В идеале, нужно прописать все изменения, необходимые для имплементации функционала. Они должны быть понятными любому инженеру, который будет работать над user story. Так, чтобы даже новые участники процесса могли включиться без дополнительных инструкций.
Для сложных задач критерием завершенности планирования может служить отсутствие вопросов к требованиям задачи и неясностей в имплементации. То есть для каждого этапа должно быть понятно, какие инструменты использовать, какие участки кода необходимо изменить и каким образом.
Если план неясен, мы открываем код и пытаемся понять, как он работает и что нужно добавить / изменить. По сути, вы будете делать то же самое при разработке, если у вас нет плана.
Если же неясна имплементация — какой фреймворк использовать, какой из аспектов приложения необходимо изменить, — то можно начать писать код и экспериментировать. Ограничив подобный эксперимент сроком, например, в 1 день, можно прояснить непонятные моменты и, в то же время, не потратить слишком много времени на эксперименты.
Процесс планирования на клиентских проектах
Перед началом итерации мы проводим IPM (Implementation Planning Meeting) — митинг для планирования и оценки задач на следующие две недели.
Наиболее эффективным будет сделать Implementation Plan перед оценкой задач на спринт. Так, понедельник мы начинаем со списка задач для нового спринта. В течение дня мы их изучаем, разбираем, обсуждаем с клиентом. Важно прийти к пониманию, действительно ли каждая запланированная задача будет эффективна.
Мы работаем в таком подходе уже несколько лет, и за это время он доказал свою эффективность:
Особенности составления планов для разных типов задач
Небольшие и очевидные задачи
Может показаться, что нет необходимости создавать Implementation Plan для маленьких и простых задач, когда всё и так очевидно.
Однако даже в таких случаях создание чеклиста для простой задачи позволит:
Практика показывает, что на крупных проектах простые, на первый взгляд, задачи могут оказаться гораздо сложнее. А если задача действительно простая, то и план ее реализации будет составлен очень быстро. Ради уверенности обычно имеет смысл взглянуть на код и набросать план.
Задачи с некоторым уровнем неопределенности
Если некоторые аспекты задачи непонятны, хорошей идеей будет провести несколько экспериментов перед тем, как планировать пути реализации.
Это помогает выяснить, что именно вас блокирует и куда двигаться дальше. Вы в любом случае будете делать то же самое во время реализации, так что время на эксперименты не будет потрачено зря.
Полностью непонятные задачи
Бывают задачи, для которых сложно сразу понять способ реализации. Может быть непонятно, какую из частей приложения нужно менять или какой фреймворк при этом использовать.
Принцип планирования для таких задач такой же: нужно экспериментировать. Открыть код, посмотреть, как он работает, начать писать то или иное решение. Составить чеклисты для тех моментов, которые понятны. Это должно внести ясность.
Если же спустя два-три часа планирования подобной задачи вы особо не продвинулись, имеет смысл обсудить этот момент с менеджером и предложить дополнительный день или два на spike (более детальное изучение путей реализации).
В данной ситуации Implementation Plan поможет понять, что некоторые задачи действительно требуют дополнительного изучения и почему в них есть та или иная неопределенность. Подобное планирование на подготовительном этапе, до начала самой разработки, позволяет спланировать риски и сэкономить время на разработку.
Об использовании Implementation Plans в разных командах
Работая с разными командами, я имею возможность наблюдать за тем, как разные люди работают с Implementation Plan подходом. В том числе и за тем, как перестают ему следовать и как это влияет на реализацию запланированных задач.
Новые инженеры
Обычно, когда в команду проекта приходит новый человек, он с большим энтузиазмом относится к созданию подробных планов, поскольку это помогает ему быстро войти в процесс и отслеживать прогресс. Но чем основательнее он разбирается в тонкостях проекта, тем менее охотно придерживается процесса планирования.
Чеклисты становятся всё менее подробными и в какой-то момент инженер прекращает их писать вовсе. С большей уверенностью инженеры начинают оценивать задачи «на глаз». В итоге бывает, что задача, оцененная в два поинта, оказывается не законченной через неделю или две.
Команды, избегающие детального планирования задач
Многие предпочитают минимизировать планирование перед реализацией. Либо не делают планирование достаточно детальным.
В таких случаях часто возникают следующие проблемы:
Плюсы и минусы Implementation Plans
Выше я описал преимущества составления Implementation Plans, рассказал о том, с какими сложностями мы сталкиваемся при отсутствии надлежащего планирования. Давайте подытожим.
У любого, даже самого интересного и эффективного подхода есть и другая сторона. Вот несколько проблемных моментов:
Есть и психологический момент: нужно поверить в то, что подход детального планирования себя оправдывает. Иногда стоит отбросить уверенность в своих знаниях проекта и способностях оценивать задачи на глаз.
Следующие шаги
Целью статьи было поделиться нашим подходом к планированию, обсудить плюсы и минусы, которые мы выявили в процессе.
Если вас заинтересовал этот метод, предлагаю попробовать внедрить его у себя на проектах:
Поначалу, как, впрочем, и с любым другим инструментом, процесс может быть трудозатратным. Но в какой-то момент планирование начнёт занимать меньше времени и и улучшит вашу эффективность.
Возможно, у вас есть похожие процессы и подходы? Или наоборот, вы работаете по совершенно другой схеме? Буду рад обсудить в комментариях.


