Tải bản đầy đủ (.doc) (11 trang)

Tìm hiểu Composite Design Pattern

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (201.66 KB, 11 trang )

Tìm hiểu Composite Design Pattern
Composite Design Pattern
Mục đích
Mục đích là sắp xếp các đối tượng thành cấu trúc cây để biểu diễn một phần của cây thừa
kế. Việc kết hợp này khiến khách hàng có thể thao tác với các đối tượng riêng lẻ hoặc
một tập các đối tượng theo cách giống hệt nhau.
Động cơ thúc đẩy
Các ứng dụng đồ họa, chẳng hạn như chương trình vẽ hình và các hệ thống capture các sơ
đồ giúp người sử dụng xây dựng các sơ đồ phức tạp từ các thành phần đơn giản. Người
sử dụng có thể nhóm các thành phần nhỏ thành các thành phần lớn. Ta có thể cài đặt các
lớp đơn giản cho các thành phần đồ họa cơ bản như Text hay Lines cộng thêm các lớp
khác đóng vai trò như là các khung chứa các hình cơ bản đó.
Nhưng nếu tiếp cận theo cách này thì có một vấn đề như sau: Mã nguồn sử dụng các lớp
phải tác động lên các đối tượng cơ bản và khung chứa theo những cách là không giống
nhau, mặc dù hầu như là người sử dụng tác động lên chúng cùng một lúc. Việc phải phân
biệt chúng như vậy làm cho ứng dụng trở nên phức tạp. Pattern Composite mô tả lmà thể
nào có thể sử dụng đệ quy các sự kết hợp này để cho khách hàng không phải thực hiện
những việc trên riêng lẻ.
Điểm mấu chốt của Composite pattern là một lớp mô tả cả các hình cơ bản và các khung
chứa nó. Đối với hệ đồ họa thì lớp này gọi là lớp Graphic. Lớp Graphic khai báo các thao
tác như là Draw() – một đặc trưng của các đối tượng đồ họa. Nó cũng khai báo các thao
tác khác mà tất cả các đối tượng Composite chia sẻ, chẳng hạn như thao tác để truy nhập
và điều khiển các thành phần con của nó.
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
1
Tìm hiểu Composite Design Pattern
Các lớp con Line, Rectangle, và Text (theo như hình trên) định nghĩa các đối tượng đồ
họa. Những lớp này cài đặt hàm Draw() để vẽ đường thẳng, vẽ hình chữ nhật, và chữ một
cách riêng lẻ. Do các đối tượng đồ họa cơ bản này không có con, nên trong chúng chẳng
cần cài đặt các phương thức liên quan đến các thành phần con.
Lớp Picture định nghĩa một đối tượng đồ họa phức hợp. Lớp Picture cần phảiu cài đặt


phương thức Draw() để gọi tới Draw() trong đối tượng con của nó, và nó cũng phải cài
đặt các thao tác khác liên quan để các đối tượng con của nó. Vì giao diện của lớp Picture
phải phù hợp với giao diện của lớp Graphic, nên các đối tượng Picture có thể kết hợp với
các đối tượng Picture khác một cách đệ qui.
Sơ đồ sau là cấu trúc của một đối tượng composite đệ qui các đối tượng Graphic
Khả năng
Sử dụng Composite pattern khi:
• Bạn muốn biểu diễn từ một phần tới toàn bộ cây thừa kế các đối tượng.
• Bạn muốn trình khách có thể bỏ qua sự khác nhau giữa tập hợp các tượng với các
đối tượng riêng biệt. Trình khách sẽ đối xử tất cả các đối tượng trong cùng tập
hợp theo cách giống nhau.
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
2
Tìm hiểu Composite Design Pattern
Cấu trúc
Một đối tượng Composite điển hình có thể trông giống như sau:
Các thành phần
• Component (Graphic) Thành phần(đối tượng đồ họa)
o Khai báo giao diện của các đối tượng trong kết tập.
o Cài đặt các hành vi ngầm định cho các giao diện chung của tất cả các lớp
một cách thích hợp.
o Khai báo giao diện để truy cập và quản lý các thành phần con của nó.
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
3
Tìm hiểu Composite Design Pattern
o (Tùy chọn) định nghĩa một giao diện để truy nhập thành phần cha trong
cấu trúc đệ quy, và cài đặt nó nếu đuợc.
• Leaf (Rectangle, Line, Text, etc.) Lá(Hình chữ nhật,đường thẳng,chữ ,…)
o Biểu diễn các đối tượng lá trong kết tập. Nút lá thì không có con.
o defines behavior for primitive objects in the composition. Định nghĩa hành

