Một Hướng Dẫn Toàn Diện Về Việc Sử Dụng OCL trong UML Với Các Ví Dụ PlantUML
Giới Thiệu

Trong khi UML (Ngôn ngữ Mô hình Hóa Đơn Nhất) xuất sắc trong việc trực quan hóa cấu trúc của một hệ thống, nhưng nó thiếu độ chính xác để thể hiện các quy tắc kinh doanh và ngữ nghĩa. Đây chính là nơi Ngôn ngữ Ràng buộc Đối tượng (OCL) phát huy vai trò. OCL hoạt động như “keo dán” hình thức, kết nối các sơ đồ UML với logic kinh doanh nghiêm ngặt, rõ ràng.
Hướng dẫn này giải thích cách OCL tích hợp với UML, các khái niệm cốt lõi bạn cần biết, và cung cấp các ví dụ thực tế PlantUML ví dụ để trực quan hóa các ràng buộc này.
1. Sự Kết Hợp: OCL Hỗ Trợ UML Như Thế Nào
Hãy hình dung UML như “khung xương” của hệ thống bạn (lớp, liên kết, trạng thái) và OCL như “hệ thần kinh” (các quy tắc, điều kiện và logic điều khiển cách khung xương vận hành).
Biểu Diễn Trực Quan trong UML
Trong UML tiêu chuẩn, các ràng buộc OCL thường được biểu diễn bằng Ghi chú đính kèm vào các phần tử mô hình liên quan (lớp, thao tác hoặc trạng thái).
-
Stereotype: Thường thì các ghi chú chứa OCL được gán cho
{ràng buộc}stereotype. -
Bối cảnh: Mỗi biểu thức OCL đều bắt đầu bằng một
contexttừ khóa, khai báo đối tượng UML mà quy tắc áp dụng vào.
2. Các khái niệm chính của OCL trong UML
A. Các bất biến lớp (inv)
Các bất biến là các điều kiện mà phải luôn luôn đúng với mọi thể hiện của một lớp, bất kể thao tác nào được gọi.
Khái niệm chính: Nếu một bất biến bị vi phạm, hệ thống sẽ ở trạng thái không hợp lệ.

@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
**Các bất biến lớp:**
context BankAccount
-- Số dư không bao giờ được âm trừ khi cho phép rút quá hạn
inv balanceRule:
self.balance >= 0 hoặc self.isOverdraftEnabled
-- Giới hạn rút quá hạn không được vượt quá 500
inv overdraftLimit:
self.isOverdraftEnabled ngụ ý self.balance >= -500
end note
@enduml
B. Hợp đồng thao tác (pre, post, body)
Các thao tác (phương thức) có thể có các ràng buộc định nghĩa hành vi của chúng trước và sau khi thực thi.
-
Điều kiện tiền đề (
pre): Điều gì phải đúng trước thao tác bắt đầu. (Trách nhiệm của người gọi). -
Điều kiện sau (
sau): Điều gì sẽ đúng sau thao tác kết thúc. (Cam kết của thao tác). -
Thân (
thân): Xác định giá trị trả về chính xác cho các thao tác truy vấn.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9
class ShoppingCart {
- items: Bag<Product>
- totalAmount: Real
+ checkout(): Receipt
+ getTotal(): Real
}
note right of ShoppingCart
**Hợp đồng thao tác:**
-- Điều kiện trước: Giỏ hàng không được trống
context ShoppingCart::checkout(): Receipt
pre: self.items->notEmpty()
-- Điều kiện sau: Giỏ hàng được dọn rỗng, hóa đơn được tạo ra
post: self.items->isEmpty() and result <> null
-- Thân: Định nghĩa thao tác truy vấn
context ShoppingCart::getTotal(): Real
body: self.items->collect(price)->sum()
end note
@enduml
C. Giá trị được suy ra và giá trị ban đầu (suy ra, ban đầu)
Thay vì lưu trữ dữ liệu trùng lặp, UML cho phép các thuộc tính được đánh dấu là {suy ra} hoặc {ban đầu}, với OCL xác định giá trị chính xác của chúng.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD
class Employee {
- firstName: String
- birthDate: Date
- {derived} age: Integer
- {initial} isActive: Boolean = true
}
note right of Employee
**Giá trị được suy ra và ban đầu:**
-- Tính tuổi động
context Employee::age: Integer
derive: self.birthDate.yearsSinceToday()
-- Trạng thái ban đầu khi tạo
context Employee::isActive: Boolean
init: true
end note
@enduml
D. Bảo vệ Máy trạng thái
Trong sơ đồ Máy trạng thái, các chuyển tiếp có thể có bảo vệ—biểu thức logic phải đánh giá thành đúng để chuyển tiếp xảy ra. OCL là ngôn ngữ chuẩn để viết các bảo vệ này.

