راهنمای جامع استفاده از OCL در UML با مثالهای PlantUML
مقدمه

اگرچه UML (زبان مدلسازی یکپارچه) برای نمایش ساختار یک سیستم استثنایی است، اما دارای دقت کافی برای بیان قوانین کاربردی پیچیده و معنایی قوانین کاربردی و معانی. اینجا جایی است که زبان محدودیت شیء (OCL) ورود پیدا میکند. OCL به عنوان «چسب رسمی» عمل میکند که نمودارهای UML را به منطق کاربردی سخت و بدون ابهام متصل میکند.
این راهنما توضیح میدهد که OCL چگونه با UML ادغام میشود، مفاهیم کلیدی که باید بدانید، و مثالهای عملی PlantUML مثالها را برای نمایش این محدودیتها ارائه میدهد.
1. همافزایی: OCL چگونه با UML تکمیل میشود
UML را به عنوان «استخوانبندی» سیستم خود (کلاسها، ارتباطات، وضعیتها) و OCL را به عنوان «سیستم عصبی» (قوانین، شرایط و منطقی که نحوه رفتار استخوانبندی را کنترل میکند) در نظر بگیرید.
نمایش بصری در UML
در UML استاندارد، محدودیتهای OCL معمولاً با استفاده از یادداشتها که به عناصر مدل مربوطه (کلاسها، عملیات یا وضعیتها) متصل میشوند.
-
استرئوتایپ:اغلب، نوتهای حاوی OCL به عنوان
{محدودیت}استریوتایپ. -
زمینه:هر عبارت OCL با کلمه کلیدی
زمینهشروع میشود که مشخص میکند قانون به کدام عنصر UML اعمال میشود.
2. مفاهیم کلیدی OCL در UML
الف. موارد ثابت کلاس (inv)
موارد ثابت شرایطی هستند که بایدهمیشه درست باشندبرای هر نمونهای از کلاس، بسته به اینکه چه عملیاتی فراخوانی شوند یا نشوند.
مفهوم کلیدی:اگر یک مورد ثابت نقض شود، سیستم در حالت نامعتبر قرار دارد.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFF9C4
class BankAccount {
- accountNumber: String
- balance: Real
- isOverdraftEnabled: Boolean
+ withdraw(amount: Real)
+ deposit(amount: Real)
}
note right of BankAccount
**موارد ثابت کلاس:**
context BankAccount
-- مانده هرگز منفی نمیتواند باشد مگر اینکه وام بیش از حد فعال باشد
inv balanceRule:
self.balance >= 0 or self.isOverdraftEnabled
-- حد وام بیش از حد نمیتواند بیش از 500 باشد
inv overdraftLimit:
self.isOverdraftEnabled implies self.balance >= -500
end note
@enduml
ب. قراردادهای عملیات (pre, post, بدن)
عملیات (روشها) میتوانند محدودیتهایی داشته باشند که رفتار آنها قبل و بعد از اجرای عملیات را تعریف میکنند.
-
شرایط پیش از (
pre):چیزی که باید درست باشدقبل ازشروع عملیات. (مسئولیت فراخوان). -
شرایط پس از (
پس):چیزی که درست خواهد بودبعد ازپایان عملیات. (ضمانت عملیات). -
بدنه (
بدنه):مقدار دقیق بازگشتی برای عملیات پرس و جو را تعریف میکند.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9
class ShoppingCart {
- items: Bag<Product>
- totalAmount: Real
+ checkout(): Receipt
+ getTotal(): Real
}
note right of ShoppingCart
**قراردادهای عملیاتی:**
-- شرط پیش از: سبد خالی نباشد
context ShoppingCart::checkout(): Receipt
pre: self.items->notEmpty()
-- شرط پس از: سبد خالی شود، فاکتور تولید شود
post: self.items->isEmpty() and result <> null
-- بدنه: تعریف عملیات پرس و جو
context ShoppingCart::getTotal(): Real
body: self.items->collect(price)->sum()
end note
@enduml
ج. مقادیر مشتق شده و اولیه (مشتق شده, اولیه)
به جای ذخیرهسازی دادههای تکراری، UML به ویژگیها اجازه میدهد که به عنوان{مشتق شده}یا{اولیه}علامتگذاری شوند، که OCL مقدار دقیق آنها را تعریف میکند.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD
class Employee {
- firstName: String
- birthDate: Date
- {derived} age: Integer
- {initial} isActive: Boolean = true
}
note right of Employee
**مقادیر مشتق شده و اولیه:**
-- محاسبه سن به صورت پویا
context Employee::age: Integer
derive: self.birthDate.yearsSinceToday()
-- حالت اولیه در زمان ایجاد
context Employee::isActive: Boolean
init: true
end note
@enduml
د. محافظهای ماشین حالت
در نمودارهای ماشین حالت، انتقالها میتوانند داشته باشندمحافظها—عبارات منطقی که باید به درستی ارزیابی شوند تا انتقال انجام شود. OCL زبان استاندارد برای نوشتن این محافظهاست.