vi cho các đối tượng gốc trong kết tập.
o
• Composite (Picture) Kết tập(Hình ảnh)
o Định nghĩa hành vi của các thành phần có con.
o Lưu trữ các thành phần con
o Cài đặt các giao diện thực hiện các thao tác liên quan đến các nút con.
• Client - Khách hàng
o Tác động các đối tượng trong kết tập thông qua giao diện của thành phần
Cộng tác
o Khách hàng sử dụng giao diện của lớp thành phần để tương tác với các đối
tượng trong cấu trúc kết tập. Nếu nút nhận là nút lá, thì các yêu cầu được
xử lý trực tiếp. Nếu nút nhận là kết tập, thì nó luôn luôn chuyển các yêu
cầu tới các thành phần con của nó, nó cũng có khả năng thực hiện thêm
một số thao tác nào đó trước hoặc sau khi chuyển hướng
Hiệu quả
Kĩ thuật composite pattern:
• Định nghĩa cây kế thừa các lớp bao gồm các đối tượng nguyên thủy và các đối
tượng kết tập. Các đối tượng nguyên thủy có thể được mở rộng thành nhiều đối
tượng phức tạp hơn, các đối tượng này đến lượt nó cũng có thể được mở rộng
tiếp, và cứ tiếp tục đệ qui như thế. Bất cứ chỗ nào sử dụng một đối tượng nguyên
thủy, thì cũng có thể dùng đối tượng kết tập để thay thế.
• Làm cho chương trình sử dụng đơn giản. Các khách hàng có thể xử lý các kết tập
và các đối tượng riêng biệt có theo cùng một cách. Thông thường, khách hàng
không biết (không quan tâm) liệu chúng đang thao tác trên nút lá hay thành phần
của kết tập. Việc này làm đơn giản code phía trình khách, vì nó tránh được việc
phải viết các hàm có dạng thẻ-và-case trên các lớp định nghĩa kết tập.
• Làm đơn giản hóa việc thêm vào các loại thành phần mới. Các kết tập được thêm
vào hoặc các lớp con Lá làm việc tự động với các cấu trúc đã có và code phía
trình khách. Trình khách không phải thay đổi cho các lớp thành phần mới.
• Có thể làm cho thiết kế của bạn trở nên tổng quát. Điểm không thuận lợi của việc

dễ dàng thêm các thành phần mới là nó làm cho việc loại bớt các thành phần của
kết tập trở nên khó khăn. Thỉnh thoảng, bạn muốn một kết tập chỉ có chính xác
các thành phần nào đó. Với Composite, bạn không thể dựa vào các kiểu có sẵn để
đưa ra các ràng buộc cho bạn. Bạn sẽ phải sử dụng kiểm tra trong-khi-thực-thi để
thay thế.
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
4
Tìm hiểu Composite Design Pattern
Thực thi
Có nhiều vấn đề để xem xét khi cài đặt:
1. Các tham chiếu lớp cha rõ ràng. Việc duy trì các tham chiếu từ các thành phần
con tới cha của chúng có thể làm đơn giản hóa các trở ngại và việc quản lý cấu
trúc của kết tập. Tham chiếu cha đơn giản hóa việc di chuyển lên mức trên trong
cấu trúc cây và xóa bỏ thành phần. Các tham chiếu cha cũng hỗ trợ “Chain of
Responsibility pattern”.
Vị trí thông thường để định nghĩa tham chiếu tới cha là trong lớp Component. Lá
và các lớp Composite có thể kế thừa tham chiếu và các thao tác mà quản lý nó.
Với việc có các tham chiếu tới cha, thì việc duy trì bất biến số lượng con của một
kết tập cũng như số lượng kết tập bao gồm nó(Tức là số lượng cha của kết tập
đang xét bằng số lượng con của nó). Cách đơn giản nhất để đảm bảo đó là chỉ
thay đổi chỉ có cha của một thành phần khi nó được thêm hoặc loại bỏ khỏi kết
tập. Do đó có thể cài đặt một lần thao tác Thêm và Xóa trong lớp Composite, và
các lớp con cháu của nó sẽ được kế thừa, như vậy việc bảo đảm trên sẽ được thực
hiện tự động.
2. Chia sẻ các thành phần. Việc chia sẻ thành phần rất là hữu ích, ví dụ như, để
giảm các yêu cầu lưu trữ. Nhưng khi một thành phần chỉ có thể có duy nhất một
cha, thì việc chia sẻ các thành phần trở nên khó khăn.
Giải pháp khả thi là cho một con lưu trữ nhiều cha. Nhưng điều này có thể dẫn
đến nhập nhằng như yêu cầu lan truyền ngược lên cây cấu trúc. Pattern Flyweight
chỉ ra cách thực hiện lại thiết kế để tránh lưu trữ các nút cha cùng nhau. Nó làm

