Обобщение и реализация UML

Я новичок в UML, поэтому у меня есть несколько вопросов об обобщении и реализации. Я моделирую поведение электронного микроконтроллера, и мне нужно сгенерировать код C++ из описания UML.

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

Вот мой вопрос (я использую Visual Paradigm в качестве инструмента моделирования). Предположим, у нас есть модуль микроконтроллера, а именно Timer. У нас есть набор операций, которые мы можем выполнять, скажем, initTimer(), startTimer(), stopTimer() и так далее. На самом деле эти функции определяют своего рода API. У нас могут быть разные классы Timer, скажем, TimerA, TimerB, TimerC, которые наследуют (или реализуют?) все указанные операции. Картинка, наверное, сделает сценарий более понятным. [C] означает классификатор.

                        +----------------------------------+   
                        |              <<SW>>              |
                        |           <<Singleton>>          |
         +--------------|              TimerA              |
         |              +----------------------------------+
         |              | -instance : TimerA* = null [C]   |
         |              | -instanceFlag : bool = false [C] |
         |              | -moduleAddress const = 0x0010    |
         |              +----------------------------------+
         |              | -TimerA()                        |
         V              | +getInstance() : TimerA* [C]     |
+---------------+       +----------------------------------+
|    <<SW>>     |       
|     Timer     |
+---------------+
| +initTimer()  |     
| +startTimer() |<-----------------------+
| +stopTimer()  |                        |
+---------------+      +----------------------------------+ 
                       |              <<SW>>              |
                       |           <<Singleton>>          |
                       |              TimerB              |
                       +----------------------------------+
                       | -instance : TimerB* = null [C]   |
                       | -instanceFlag : bool = false [C] |
                       | -moduleAddress const = 0x0020    |
                       +----------------------------------+
                       | -TimerB()                        |
                       | +getInstance() : TimerB* [C]     |
                       +----------------------------------+

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

1) Обобщение: Timer класс с набором операций. Каждая операция имеет свою кодовую реализацию. Два производных класса TimerA и TimerB со ссылкой на обобщение, наследующие операции класса Timer.

2) Реализация: Timer — это интерфейс (а не класс, как показано) и два класса реализации TimerA и TimerB. Критический момент заключается в следующем. Хотя Timer — это интерфейс, и его операции не должны содержать деталей реализации, VP позволяет писать код реализации для трех операций. Во время генерации кода создается интерфейс C++ класса Timer: initTimer(), startTimer() и stopTimer() являются виртуальными членами класса Timer без кода (как и должно быть). Создается класс C++ TimerA, который наследует члены класса Timer; кроме того, три операции Timer копируются среди членов TimerA с реализацией кода, который я написал для операций класса интерфейса. Это происходит и для TimerB.

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


person Umberto D.    schedule 02.04.2015    source источник


Ответы (2)


На ваш взгляд, какое из двух описаний лучше?

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

введите здесь описание изображения

VP позволяет написать код реализации для трех операций. Во время генерации кода... Правильно ли писать реализацию кода для операций интерфейса...?

Я не знаю, как правильно настроить генератор кода Visual Paradigm для получения того, что вы хотите, но хотя интерфейс C++ просто класс, как и любой другой, и для этой концепции нет специального keyword, интерфейс UML предназначен только для описания contract без связанной реализации. Некоторые языки, такие как C# или Java имеет специальные interface keyword для этой цели с правилом отсутствия кода, жестко запрограммированным в компиляторе .

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


Сделай свой выбор. Если вам нужен только код, который что-то делает, то все равно идите и взломайте его, который работает (как предлагает @gilead-silvanas). Если вы хотите попрактиковаться в UML для использования в будущих проектах с использованием разных языков, не делайте этого.

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

Я бы пересматривал (регулярно), если борьба с инструментом проектирования перевешивает преимущества генерации кода

person xmojmr    schedule 02.04.2015
comment
Пожалуйста, поправьте меня, если я ошибаюсь. При вашем моделировании обеспечивается единственное существование классов TimerA и TimerB, поскольку соответствующий флаг устанавливается любой из двух одноэлементных оболочек. На самом деле создается экземпляр объекта класса Timer для всех доступных оболочек Singleton (это делается с помощью статических членов TimerA::getInstance() и TimerB::getInstance()). Итак, в конце концов, конструкторы TimerA() и TimerB() не используются? Могу ли я передать аргументы в класс Timer из TimerA и TimerB. Я имею в виду, в частности, moduleAddress. Спасибо. - person Umberto D.; 02.04.2015
comment
@УмбертоД. да, это то, что я имел в виду, TimerA() и TimerB() могут иметь статические конструкторы классов, которые будут инициализировать переменные instance. Но не воспринимайте это буквально, так как это справедливо для C#, где отсчет таймера будет обрабатываться функцией, прикрепленной к обработчик четности таймера. Я не был в C++ мире активно несколько лет (и я этому рад). В C++ вам возможно, придется реализовать это, создав подклассы. Так что мой ответ может ввести в заблуждение, когда дело доходит до генерации кода. - person xmojmr; 02.04.2015

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

person Gilead Silvanas    schedule 02.04.2015
comment
Спасибо за ответ. Мое первоначальное мнение состояло в том, что Timer на самом деле представляет собой интерфейс, поскольку он содержит набор методов (API), которые должен реализовывать каждый конкретный модуль (TimerA, TimerB и т. д.). Я сомневался в наиболее концептуально приемлемом решении с точки зрения UML. Другой тревожный момент заключался в том, чтобы записывать код C++ (в VP), следовательно, реализовывать операции, которые являются частью интерфейса, а не класса. - person Umberto D.; 02.04.2015
comment
На мой взгляд, с точки зрения UML было бы неправильно писать реализации в интерфейсе, если мы собираемся строго следовать правилам объектно-ориентированного программирования. - person Gilead Silvanas; 02.04.2015