Một sự cộng tác mô tả tương tác giữa các đối tượng được thiết kế để làm việc với nhau. Mỗi đối tượng được thiết kếđể nhằm một mục đích nhất định và tất cả các hoạt động của nó đều nhằm vào hoàn thành mục đích đó. Hầu hết các kỹ năng thiết kế
hướng đối tượng là thiết kế sự cộng tác. Kỹ thuật CRC (classes: các lớp, responsibilities: các trách nhiệm, collaborations: các cộng tác) là kỹ thuật cơ bản của việc thiết kế hướng đối tượng và là sự phân chia các trách nhiệm giữa các đối tượng cộng tác với nhau để làm một công việc nào đó. Chính việc phân tích này phân biệt việc lập trình hướng đối tượng và thiết kế hướng cấu trúc, ở đó tất cả các công việc
được gộp vào một chương trình. Nếu ta làm công việc thiết kế tốt, ta sẽ có được một thiết kế phân rã có tính linh hoạt và mở rộng được.
Khi hoàn thành tốt một thiết kế về sự cộng tác, nó sẽ có tác dụng ghi lại các ý tưởng của người thiết kế và sử dụng lại được các ý tưởng đó. Đây chính là động lực trong việc sử dụng các mẫu thiết kế. Một vài công cụ có khả năng hỗ trợ cách thức
định nghĩa các cộng tác này và kết nối chúng lại với nhau tạo thành các thiết kế lớn hơn.
Các khung làm việc chính là các thành phần có khả năng sử dụng lại của thiết kế. Chúng ta sẽ sử dụng các khung làm việc này vào các thành phần có khả năng sử
dụng lại của sự cộng tác.
Trong thực tế, các đối tượng tham gia vào nhiều sự cộng tác lẫn nhau, và trong mỗi cộng tác đó, nó đều có một vai trò nhất định. Ví dụ, khi miêu tả về sự cộng tác giữa đối tượng nhân viên, nó miêu tả sự tác động lẫn nhau giữa các nhân viên trong công việc. Tuy nhiên, một nhân viên vừa có thể đóng vai trò là người quản lý, vừa
đóng vai trò là nhân viên. Tức là khi tham dự vào nhiều sự cộng tác thì trong mỗi sự
cộng tác, một đối tượng có thểđóng vai trò khác nhau.
Tuy nhiên trong những sự cộng tác như vậy, có thể có các sự cộng tác riêng rẽ ảnh hưởng đến cùng một thuộc tính của một đối tượng. Ví dụ, giá trị tài khoản ngân hàng (thuộc tính) của một người có thểđược tăng lên nhờ vào việc gửi thêm tiền vào tài khoản (sự cộng tác giữa khách hàng và ngân hàng). Mặt khác giá trị tài khoản đó cũng có thể được tăng nhờ vào việc các nhân viên ngân hàng cộng gộp tiền lãi của tài khoản vào trong tài khoản gốc (sự cộng tác của nhân viên ngân hàng và ngân hàng) của người đó.
Để minh họa, ta xem xét sự cộng tác Subject-Observer. Trong hình 12 chúng ta chỉ ra các giao diện bên ngoài của mỗi đối tượng chia được thành các vai trò trong các sự cộng tác khác nhau cho dù các thuộc tính bên trong có thểđược chia sẻ. Sự cộng tác này “quản lý” hai vai trò: Subject và Observer. Subject có thuộc tính value và
Observer có thuộc tính khác được gọi là value_view. Một hành động update (cập nhật)
được khởi tạo bởi Subject và làm cho Observer cập nhật theo.
Hình 12. Sự cộng tác giữa các đối tượng
Tất nhiên một cặp các lớp bất kỳ có mối quan hệ như vậy trong một chuỗi các mã chương trình có thể không được gọi là Subject và Observer. Chúng có vai trò lớn và đáng quan tâm hơn trong chương trình của chúng, có lẽ giống như các thành phần của GUI hay các ủy nhiệm trong các hệ thống phân tán. Nhưng cái đó mới nói lên chính xác khung làm việc là gì: Chúng ta có thể chỉ định nghĩa các khía cạnh về một cái gì đó mà ta muốn nói và sau đó cho phép người dùng sử dụng các tên khác và mở
rộng các định nghĩa khi họ áp dụng khung làm việc của ta.
Chính vì thế, đúng như chúng ta chỉ ra trong hình 12, chúng ta thực sự chỉ biết một phần của đối tượng mà chúng ta đang miêu tả: phần còn lại phụ thuộc vào bất cứ
ai sử dụng khung làm việc. Trong ví dụ này, chúng ta không biết cách thức và tại sao
value của Subject thay đổi. Chúng ta chỉ biết rằng nó có thể thay đổi và khi nó thay đổi
Observer phải được cập nhật.
value value_view
Cập nhật
Subject Observer Các vai trò được nắm giữ bởi phần này của mỗi đối tượng Phần này của đối tượng liên quan đến sự cộng tác và không được biết đến Phần này của đối tượng liên quan đến sự cộng
tác Subject-Observer Biđếến cn nảộ hai phi tại liên quan ần Phần còn lại của đối tượng
3.2.1.1. Các hành động trong khung làm việc cộng tác
Sự khác nhau lớn nhất giữa khung làm việc này và các khung làm việc khác là khung làm việc này có các hành động. Thực tế, khung làm việc này có các hành động sau:
♦ Hành động cập nhật giữa Subject và Observer.
♦ Tất cả các hành động khác mà ta không biết về chúng, các hành động đó thay
đổi giá trị của Subject.
Việc xác định hành động đầu tiên là khá dễ dàng:
action Observer :: update () post value_view = subject.value
- - phản ánh một cách chính xác giá trị value của Subject
Hành động update chỉ là một trong rất nhiều hành động có thể làm thay đổi giá trị value của Subject. Nói một cách hình thức, ta sử dụng một kết quả bất biến, tức là ta không quan tâm đến các hành động xảy ra đối với hệ thống như thế nào, khi nào và như thế nào. Ta chỉ cần biết rằng, khi có một hành động xảy ra thì ta thu được một kết quả bất biến. Ở đây cũng vây, ta không quan tâm xem hành động nào xảy ra, xảy ra khi nào và xảy ra như thế nào đối với giá trị của Subject, nhưng ta chỉ biết một kết quả
bất biến là khi giá trị bị thay đổi thì hành động update luôn xảy ra: nó là một điều kiện sau :
inv effect Subject ::
post value@pre <> value _ [[ observers.update() ]]
---Bất cứ hành động nào thay đổi giá trị value cũng đảm bảo Observer phản ánh chính xác giá trị mới.
Điều kiện sau không chỉ áp dụng cho khung làm việc, mà nó còn phải được thực hiện một cách chính xác trong các chương trình cụ thể là trong các lớp của
Subject và Observer. Không những thế các đặc tả (làm tài liệu) của nó cũng phải miêu tả chính xác về các điều kiện sau này.
Hình sau đây chỉ ra khung làm việc cộng tác như chúng ta đã miêu tả nó trong phần trước. Sự kết hợp Subject-Observer liên kết các thể hiện cụ thể của hai kiểu này và hành động update chỉ áp dụng cho các Observer có một Subject hiện tại. Hành động
register liên kết một Subject cụ thể tới một Observer cụ thể. Nó là một hành động kết nối mà chúng ta không xác định cách thức nó xảy ra, thậm chí ai là người gửi thông
điệp làm cho nó xảy ra, chúng ta chỉ xác định được là sẽ có một hành động như thế, với trách nhiệm thực thi nó được phân bố giữa Subject và Observer.
Hình 13. Khuôn mẫu cộng tác 3.2.1.2. Áp dụng khung làm việc cộng tác
Bây giờ chúng ta sẽ xem xét một áp dụng cho khung làm việc cộng tác đã nói ở
phần trên cho hệ thống quản lý cuộc gọi. Một trong số các kiểu của mô hình được gọi là CallQueue: một danh sách các cuộc gọi. Giả sử chúng ta đã có kiểu đó trong một gói nào đó. Trong gói khác ta có một tập trang bị các giao diện người sử dụng đồ họa (GUI), mà một trong sốđó là Thermometer—dùng cho việc hiển thị giá trị các số.
Bây giờ chúng ta thiết kế một cầu nối giữa lôgic nghiệp vụ và giao diện người sử dụng đồ họa (GUI), và chúng ta tạo ra một gói mà trong đó chúng ta thay thế
Thermometer và CallQueue. Ta muốn Thernmometer được sử dụng để chỉ ra số cuộc gọi ( Calls) trong một danh sách hàng đợi cuộc gọi ( CallQueue). Chính vì vậy chúng ta áp dụng khung làm việc quan sát như hình 14. Tại đây chúng ta thêm vào các đặc tả
<value> <value_view> Register subject observers update 0,1 0..* <Subject> <Observer> Observer :: action update ( )
pre subject <> null
post value_view = subject.value
(s: Subject, o: Observer) :: action register ( ) pre o.subject = null post o.subject = s
Subject ::
inv effect
post value @ pre <> value ® [[observers.update()]]
cần thiết. Hình 13 hiển thị mô hình được mở: kết quả của việc áp dụng khung làm việc và đưa ra đặc tả.
Hình 14. Mô hình kiểu đích cho việc sử dụng sự quan sát
Hình 15. Ví dụứng dụng khung làm việc và sự thay thế
addCall (c : Call)
post calls = calls @ pre + c getCall ( ) : Call
post result = calls @ pre->head & calls = calls @ pre->tail
temperature : int other stuff about Thermometers Thermometer Call CallQueue calls 0..* {seq} CallQueue Call Thermometer reading Observation Subject [value\calls->size] Observer [value_view\reading] 0..* calls
Hình 16. Kết quả mở của ứng dụng khung làm việc
Khi nhìn vào ứng dụng khung làm việc, ta thấy tên Subject và Observerđã được thay thế và các phương thức addCall và getCall cũng đã được đưa vào, chúng luôn luôn thay đổi độ lớn các cuộc gọi vào mọi lúc, chính vì vậy cần loại bỏ
value<>value@pre từ các hậu điều kiện của chúng. Tại đây, hành động update có thể được nhận diện như thông điệp notify (newvalue) hay theo kiểu MVC (Model, View, Control) thì có thể bao gồm thông điệp update() tới Thermometer, và sau đó, nó phải quay ngược trở lại CallQueueđể yêu cầu chi tiết về sự thay đổi đã xảy ra.
addCall (c : Call) post calls = calls @ pre + c
& [[observers.update ( )]]
getCall ( ) : Call post result = calls @ pre->head
& calls = calls @ pre- >tail & [[observers.update ( )]] CallQueue reading other stuff about Thermometers Thermometer Call calls 0..* {seq} subject observers 0,1 0..* update register Thermometer :: action update ( ) pre subject <> null
post reading = subject.calls->size
(s: CallQueue, o: Thermometer) :: action register ( )
pre o.subject = null post o.subject = s
Hành động register có thểđược khởi tạo bằng một đối tượng giám sát để chỉ ra một Thermometer cụ thể quan sát một CallQueue cụ thể. Tiếp đó, Thermometer phải chỉ ra chính bản thân nó cho CallQueue biết, có như vậy thì chúng mới biết về nhau.