việc trong các trường hợp mà các con có thể tránh gửi cho nút cha các yêu cầu
bằng cách mở rộng một vài hoặc tất cả trạng thái của chúng.
3. Cực đại hóa giao diện Component. Một trong những mục tiêu của Composite
pattern là làm cho các trình khách không biết được lá hay các lớp kết tập chúng
đang sử dụng là gì. Để đạt được mục tiêu này, lớp Thành phần nên định nghĩa
nhiều nhất có thể các thao tác chung cho các lớp kết tập và các lớp lá. Lớp
Component thường cung cấp các thực thi mặc định đối với những thao tác này, và
các lớp lá và các lớp kết tập con sẽ override (cài đặt lại-hay chồng hàm) chúng.
Tuy nhiên, mục tiêu này thỉnh thoảng sẽ xung đột với quy tắc của việc thiết kế lớp
kế thừa, nguyên lí một lớp chỉ định nghĩa các thao tác thực sự có ý nghĩa với các
lớp con của nó. Có nhiều thao tác mà Component hỗ trợ dường như không có ý
nghĩa đối với các lớp lá. Làm thế nào mà Component có thể cung cấp thực thi
mặc định cho chúng?
Thỉnh thoảng một sự sáng tạo nhỏ chỉ ra cách mà một thao tác xuất hiện sẽ chỉ có
ý nghĩa cho các lớp kết tập có thể được cài đặt trong tất cả các Component bằng
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
5
Tìm hiểu Composite Design Pattern
cách đưa nó vào lớp Component. Ví dụ, giao diện để truy nhập tới đối tượng con
là yếu tố cơ bản của một lớp Composite nhưng không cần thiết cho các lớp lá.
Nhưng nếu chúng ta xem một lớp lá như một Component mà không bao giờ có
con, thì sau đó chúng ta có thể định nghĩa một thao tác mặc định để truy nhập tới
đối tượng con trong lớp Component mà không bao giờ trả về bất kì nút con nào.
Các lớp lá có thể sử dụng việc luôn thao tác mặc định, nhưng các lớp Composite
sẽ cài đặt lại nó để trả về các con của nó
Thực tế để quản lí các thao tác của lớp con phức tạp hơn và sẽ được thảo luận
trong phần tiếp theo
4. Khai báo các thao tác quản lý nút con. Mặc dù lớp Component cài đặt việc thêm
và loại bỏ các thao tác để quản lý các nút con, một vấn đề quan trọng trong
Composite pattern là những lớp nào trong cây kế thừa Composite sẽ khai báo

