UML2 и унифицированный процесс. Джим арлоуайла нейштадтпрактический объектно ориентированныйанализ и проектированиеu
Скачать 6.08 Mb.
|
Рис. 25.10. Выражения для навигации по ассоциации между класами А и В с кратностью на конце, равной 1 25.9. Навигация в OCL 557 и целевой класс. Когда кратность больше 1, он возвращает Set объек тов целевого класса. По умолчанию, если кратность >1, оператор «точка» возвращает Set. По умолчанию, если кратность – «много», оператор «точка» возвраща ет Set объектов. Однако с помощью свойств ассоциаций, приведенных в табл. 25.16, можно задавать тип возвращаемой коллекции. Таблица 25.16 Доступ к свойству коллекции является сокращенной записью операции collect(...). Когда осуществляется доступ к свойству коллекции, например self.d.d1 это выражение является сокращенной записью для self.d >collect( d1 ) Возможно, вы помните из раздела 25.8.7, что collect( iteratorExpression ) возвращает Bag, содержащий результаты выполнения iteratorExpression для каждого элемента коллекции. В данном случае возвращается Bag значений атрибута d1 для каждого объекта D в Set(D), полученном пу тем обхода self.d. OCL коллекция Свойства конца ассоциации Set { unordered, unique } – применяется по умолчанию OrderedSet { ordered, unique } Bag { unordered, nonunique } Sequence { ordered, nonunique } C c1:String D d1:String * d op1():String self self.d self.d.d1 self.d.op1() Экземпляр контекста – экземпляр С Set(D) объектов типа D Bag(String) значений атрибута D::d1 Краткая запись для self.d >collect( d1 ) Bag(String) результатов операции D::op1() Краткая запись для self.d >collect( op1() ) Навигационные выражения Выражение Значение Пример модели контекст Рис. 25.11. Выражения для навигации по ассоциации между класами А и В с кратностью на конце – * (много) 558 Глава 25. Введение в OCL Аналогично self.d.op1() является сокращенной записью для self.d >collect( d.op1() ) Результатом этого выражения является Bag, содержащий возвращае мые значения операции op1(), примененной к каждому объекту D в Set(D), полученном путем обхода self.d. Операция collect() всегда возвращает плоскую коллекцию. Если необ ходимо сохранить вложенность целевой коллекции в возвращаемой коллекции, следует использовать операцию collectNested(). 25.9.3. Навигация по нескольким ассоциациям В данном разделе рассматривается навигация по нескольким ассоциа циям. В принципе можно осуществлять навигацию по любому числу ассо циаций. На практике навигация ограничивается максимум двумя ас социациями, потому что пространные навигационные выражения чре ваты ошибками и могут быть сложны для понимания. Они также дела ют OCL выражения слишком многословными. Давайте рассмотрим простой пример навигации по двум ассоциациям (рис. 25.12). Результатом навигации по ассоциации с кратностью >1 является Bag. Можно заметить, что результатом навигации по ассоциации с кратно стью больше 1 всегда является Bag, потому что эта операция эквива лентна применению collect(...). Например, выражение self.k.l.l1 эквивалентно self.k >collect( l ) >collect( l1 ) Аналогичным образом можно провести навигацию по большему числу ассоциаций, но этого делать не рекомендуется. 25.10. Подробно о типах OCL выражений В разделе 25.7 были представлены различные типы OCL выражений. Теперь, рассмотрев синтаксис OCL, мы можем подробно остановиться на каждом из этих типов. В качестве примера воспользуемся простой моделью, приведенной на рис. 25.13. 25.10. Подробно о типах OCL выражений 559 A a1:String B b1:String C c1:String 1 1 b c D d1:String E e1:String F f1:String 1 * e f J j1:String K k1:String L l1:String * * k l G g1:String H h1:String I i1:String * 1 h i self Экземпляр контекста экземпляр А self.b Объект типа B self.b.b1 Значение атрибута B::b1 self.b.c Объект типа C self.b.c.c1 Значение атрибута C::c1 self Экземпляр контекста экземпляр D self.e Объект типа E self.e.e1 Значение атрибута E::e1 self.e.f Set(F) объектов типа F self.e.f.f1 Bag(String) значений атрибута F::f1 self Экземпляр контекста экземпляр G self.h Set(H) объектов типа H self.h.h1 Bag(String) значений атрибута H::h1 self.h.i Bag(I) объектов типа I self.h.i.i1 Bag(String) значений атрибута I::i1 self Экземпляр контекста экземпляр J self.k Set(K) объектов типа K self.k.k1 Bag(String) значений атрибута K::k1 self.k.l Bag(L) объектов типа L self.k.l.l1 Bag(String) значений атрибута L::l1 Навигационные выражения Выражение Значение Пример модели контекст контекст контекст контекст Рис. 25.12. Навигация по двум ассоциациям BankAccount balance : Real accountNumber : String deposit( amount : Real ):Real withdraw( amount : Real ) getBalance() : Real getOwner() : Person getOperators() : Person[] CheckingAccount overdraftLimit : Real withdraw( amount : Real ) getAvailableBalance() : Real getAvailableOverdraft() : Real DepositAccount withdraw( amount : Real ) Person name : String id : String address : String getName() : String getId() : String getAddress() : String owner operators 1..* * * 1 ownedAccounts operatedAccounts Рис. 25.13. Модель банковского счета 560 Глава 25. Введение в OCL 25.10.1. inv: Инвариант – это нечто, что должно быть истинным для всех экземпля ров контекстного классификатора. Рассмотрим модель простого банковского счета (рис. 25.13). Классы Che ckingAccount и DepositAccount подчиняются четырем бизнес правилам. 1. Ни на одном из счетов кредит не может быть превышен более чем на $1000. 2. CheckingAccount предоставляет возможность превышения кредита. Со счета может быть снята сумма, не превышающая установленно го ограничения по превышению кредита. 3. На счетах DepositAccount не допускается превышение кредита. 4. Каждый accountNumber должен быть уникальным. Первое правило, состоящее в том, что ни на одном из счетов кредит не может быть превышен более чем на $1000, можно представить как ин вариант класса BankAccount, потому что оно должно выполняться для всех экземпляров BankAccount (т. е. всех экземпляров его подклассов). context BankAccount inv balanceValue: баланс BankAccount должен быть >1000.0 self.balance >= ( 1000.0) Этот инвариант наследуется двумя подклассами: CheckingAccount и De positAccount. Для обеспечения сохранения принципа замещаемости (раздел 10.2) подклассы могут усилить инвариант, но ни в коем слу чае не ослабить его. Подкласс может усилить инвариант, но не ослабить его. Правила 1 и 2 могут быть выражены как инварианты класса Checking Account: context CheckingAccount inv balanceValue: превышение кредита на CheckingAccount не должно быть больше установленного ограничения self.balance >= ( overdraftLimit) inv maximumOverdraftLimit: превышение кредита на CheckingAccount не должно быть больше 1000.0 self.overdraftLimit <= 1000.0 Правило 3 может быть выражено как инвариант класса DepositAccount: context DepositAccount inv balanceValue: баланс DepositAccount должен быть нулевым или положительным self.balance >= 0.0 25.10. Подробно о типах OCL выражений 561 Обратите внимание, как оба этих класса, переопределив инвариант класса BankAccount::balance, усилили его. Ограничение, касающееся уникальности accountNumber каждого счета, можно представить как инвариант класса BankAccount: context BankAccount inv uniqueAccountNumber: у каждого BankAccount должен быть уникальный accountNumber BankAccount::allInstances( ) >isUnique( account | account.accountNumber ) Из рис. 25.13 видно, что у каждого BankAccount есть единственный вла делец ( owner) и один или более операторов (operator). Владелец – это че ловек ( Person), которому принадлежит счет, а операторы – это люди ( People), имеющие право снимать деньги и имеющие доступ к информа ции счета. Существует бизнес ограничение о том, что owner должен быть также и operator. Данное ограничение может быть отражено сле дующим образом: context BankAccount inv ownerIsOperator: владельцем BankAccount должен быть один из его операторов self.operators >includes( self.owner ) Для Person можно записать такое ограничение: context Person inv ownedAccountsSubsetOfOperatedAccounts: счета, принадлежащие Person (ownedAccount), должны быть подмножеством счетов, управляемых этим Person (operatedAccount) self.operatedAccounts >includesAll( self.ownedAccounts ) При сравнении объектов в OCL выражениях необходимо помнить, что они могут быть: • идентичные – каждый объект ссылается на одну и ту же область па мяти (имеют идентичные объектные ссылки); • эквивалентные – каждый объект имеет одинаковый набор значе ний атрибутов, но разные объектные ссылки. В приведенных выше OCL выражениях мы всегда аккуратны при сравнении объектов на основании их идентичности или эквивалентно сти соответственно. С этим надо проявлять осторожность. Например, сравнение объектов BankAccount (сравнение на основании идентично сти) не то же самое, что сравнение accountNumber этих объектов (сравне ние на основании эквивалентности). 25.10.2. pre:, post: и @pre Предусловия и постусловия применяются к операциям. Их экземпля ром контекста является экземпляр классификатора, которому при надлежат эти операции. 562 Глава 25. Введение в OCL pre: и post: применяются к операциям. • Предусловия определяют сущности, которые должны быть истин ны перед выполнением операции. • Постусловия определяют сущности, которые должны быть истин ны после выполнения операции. Вернемся к нашему примеру BankAccount на рис. 25.13 и рассмотрим операцию deposit(...), унаследованную обоими подклассами, Checking Account и DepositAccount, от BankAccount. Установлено два бизнес правила. 1. Сумма ( amount) вклада должна быть больше нуля. 2. После выполнения операции сумма должна быть добавлена в ба ланс ( balance). Эти правила могут быть выражены кратко и точно в виде предусловий и постусловий операции BankAccount::deposit(...): context BankAccount::deposit( amount : Real ) : Real pre amountToDepositGreaterThanZero: сумма вклада должна быть больше нуля amount >0 post depositSucceeded: окончательный баланс должен быть суммой исходного баланса и вклада self.balance = self.balance@pre + amount Предусловие amountToDepositGreaterThanZero (сумма вклада больше нуля) должно быть истинным, чтобы обеспечить возможность выполнения операции. Оно гарантирует: • невозможность нулевых вкладов; • невозможность вкладов с отрицательным значением суммы. Постусловие depositSucceeded (вклад сделан успешно) должно быть true после выполнения операции. Оно определяет увеличение исходного баланса ( balance@pre) на сумму вклада (amount) для получения оконча тельного баланса. attributeName@pre ссылается на значение перед выполнением операции. Обратите внимание на ключевое слово @pre. Оно может использовать ся только в постусловиях. Атрибут balance принимает одно значение до выполнения операции и другое значение после ее выполнения. Вы ражение balance@pre ссылается на значение balance перед выполнением операции. Часто в постусловии необходимо сослаться на исходное зна чение чего либо. Для полноты информации приводим ограничения, налагаемые на опе рацию BankAccount::withdraw(...). 25.10. Подробно о типах OCL выражений 563 context BankAccount::withdraw( amount : Real ) pre amountToWithdrawGreaterThanZero: снимаемая сумма должна быть больше нуля amount >0 post withdrawalSucceeded: окончательный баланс – это разность исходного баланса и снятой суммы self.balance = self.balance@pre amount Прежде чем завершить обсуждение предусловий и постусловий, необ ходимо рассмотреть наследование. Когда подкласс переопределяет операцию, он принимает предусловия и постусловия переопределяе мой операции. Он может только изменить их. • Переопределенная операция может только ослабить предусловие. • Переопределенная операция может только усилить постусловие. Эти ограничения гарантируют сохранение принципа замещаемости (раздел 10.2). 25.10.3. body: OCL можно использовать для определения результата операции запроса. Все операции getXXX() в нашей простой модели BankAccount (рис. 25.13) являются операциями запроса. BankAccount::getBalance( ) : Real BankAccount::getOwner() : Person BankAccount::getOperators() : Set( Person ) CheckingAccount::getAvailableBalance() : Real CheckingAccount::getAvailableOverdraft() : Real OCL выражения для операций запроса BankAccount тривиальны, и обыч но их написание не должно вызывать затруднений. Выражения приве дены ниже в качестве примера: context BankAccount::getBalance( ) : Real body: self.balance context BankAccount::getOwner( ) : Person body: self.owner context BankAccount::getOperators() : Set(Person) body: self.operators Операции запроса класса CheckingAccount более интересны: context CheckingAccount::getAvailableBalance() : Real body: можно снимать сумму, не превышающую ограничения по превышению кредита self.balance + self.overdraftLimit 564 Глава 25. Введение в OCL context CheckingAccount::getAvailableOverdraft() : Real body: if self.balance >= 0 then возможность превышения кредита доступна полностью self.overdraftLimit else возможность превышения кредита использована частично self.balance + self.overdraftLimit endif Как видите, в этих двух операциях запроса OCL определяет, как вы числяется результат операции. Возвращаемое значение операции – это результат вычисления OCL выражения. 25.10.4. init: OCL может использоваться для задания начального значения атрибу тов. Например: context BankAccount::balance init: 0 Обычно эта возможность OCL используется в случае сложности инициа лизации. Простые инициализации (как та, что приведена выше) лучше всего размещать прямо в ячейке атрибутов класса. 25.10.5. def: OCL позволяет добавлять атрибуты и операции в классификатор с по мощью стереотипа « OclHelper». Они могут использоваться только в OCL выражениях. Добавленные атрибуты в OCL называют перемен ными. Их применение во многом аналогично использованию перемен ных в других языках программирования. Добавленные операции на зываются вспомогательными операциями, потому что они «помогают» в OCL выражениях. def: позволяет определить переменные и вспомогательные операции классификатора для использования в других OCL выражениях. Переменные и вспомогательные операции используются для упроще ния OCL выражений. Рассмотрим пример. Возьмем ограничения, определенные ранее: context CheckingAccount::getAvailableBalance() : Real body: можно снимать сумму, не превышающую ограничения превышения кредита balance + overdraftLimit context CheckingAccount::getAvailableOverdraft() : Real 25.10. Подробно о типах OCL выражений 565 body: if balance >= 0 then возможность превышения кредита доступна полностью overdraftLimit else возможность превышения кредита использована частично balance + overdraftLimit endif Можно заметить, что операция balance + overdraftLimit присутствует в двух выражениях. Поэтому есть смысл определить ее один раз как переменную availableOverdraft (доступное превышение кредита), которая может использоваться обоими выражениями. Для этого применяется оператор def:. context CheckingAccount def: availableBalance = balance + overdraftLimit Теперь можно переписать эти два ограничения, используя введенную переменную: context CheckingAccount::getAvailableBalance() : Real body: можно снимать сумму, не превышающую ограничения превышения кредита availableBalance context CheckingAccount::getAvailableOverdraft() : Real body: if balance >= 0 then возможность превышения кредита доступна полностью overdraftLimit else возможность превышения кредита использована частично availableBalance endif Можно также определить вспомогательные операции. Например, в OCL выражениях может быть полезной операция для проверки возможно сти снятия денег со счета. Ее можно было бы определить следующим образом: context CheckingAccount def: canWithdraw( amount : Real ) : Boolean = ( (availableBalance amount ) >= 0 ) 25.10.6. Выражения let Если def: предоставляет возможность определять переменные уровня контекста выражения, то let позволяет задавать переменные, область действия которых ограничена конкретным OCL выражением. Эти пере 566 Глава 25. Введение в OCL менные подобны локальным переменным в обычных языках програм мирования. И назначение их практически такое же – хранение вычис ленного значения, используемого в выражении более одного раза. let определяет локальную переменную OCL выражения. Выражение let состоит из двух частей – let и in. let <имяПеременной>:<типПеременной> = <выражениеLet> in <использующееВыражение> В первой части значение выражения let (<выражениеLet>) присваивается переменной ( <имяПеременной>). Вторая часть определяет OCL выраже ние, являющееся областью действия переменной и местом, где она мо жет использоваться ( <использующееВыражение>). В нашем примере банковского счета на самом деле нет необходимости в выражении let. Однако для иллюстрации рассмотрим пример, в кото ром определена переменная originalBalance (исходный баланс), локаль ная для ограничения withdrawalSucceeded. context BankAccount::withdraw( amount : Real ) post withdrawalSucceeded: let originalBalance : Real = self.balance@pre in окончательный баланс – это исходный баланс минус снятая сумма self.balance = originalBalance amount |