Một Hướng Dẫn Toàn Diện Về Ngôn Ngữ Ràng Buộc Đối Tượng (OCL)
Dựa trên các tài liệu bài giảng được cung cấp, hướng dẫn này bao quát các khái niệm cơ bản, cú pháp và các ứng dụng thực tiễn của Ngôn ngữ Ràng Buộc Đối Tượng (OCL) trong bối cảnh mô hình hóa UML.

1. Giới thiệu về OCL
Tại sao chúng ta cần OCL?
Mặc dù các sơ đồ UML (như sơ đồ lớp) rất tốt trong việc trực quan hóa cấu trúc, chúng không thể biểu diễn tất cả các khía cạnh liên quan của một bản mô tả.
-
Ngôn ngữ tự nhiên là mơ hồ.
-
Các ngôn ngữ hình thức truyền thống rõ ràng nhưng quá khó đối với người mô hình hóa hệ thống trung bình để đọc và viết.
-
OCL lấp đầy khoảng trống này: nó là một ngôn ngữ hình thức được sử dụng để biểu diễn các ràng buộc chính xác, nhưng vẫn giữ được dễ đọc và dễ viết.
Những Đặc Trưng Chính Của OCL
-
Ngôn ngữ Biểu Thức Thuần Túy: Các biểu thức không có hiệu ứng phụ. Đánh giá một biểu thức OCL trả về một giá trị nhưng không thể thay đổi trạng thái của hệ thống. Tuy nhiên, nó có thể mô tả một thay đổi trạng thái (ví dụ, trong một điều kiện hậu).
-
Không phải là Ngôn Ngữ Lập Trình: Bạn không thể viết logic chương trình, luồng điều khiển, hoặc gọi các quy trình/thao tác làm thay đổi trạng thái.
-
Ngôn ngữ Có Kiểu: Mỗi biểu thức đều có kiểu. Các biểu thức hợp lệ phải tuân theo các quy tắc phù hợp kiểu. Mỗi bộ phân loại UML đều là một kiểu OCL.
-
Đánh Giá Tức Thời: Trạng thái của các đối tượng không thể thay đổi trong quá trình việc đánh giá một biểu thức OCL.
2. Ngữ pháp và ngữ nghĩa cơ bản
Bối cảnh và Self
Mỗi biểu thức OCL đều được đánh giá trong bối cảnh của một bộ phân loại UML cụ thể (lớp, giao diện, v.v.).
-
bối cảnh: Xác định lớp mà ràng buộc áp dụng. -
self: Một từ khóa được bảo lưu, tham chiếu đến một thể hiện cụ thể của lớp bối cảnh đang được đánh giá.
bối cảnh Person
-- 'self' tham chiếu đến một thể hiện cụ thể của Person
Ghi chú và toán tử
-
Ghi chú: Được đánh dấu bằng
--(ví dụ,-- đây là một ghi chú). -
Toán tử trung tố: Bạn có thể sử dụng các toán tử toán học/logic chuẩn (
+,-,=,<). Về mặt khái niệm,a + bgiống hệt vớia.+(b).
Truy cập thuộc tính
Sử dụng ký hiệu chấm để truy cập các thuộc tính hoặc thao tác. Dấu ngoặc đơn là bắt buộc cho các thao tác, ngay cả khi chúng không có tham số.
bối cảnh Person
self.age -- Truy cập thuộc tính 'age'
self.stockPrice() -- Gọi thao tác 'stockPrice'
3. Kiểu và Bộ sưu tập
Các kiểu được định nghĩa sẵn cơ bản
OCL bao gồm các kiểu chuẩn với các thao tác cụ thể:
-
Boolean:
đúng,sai(Thao tác:và,hoặc,không,ngụ ý,xoặc) -
Nguyên / Thực: Số (Thao tác:
+,-,*,/,abs,max,min,div,mod) -
Chuỗi: Văn bản (Thao tác:
kích thước,nối,con chuỗi,thànhSốNguyên)
Bộ sưu tập
Bộ sưu tập là các kiểu trừu tượng được dùng để lưu trữ nhiều phần tử. Chúng được truy cập bằng cách sử dụng ký hiệu -> ký hiệu.
-
Tập hợp: Tập hợp toán học (không có phần tử trùng lặp, không thứ tự).
Tập hợp {1, 2, 5} -
Tập hợp có thứ tự: Không có phần tử trùng lặp, nhưng được sắp xếp theo vị trí.
Tập hợp có thứ tự {5, 4, 3} -
Túi: Cho phép trùng lặp, không theo thứ tự.
Túi {1, 2, 2, 5} -
Dãy: Cho phép trùng lặp, theo thứ tự.
Dãy {1, 2, 5, 10}-
Ghi chú: Ký hiệu
..ký hiệu tạo ra một dãy các số nguyên liên tiếp:Dãy {1..5}.
-
Các thao tác tập hợp chính
-
Chung:
size(),isEmpty(),notEmpty(),includes(v),excludes(v),count(v),sum(). -
Bộ lặp (Cần thiết cho OCL):
-
select(expr): Trả về các phần tử nơi màbiểu_thứclà đúng. -
reject(biểu_thức): Trả về các phần tử nơi màbiểu_thứclà sai. -
collect(biểu_thức): Trích xuất một thuộc tính cụ thể từ tất cả các phần tử vào một bộ sưu tập mới. -
forAll(biểu_thức): Trả vềđúngnếubiểu_thứclà đúng cho tất cả các phần tử. -
exists(biểu_thức): Trả vềđúngnếubiểu_thứclà đúng cho ít nhất một phần tử. -
iterate(elem; acc = init | biểu_thức): Cơ sở chung cho tất cả các bộ lặp.
-
Các biến thể cú pháp bộ lặp:
self.employee->select(age > 50)
self.employee->select(p | p.age > 50)
self.employee->select(p: Person | p.age > 50)
4. Duyệt qua các Mô hình UML
OCL được sử dụng rộng rãi như một ngôn ngữ điều hướng để đi qua các mối quan hệ.
Điều hướng tiêu chuẩn
Điều hướng bằng cách sử dụng tên vai trò đối diện. Kiểu trả về phụ thuộc vào bội số:
-
Bội số
1hoặc0..1: Trả về một đối tượng duy nhất. -
Bội số
*: Trả về mộtTập hợp(hoặcTập hợp có thứ tựnếu được đánh dấu{có thứ tự}).
bối cảnh Công ty
self.manager.age > 40 -- manager là 0..1, trả về một đối tượng Person
self.employee->notEmpty() -- employee là 0..*, trả về một Tập hợp
Nếu tên vai trò bị thiếu, hãy sử dụng tên viết thường của lớp ở đầu kia (ví dụ như self.bank).
Lớp liên kết
Điều hướng đến một lớp liên kết bằng cách sử dụng tên viết thường của lớp liên kết.
bối cảnh Người
self.job->size() >= 1 -- 'job' là lớp liên kết giữa Người và Công ty
Các mối quan hệ đệ quy
Khi một mối quan hệ quay trở lại lớp giống nhau (ví dụ như Cuộc hôn nhân giữa hai Người), bạn phải chỉ định hướng bằng cách sử dụng dấu ngoặc vuông [tênVaiTrò].
bối cảnh Người
self.marriage[wife]->chọn(m | m.đã_kết_thúc = false)->kích_thước() = 1
Các mối quan hệ được xác định rõ
Sử dụng các thuộc tính định danh trong dấu ngoặc vuông để chọn một đối tượng cụ thể.
bối cảnh Ngân hàng
self.customer[12345] -- Trả về đối tượng Người cụ thể có accountNo 12345
self.customer -- Trả về một Tập hợp tất cả khách hàng
Chuyển kiểu lại (ép kiểu)
Sử dụng oclAsType() để truy cập các thuộc tính của một kiểu con hoặc một thuộc tính lớp cha bị ẩn.
self.oclAsType(Sub).p -- Truy cập thuộc tính 'p' được định nghĩa trong kiểu con
self.oclAsType(Super).p -- Truy cập thuộc tính 'p' được định nghĩa trong lớp cha
5. Viết các ràng buộc
1. Các bất biến (inv)
Các điều kiện phải luôn luôn đúng với mọi thể hiện của một lớp.
bối cảnh Công ty
inv: self.số_lượng_nhân_viên <= 50
inv SME: self.giá_cổ_phiếu() > 0 -- 'SME' là tên tùy chọn cho ràng buộc
2. Điều kiện tiền và hậu (pre, post)
Các ràng buộc liên quan đến các thao tác.
-
pre: Phải đúng trước thực thi. -
post: Phải đúng sau thực thi. Sử dụngkết quảcho giá trị trả về. -
@pre: Được sử dụng trong điều kiện hậu để tham chiếu đến giá trị của một thuộc tính trước thao tác bắt đầu.
bối cảnh Person::income(): Integer
pre adult: self.age >= 18
post resultOK: result < 5000
bối cảnh Company::hireEmployee(p: Person)
post: employee = employee@pre->including(p)
3. Biểu thức Body, Khởi tạo và Đã suy ra
-
body: Xác định kết quả của một thao tác truy vấn (có thể đệ quy). -
init: Xác định giá trị khởi đầu của một thuộc tính. -
derive: Xác định một thuộc tính đã suy ra.
bối cảnh Person::income(): Integer
body: self.job.salary->sum()
bối cảnh Person::isMarried: Boolean
init: false
bối cảnh Company::noEmployees: Integer
derive: self.employee->size()
6. Tính năng nâng cao của OCL
Biểu thức Let và Def
-
let: Xác định một biến cục bộ trong một biểu thức duy nhất.let totHours: Integer = self.worksOn->collect(hours)->sum() in totHours >= 30 -
def: Xác định các biến hoặc thao tác có thể tái sử dụng gắn với một bộ phân loại.def: hasTitle(t: String): Boolean = self.job->exists(title = t)
Giá trị không xác định (Logic 3 giá trị)
Nếu một biểu thức con không xác định (ví dụ: đi tới một liên kết null), toàn bộ biểu thức sẽ không xác định. Tuy nhiên, các toán tử Boolean có ngoại lệ ngắn mạch:
-
true or <không xác định>= true -
false and <không xác định>= sai -
sai suy ra <undefined>= đúng -
<undefined> suy ra đúng= đúng
Tính năng lớp và thuộc tính đối tượng được định nghĩa sẵn
-
allInstances(): Trả về một tập hợp tất cả các thể hiện hiện có của một lớp.Person.allInstances()->size() <= 100 -
Thuộc tính đối tượng:
oclIsTypeOf(Loại),oclIsKindOf(Loại),oclInState(Trạng thái),oclIsNew(sử dụng trong điều kiện sau).
7. Nghiên cứu trường hợp toàn diện: Mô hình Công ty
Để củng cố các khái niệm này, dưới đây là một bộ các ràng buộc toàn vẹn được áp dụng cho mô hình cơ sở dữ liệu Công ty (Nhân viên, Phòng ban, Dự án, Người phụ thuộc).
Ràng buộc thuộc tính cơ bản
-- Tuổi phải lớn hơn hoặc bằng 18
bối cảnh Employee inv: self.age() >= 18
-- Ngày tuyển dụng phải sau ngày sinh
bối cảnh Employee inv: self.hireDate > self.birthDate
-- Ngày bắt đầu làm quản lý phải sau ngày tuyển dụng
bối cảnh Employee inv: self.manages->không rỗng suy ra self.manages.startDate > self.hireDate
Ràng buộc điều hướng và mối quan hệ
-- Người giám sát phải lớn tuổi hơn và được trả lương cao hơn nhân viên
bối cảnh Employee
inv: self.supervisor->không rỗng suy ra self.age() > self.supervisor.age()
inv: self.supervisor->không rỗng suy ra self.salary < self.supervisor.salary
-- Người giám sát phải được tuyển dụng trước các nhân viên dưới quyền
bối cảnh Employee
inv: self.subordinates->không rỗng suy ra self.subordinates->forall(e | e.hireDate > self.hireDate)
-- Trưởng phòng phải là nhân viên của phòng đó
bối cảnh Department
inv: self.worksFor->bao gồm(self.manages.employee)
-- Địa điểm dự án phải là một trong các địa điểm của phòng ban
bối cảnh Project
inv: self.controls.locations->bao gồm(self.location)
-- 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
bối cảnh Employee
inv: self.worksFor.controls->bao gồmTất cả(self.worksOn.project)
Ràng buộc tập hợp và bộ đếm
-- Số bảo hiểm xã hội phải là định danh duy nhất trên tất cả nhân viên
bối cảnh Employee
inv: Employee.allInstances->forAll(e1, e2 | e1 <> e2 suy ra e1.SSN <> e2.SSN)
-- Tên người phụ thuộc và mối quan hệ phải duy nhất cho mỗi nhân viên
bối cảnh Employee
inv: self.dependents->forAll(e1, e2 |
e1 <> e2 suy ra (e1.name <> e2.name hoặc e1.relationship <> e2.relationship))
-- Thuộc tính được suy ra: số lượng nhân viên trong một phòng ban
bối cảnh Department
inv: self.nbrEmployees = self.worksFor->size()
-- Nhân viên chỉ được làm việc trên tối đa 4 dự án
bối cảnh Employee
inv: self.worksOn->size() <= 4
Logic phức tạp sử dụng cho và chọn
-- Tổng số giờ làm việc phải nằm trong khoảng từ 30 đến 50 mỗi tuần
bối cảnh Nhân viên
điều kiện: cho totHours: Integer = self.worksOn->thu thập(giờ)->tổng
trong totHours >= 30 và totHours <= 50
-- Nhiều nhất 2 nhân viên có thể làm việc ít hơn 10 giờ trên một dự án cụ thể
bối cảnh Dự án
điều kiện: self.worksOn->chọn(giờ < 10)->kích thước() <= 2
-- Chỉ những quản lý phòng ban mới được phép làm việc ít hơn 5 giờ trên một dự án
bối cảnh Nhân viên
điều kiện: self.worksOn->chọn(giờ < 5)->không rỗng() ngụ ý
self.worksFor.manages.employee = self
-- Nhân viên không có cấp dưới phải làm việc từ 10 giờ trở lên trên TẤT CẢ các dự án của họ
bối cảnh Nhân viên
điều kiện: self.subordinates->rỗng() ngụ ý self.worksOn->mọi(giờ >= 10)
-- Quản lý phòng ban phải làm việc từ 5 giờ trở lên trên TẤT CẢ các dự án do phòng ban kiểm soát
bối cảnh Phòng ban
điều kiện: self.controls->mọi(p: Dự án |
self.manages.employee.worksOn->chọn(giờ >= 5)->chứa(p))
Ngăn chặn chu trình (Logic đệ quy)
-- Một nhân viên không thể giám sát chính mình
bối cảnh Nhân viên điều kiện: self.subordinates->loại trừ(self)
-- Cấu trúc giám sát không được có chu trình
bối cảnh Nhân viên
def: tất_các_cấp_dưới = self.subordinates->hợp(
self.subordinates->thu thập(e: Nhân viên | e.tất_các_cấp_dưới))
điều kiện: self.tất_các_cấp_dưới->loại trừ(self)