@startuml
skinparam stateBackgroundColor #F3E5F5
[*] --> Created
Created --> PendingApproval : submit()
state PendingApproval {
}
PendingApproval --> Approved : [total < 10000]
PendingApproval --> Rejected : [total >= 10000]
Approved --> Shipped : ship()
Shipped --> [*]
note right of PendingApproval
**پایداری حالت:**
context Order
inv: self.status = 'Pending' implies
self.submittedBy->notEmpty()
end note
note right of Approved
**شرایط پس از تأیید():**
context Order::approve()
post: self.status = 'Approved' and
self.approvedDate <> null
end note
@enduml
3. مطالعه موردی جامع: مدل شرکت
بیایید OCL را به یک نمودار کلاس UML پیچیده که شامل کارمند, اداره, و پروژه. این نشان میدهد که OCL چگونه به ارتباطات میپردازد و از تکرارکنندههای مجموعه استفاده میکند (select, forAll, size).

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFFDE7
skinparam noteBorderColor #FBC02D
class Employee {
- SSN: String
- firstName: String
- birthDate: Date
- hireDate: Date
- salary: Float
+ age(): Integer
}
class Department {
- number: Integer
- name: String
- locations: Set<String>
- {derived} nbrEmployees: Integer
}
class Project {
- number: Integer
- name: String
- location: String
}
Employee "0..*" -- "1" Department : worksFor > department
Employee "0..*" -- "0..*" Project : worksOn > project
Employee "1" -- "0..1" Department : manages > managedDept
Department "1" -- "0..*" Project : controls > projects
Employee "1" -- "0..*" Employee : supervisor > subordinates
note right of Employee
**پایداریهای پیچیده و کاوش در ارتباطات:**
context Employee
-- 1. قانون ویژگی پایه
inv ageCheck: self.age() >= 18
-- 2. کاوش در ارتباطات (چندگانگی)
inv maxProjects: self.worksOn->size() <= 4
-- 3. استفاده از تکرارکنندهها (forAll)
-- مدیر باید قبل از زیردستها استخدام شود
inv hireOrder: self.subordinates->forAll(
sub | sub.hireDate > self.hireDate
)
-- 4. قانون بین ارتباطات
-- کارمند فقط میتواند در پروژههایی که توسط ادارهاش کنترل میشوند کار کند
inv projectControl:
self.worksFor.controls->includesAll(self.worksOn)
-- 5. شناسه منحصر به فرد (کلید)
inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 |
e1 <> e2 implies e1.SSN <> e2.SSN
)
end note
note bottom of Department
**ویژگی مشتق شده:**
context Department::nbrEmployees: Integer
derive: self.worksFor->size()
end note
note right of Project
**پایداری پروژه:**
context Project
-- مکان پروژه باید یکی از مکانهای اداره باشد
inv validLocation:
self.controls.locations->includes(self.location)
end note
@enduml
4. تکنیکهای پیشرفته OCL در UML
الف. استفاده از تعریفبرای خوانایی
وقتی یک عبارت OCL خیلی پیچیده میشود، از کلمه کلیدی تعریفبرای تعریف متغیرهای محلی درون محدودیت استفاده کنید. این کار نوت UML را بسیار خواناتر میکند.

@startuml
skinparam classAttributeIconSize 0
class Employee {
- worksOn: Set<Project>
}
note right of Employee
**استفاده از 'let' برای منطق پیچیده:**
context Employee
-- محاسبه کل ساعتها و بررسی محدودیتها
inv workingHours:
let totalHours: Integer =
self.worksOn->collect(hours)->sum()
in totalHours >= 30 و totalHours <= 50
end note
@enduml
ب. مدیریت مقادیر قبلی در شرایط پساز عملیات (@pre)
وقتی شرط پساز عملیات را تعریف میکنید، اغلب نیاز دارید تا حالت جدید را با حالت قدیمی مقایسه کنید.جدیدحالت را با حالت قدیمیمقایسه کنید. OCL از علامت @preپسوند برای دسترسی به مقدار یک ویژگی دقیقاً همانطور که قبل از شروع عملیات بود، استفاده میکند.

@startuml
skinparam classAttributeIconSize 0
class Company {
- employees: Set<Employee>
+ hireEmployee(p: Employee)
}
note right of Company
**استفاده از @pre در شرایط پساز عملیات:**
context Company::hireEmployee(p: Employee)
-- مجموعه کارمندان جدید، مجموعه قدیمی به علاوه فرد جدید است
post: self.employees = self.employees@pre->including(p)
end note
@enduml
5. بهترین روشها برای OCL در UML
-
محدودیتهای خود را نامگذاری کنید:همیشه محدودیتها و شرایط خود را نامگذاری کنید (مثلاً
inv maxProjects:). این کار باعث میشود که در مستندات یا ماتریسهای ردیابی به آنها به راحتی ارجاع داده شود. -
از نوتها به صورت استراتژیک استفاده کنید:در PlantUML و ابزارهای بصری UML، جعبه کلاس را با OCL پر نکنید. OCL را در نوتهای مجاور قرار دهید. این کار نمودار را تمیز و خوانا نگه میدارد.
-
از محدود کردن بیش از حد خودداری کنید:فقط OCL را برای قوانینی بنویسید که به راحتی با ضرایب UML بیان نمیشوند (مثلاً
0..1,1..*) یا انواع پایه. اگر قانونی بتواند به صورت بصری نشان داده شود، آن را به صورت بصری نشان دهید. -
از تکنیکهای تکرارگر گروهها بهره ببرید:مسلط شوید
->select(),->forAll(),->exists(), و->collect(). اینها قویترین ابزارها برای پیمایش ارتباطات UML در OCL هستند. -
بررسی کنید که آیا مقدار null یا مجموعه خالی وجود دارد یا خیر:هنگام پیمایش ارتباطات اختیاری (ضریب
0..1یا0..*) همیشه از->notEmpty()یا->isEmpty()قبل از دسترسی به ویژگیها برای جلوگیری از خطاهای ارزیابی نامشخص استفاده کنید.