những thao tác này. Chúng ta có nên khai báo những toán hạng này trong
Component và làm cho chúng có ý nghĩa đối với các lớp lá, hoặc chúng ta chỉ
nên khai báo và định nghĩa chúng chỉ trong Composite và các lớp con của nó?
Việc quyết định bao gồm sự thỏa hiệp giữa các yếu tố là sự an toàn và trong suốt:
o Định nghĩa giao diện quản trị nút con ở gốc của kế thừa lớp cho bạn sự
trong suốt để bạn có thể xử lý tất cả các thành phần theo cùng một cách.
Cái giá phải trả là sự an toàn, tuy nhiên, bởi vì các trình khách có thể cố
gắng làm những thứ vô nghĩa như là thêm hay loại bỏ những đối tượng từ
các lá.
o Định nghĩa sự quản trị các nút con trong lớp Composite cho bạn sự an
toàn bởi vì bất cứ nỗ lực nào để thêm hay loại bỏ các đối tượng từ các lá
sẽ được phát hiện lúc biên dịch trong ngôn ngữ kiểu tĩnh như C++.Nhưng
bạn mất đi sự trong suốt, bởi vì các lá và Compsites lại có các giao diện
khác nhau.
Chúng ta đã nhấn mạnh sự trong suốt thông qua sự an toàn trong phần trên. Nếu
như bạn lựa chọn sự an toàn, do đó bạn có thể bị mất thông tin về kiểu và phải
chuyển đổi một Component sang Composite. Làm cách nào bạn có thể làm việc
đó mà không phải sử dụng đến sự chuyển đổi kiểu không an toàn?
Một cách tiếp cận là khai báo một phương thức Composite* GetCompposite()
trong lớp Component. Component cung cấp một phương thức mặc định mà trả về
con trỏ rỗng. Lớp Composite định nghĩa lại phương thức này để trả về bản thân nó
thông qua con trỏ this.
class Composite;
class Component {
public:
//
virtual Composite* GetComposite() { return 0; }
};
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
6

Tìm hiểu Composite Design Pattern
class Composite : public Component {
public:
void Add(Component*);
//
virtual Composite* GetComposite() { return this; }
};
class Leaf : public Component {
//
};
GetComposite() cho phép bạn tra hỏi một thành phần để xem nó có phải là một
Composite không. Bạn có thể thực hiện Add hay Remove một cách an toàn trên
Composite nó trả về:
Composite* aComposite = new Composite;
Leaf* aLeaf = new Leaf;
Component* aComponent;
Composite* test;
aComponent = aComposite;
if (test = aComponent->GetComposite()) {
test->Add(new Leaf);
}
aComponent = aLeaf;
if (test = aComponent->GetComposite()) {
test->Add(new Leaf); // sẽ không thêm lá
}
Việc kiểm tra tương tự cho một Composite có thể được thực hiện bằng cách sử
dụng cấu trúc ép kiểu tự động của C++.
Tất nhiên, vấn đề ở đây là chúng ta không xử lý tất cả các thành phần một cách
giống nhau. Chúng ta phải trở lại để kiểm tra đối với các kiểu khác trước khi đưa
ra các hành động thích hợp.