@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
**Bất biến trạng thái:**
context Order
inv: self.status = 'Pending' implies
self.submittedBy->notEmpty()
end note
note right of Approved
**Điều kiện hậu của approve():**
context Order::approve()
post: self.status = 'Approved' and
self.approvedDate <> null
end note
@enduml
3. Nghiên cứu trường hợp toàn diện: Mô hình Công ty
Hãy áp dụng OCL vào một sơ đồ Lớp UML phức tạp bao gồm Nhân viên, Phòng ban, và Dự án. Điều này minh họa cách OCL đi qua các mối quan hệ và sử dụng bộ lặp tập hợp (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
**Bất biến phức tạp và Điều hướng:**
context Employee
-- 1. Quy tắc thuộc tính cơ bản
inv ageCheck: self.age() >= 18
-- 2. Điều hướng mối quan hệ (Độ đa dạng)
inv maxProjects: self.worksOn->size() <= 4
-- 3. Sử dụng bộ lặp (forAll)
-- Trưởng phòng phải được tuyển dụng trước nhân viên trực thuộc
inv hireOrder: self.subordinates->forAll(
sub | sub.hireDate > self.hireDate
)
-- 4. Quy tắc chéo mối quan hệ
-- Nhân viên chỉ được làm việc trên các dự án do phòng ban của họ điều khiển
inv projectControl:
self.worksFor.controls->includesAll(self.worksOn)
-- 5. Mã định danh duy nhất (Khóa)
inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 |
e1 <> e2 implies e1.SSN <> e2.SSN
)
end note
note bottom of Department
**Thuộc tính được suy ra:**
context Department::nbrEmployees: Integer
derive: self.worksFor->size()
end note
note right of Project
**Bất biến Dự án:**
context Project
-- Vị trí dự án phải là một trong các vị trí của phòng ban
inv validLocation:
self.controls.locations->includes(self.location)
end note
@enduml
4. Kỹ thuật OCL nâng cao trong UML
A. Sử dụng let vì tính dễ đọc
Khi một biểu thức OCL trở nên quá phức tạp, hãy sử dụng lettừ khóa để định nghĩa các biến cục bộ bên trong ràng buộc. Điều này giúp ghi chú UML trở nên dễ đọc hơn nhiều.

@startuml
skinparam classAttributeIconSize 0
class Employee {
- worksOn: Set<Project>
}
note right of Employee
**Sử dụng 'let' cho logic phức tạp:**
context Employee
-- Tính tổng giờ và kiểm tra giới hạn
inv workingHours:
let totalHours: Integer =
self.worksOn->collect(hours)->sum()
in totalHours >= 30 và totalHours <= 50
end note
@enduml
B. Xử lý các giá trị trước trong điều kiện sau (@pre)
Khi định nghĩa điều kiện sau, bạn thường cần so sánh trạng thái mới trạng thái với cũ trạng thái. OCL sử dụng ký hiệu @pre sau để truy cập giá trị của một thuộc tính chính xác như lúc bắt đầu thao tác.

@startuml
skinparam classAttributeIconSize 0
class Company {
- employees: Set<Employee>
+ hireEmployee(p: Employee)
}
note right of Company
**Sử dụng @pre trong điều kiện sau:**
context Company::hireEmployee(p: Employee)
-- Tập hợp nhân viên mới là tập hợp cũ + người mới
post: self.employees = self.employees@pre->including(p)
end note
@enduml
5. Các thực hành tốt nhất cho OCL trong UML
-
Đặt tên cho các ràng buộc: Luôn đặt tên cho các bất biến và điều kiện của bạn (ví dụ:
inv maxProjects:). Điều này giúp dễ tham chiếu chúng trong tài liệu hoặc ma trận theo dõi. -
Sử dụng ghi chú một cách chiến lược: Trong PlantUML và các công cụ UML trực quan, đừng làm rối hộp lớp bằng OCL. Đặt OCL vào các ghi chú liền kề. Điều này giúp sơ đồ sạch sẽ và dễ đọc.
-
Đừng ràng buộc quá mức: Chỉ viết OCL cho các quy tắc không thể diễn đạt dễ dàng thông qua các bội số UML (ví dụ như
0..1,1..*) hoặc kiểu cơ bản. Nếu một quy tắc có thể được thể hiện trực quan, hãy thể hiện nó theo cách trực quan. -
Tận dụng các bộ lặp tập hợp: Nắm vững
->select(),->forAll(),->exists(), và->collect(). Chúng là công cụ mạnh mẽ nhất để điều hướng các liên kết UML trong OCL. -
Kiểm tra các giá trị null/tập rỗng: Khi điều hướng các liên kết tùy chọn (bội số
0..1hoặc0..*), luôn sử dụng->notEmpty()hoặc->isEmpty()trước khi truy cập thuộc tính để tránh lỗi đánh giá không xác định.