Cách duy nhất để cung cấp sự trong suốt là định nghĩa các hàm Add và Remove
mặc định trong Component. Điều này làm nảy sinh một vấn đề: không có cách
nào để thực thi Component::Add mà không nói tới nhược điểm của nó. Bạn có thể
làm cho nó không thực hiện gì, nhưng như thế lại bỏ qua một điều đáng lưu tâm;
đó là, một nỗ lực để add một cái gì đó vào nút lá sẽ gây ra lỗi. Trong trường hợp
này, hàm Add sinh ra dữ liệu không thích hợp. Bạn có thể delete đối số của nó,
nhưng không như những gì các trình khách mong đợi.
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
7
Tìm hiểu Composite Design Pattern
Thông thường cách xử lí tốt hơn để Add và Remove hỏng bằng cách mặc định (có
lẽ bởi việc sinh ra một ngoại lệ) nếu như thành phần không được phép có các con
hoặc nếu như đối số của Remove không là con của thành phần.
Có một cách thay thế khác là để thay đổi ý nghĩa của “remove” ở một mức độ
không đáng kể. Nếu một thành phần duy trì một tham chiếu tới cha, thì chúng ta
có thể định nghĩa lại Component::Remove để loại bỏ chính nó trong cha của nó.
Tuy nhiên, vẫn không có nhiều ý nghĩa khi thông dịch để tương ứng với Add.
5. Component có nên cài đặt một danh sách Components? Bạn có thể cố định nghĩa
một tập các con như một biến thể hiện trong lớp Component nơi mà việc truy
nhập nút con và quản lí các hàm đã được khai báo. Nhưng việc đặt con trỏ tới nút
con trong lớp cơ sở gây ra một khoảng trống bất lợi cho mỗi nút lá, mặc dù nút lá
không bao giờ có con. Đây là điều quan trọng nếu như có một vài nút con liên
quan trong cấu trúc.
6. Sắp thứ tự các nút con. Nhiều thiết kế chỉ ra một thứ tự của các con của
Composite. Trong ví dụ về đồ họa trước đây,việc sắp thứ tự có thể phản ánh thứ
tự từ-trước-ra-sau. Nếu Composites biểu diễn các cây phân tích cú pháp, thì các
câu lệnh ghép có thể là các thể hiện của một Composite mà các con của nó phải
được sắp thứ tự để phản ánh chương trình.
Khi sắp thứ tự nút con là một vấn đề, bạn phải thiết kế nút con truy nhập và quản
trị các giao diện một cách cẩn thận để quản lý thứ tự của các nút con. Mẫu lặp có

thể chỉ dẫn bạn về điều này.
7. Cache để nâng cao hiệu năng. Nếu bạn cần duyệt hay tìm kiếm trong các kết tập
thường xuyên, lớp Composite có thể cache kết quả thực hoặc chỉ các thông tin
giúp giảm bớt việc duyệt hay tìm kiếm. Chẳng hạn, lớp Picture từ ví dụ
Motivation có thể cache lại khung của các con của nó. Trong khi vẽ hay lựa chọn,
khung viền được cache này cho phép Picture tránh được việc vẽ hay tìm kiếm khi
các con của nó không thể được hiện trong cửa sổ hiện tại.
Changes to a component will require invalidating the caches of its parents. This
works best when components know their parents. So if you're using caching, you
need to define an interface for telling composites that their caches are
invalid.Thay đổi đối với Component sẽ đòi hỏi các cha không được cache. Điều
này làm việc tốt nhất khi các thành phần biết các cha của chúng. Vì vậy nếu bạn
đang sử dụng cache, bạn cần phải định nghĩa một giao diện để báo cho các
Composites biết việc cache của chúng là không có hiệu lực.
8. Ai nên xóa các thành phần? Trong các ngôn ngữ mà không có trình thu rác tự
động, thì lúc tốt nhất để là xóa các Composite tương ứng khi hủy. Một ngoại lệ
cho luật này là khi các đối tượng Leaf là không thể biến đổi được và do đó có thể
được chia sẻ.
9. Cấu trúc dữ liệu nào tốt nhất cho việc lưu trữ các thành phần? Composites có thể
sử dụng các cấu trúc dữ liệu đa dạng để lưu trữ các con của nó, bao gồm danh
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
8
Tìm hiểu Composite Design Pattern
sách liên kết, các cây, các mảng, và các bảng băm. Việc chọn một cấu trúc dữ liệu
(luôn luôn) phụ thuộc vào hiệu suất. Thực tế, thậm chí không cần thiết phải sử
dụng cấu trúc dữ liệu có mục đích đa năng. Thỉnh thoảng các Composites có mỗi
biến cho mỗi nút con, mặc dù các yêu cầu này cho mỗi lớp con của Composite
phải thực thi việc quản trị giao diện của riêng nó.
Sample Code
Các thiết bị như máy tính và máy chơi nhạc thường được tổ chức thành một-phần-hay-

toàn-bộ cây kế thừa những thứ mà nó bao gồm. Ví dụ như, một bộ khung có thể chứa các
ổ đĩa và các tấm ván 2 chiều, bus có thể chứa các thẻ, và một cái hộp có thể chứa khung,
các bus,… Các cấu trúc như thế có thể được mô hình hóa một cách tự nhiên với
Composite pattern.
Lớp Equiment định nghĩa một giao diện cho tất cả các equipment trong toàn thể cây kế
thừa
class Equipment {
public:
virtual ~Equipment();
const char* Name() { return _name; }
virtual Watt Power();
virtual Currency NetPrice();
virtual Currency DiscountPrice();
virtual void Add(Equipment*);
virtual void Remove(Equipment*);
virtual Iterator* CreateIterator();
protected:
Equipment(const char*);
private:
const char* _name;
};
Equiment khai báo các hàm trả về các thuộc tính của một phần equipment, giống như
công suất và giá tiền của nó. Các lớp con thực thi những hàm này cho các kiểu equipment
cụ thể. Equiment cũng khai báo một toán hạng CreateIterator mà trả về Iterator(Xem phụ
lục C)để truy nhập các phần của nó. Việc thực thi mặc định của toán hạng này trả về
NullIterator, mà lặp lại một tập rỗng.
Các lớp con của Equipment có thể chứa các lớp Lá mà biểu diễn các ổ cứng, các mạch
tích hợp và các chuyển mạch
class FloppyDisk : public Equipment {
public:

FloppyDisk(const char*);
virtual ~FloppyDisk();
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
9
Tìm hiểu Composite Design Pattern
virtual Watt Power();
virtual Currency NetPrice();
virtual Currency DiscountPrice();
};
CompositeEquipment là một lớp cơ sở của equipment mà chứa equipment khác.Nó cũng
là một lớp con của Equipment
class CompositeEquipment : public Equipment {
public:
virtual ~CompositeEquipment();
virtual Watt Power();
virtual Currency NetPrice();
virtual Currency DiscountPrice();
virtual void Add(Equipment*);
virtual void Remove(Equipment*);
virtual Iterator* CreateIterator();
protected:
CompositeEquipment(const char*);
private:
List _equipment;
};
CompositeEquipment định nghĩa các hàm để truy nhập và quản trị các subequipment.
Các toán hạng Add và Remove chèn và xóa equipment từ danh sách equipment lưu trữ
trong thành viên _equipment. Hàm CreateIterator trả về một Iterator (đặc biệt, một thể
hiện của ListIterator) để duyệt qua danh sách này.
Việc thực thi mặc định của NetPrice có thể sử dụng CreateIterator để tổng cộng các giá

của thiết bị.
2
Currency CompositeEquipment::NetPrice () {
Iterator* i = CreateIterator();
Currency total = 0;
for (i->First(); !i->IsDone(); i->Next()) {
total += i->CurrentItem()->NetPrice();
}
delete i;
return total;
}
Bây giờ chúng ta có biểu diễn một bộ khung của máy tính như là một lớp con của
CompositeEquipment gọi là Chassis. Chassis kế thừa các hàm liên quan tới các nút con từ
CompositeEquipment.
class Chassis : public CompositeEquipment {
public:
Chassis(const char*);
virtual ~Chassis();
Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
10
Tìm hiểu Composite Design Pattern
virtual Watt Power();
virtual Currency NetPrice();
virtual Currency DiscountPrice();
};
Chúng ta có thể định nghĩa các khung chứa các equipment khác như Cabinet và Bus theo
cách tương tự. Điều đó cho chúng ta mọi thứ chúng ta cần để tập hợp (thật dễ dàng)
equipment thành máy tính cá nhân.
Cabinet* cabinet = new Cabinet("PC Cabinet");
Chassis* chassis = new Chassis("PC Chassis");

cabinet->Add(chassis);
Bus* bus = new Bus("MCA Bus");
bus->Add(new Card("16Mbs Token Ring"));
chassis->Add(bus);
chassis->Add(new FloppyDisk("3.5in Floppy"));
cout << "The net price is " << chassis->NetPrice() << endl;
2
Thật dễ để quên đi việc xóa Iterator khi bạn đã thực hiện nó. Pattern Iterator chỉ ra cách
để bảo vệ để chống lại các lỗi xảy ra.

Nhóm 16 – HTTT – K49 – Phạm Trọng Đức – Nguyễn Ngọc Chung
11

×