2012-09-12 49 views
20

Tôi đã đọc mẫu thiết kế trang trí từ wiki http://en.wikipedia.org/wiki/Decorator_pattern và ví dụ mã từ http://www.vincehuston.org/dp/decorator.html.Mẫu thiết kế trang trí so với kế thừa?

Tôi thấy điểm thừa kế truyền thống tuân theo mẫu 'is-a' trong khi trang trí theo sau mẫu 'có-a'. Và quy ước gọi của trang trí trông giống như một 'làn da' trên 'da' .. trên 'lõi'. ví dụ.

I* anXYZ = new Z(new Y(new X(new A))); 

như minh họa trong liên kết ví dụ mã trên.

Tuy nhiên vẫn còn một vài câu hỏi mà tôi không hiểu:

  1. gì wiki ý nghĩa của 'Những mẫu trang trí có thể được sử dụng để mở rộng (trang trí) các chức năng của một đối tượng nhất định tại thời gian chạy '? 'mới ... (mới ... (mới ...))' là cuộc gọi thời gian chạy và tốt nhưng là 'AwithXYZ anXYZ;' là một thừa kế tại thời gian biên dịch và là xấu?

  2. từ liên kết ví dụ mã tôi có thể thấy rằng số định nghĩa lớp gần như giống nhau trong cả hai triển khai. Tôi nhớ lại trong một số cuốn sách mẫu thiết kế khác như 'Đầu mẫu thiết kế đầu tiên'. Họ sử dụng cà phê starbuzz làm ví dụ và nói rằng thừa kế truyền thống sẽ gây ra vụ nổ lớp học '' cho mỗi kết hợp cà phê, bạn sẽ đưa ra một lớp cho nó. Nhưng nó không giống với trang trí trong trường hợp này? Nếu một lớp trang trí có thể mất bất kỳ lớp trừu tượng và trang trí nó, sau đó tôi đoán nó ngăn chặn sự bùng nổ, nhưng từ mã ví dụ, bạn có chính xáC# định nghĩa lớp, không kém ...

bất cứ ai có thể giúp giải thích?

Thanks a lot,

+0

[Khi chúng ta nên sử dụng hoa văn trang trí thiết kế và không thừa kế] (http: // www. singhajit.com/decorator-design-pattern/) –

Trả lời

65

Hãy xem xét một số luồng trừu tượng và tưởng tượng bạn muốn cung cấp dịch vụ mã hóa và nén qua chúng.

Với trang trí bạn có (pseudo code):

Stream plain = Stream(); 
Stream encrypted = EncryptedStream(Stream()); 
Stream zipped = ZippedStream(Stream()); 
Stream zippedEncrypted = ZippedStream(EncryptedStream(Stream()); 
Stream encryptedZipped = EncryptedStream(ZippedStream(Stream()); 

Với thừa kế, bạn có:

class Stream() {...} 
class EncryptedStream() : Stream {...} 
class ZippedStream() : Stream {...} 
class ZippedEncryptedStream() : EncryptedStream {...} 
class EncryptedZippedStream() : ZippedStream {...} 

1) với trang trí, bạn kết hợp các chức năng trong thời gian chạy, tùy thuộc vào nhu cầu của bạn. Mỗi lớp chỉ chăm sóc một khía cạnh chức năng (nén, mã hóa, ...)

2) trong ví dụ đơn giản này, chúng tôi có 3 lớp với trang trí và 5 với thừa kế. Bây giờ, hãy thêm một số dịch vụ khác, ví dụ: lọc và cắt. Với trình trang trí, bạn chỉ cần thêm 2 lớp để hỗ trợ tất cả các tình huống có thể xảy ra, ví dụ: lọc -> clipping -> compression -> encription. Với thừa kế, bạn cần phải cung cấp một lớp cho mỗi kết hợp để bạn kết thúc với hàng chục lớp.

+1

Cảm ơn Zdeslav. Một lời giải thích tuyệt vời về cách trang trí được sử dụng và không gây nổ! – user1559625

+0

Bạn được chào đón :) –

+0

câu trả lời rất hay !! thực sự tốt đẹp – Danyal

1

Để giải quyết phần thứ hai của câu hỏi của bạn (mà có thể lần lượt giải quyết một phần đầu tiên của bạn), sử dụng phương pháp trang trí bạn có thể truy cập cùng một số kết hợp, nhưng không phải viết chúng. Nếu bạn có 3 lớp trang trí với 5 tùy chọn ở mỗi cấp, bạn có 5*5*5 các lớp có thể để xác định sử dụng thừa kế. Sử dụng phương pháp trang trí bạn cần 15.

1

Trước hết, tôi là người C# và chưa xử lý C++ trong một thời gian, nhưng hy vọng bạn sẽ đến được nơi tôi đến.

Một ví dụ điển hình mà đến với tâm là một DbRepositoryCachingDbRepository:

public interface IRepository { 
    object GetStuff(); 
} 

public class DbRepository : IRepository { 

    public object GetStuff() { 
    //do something against the database 
    } 
} 

public class CachingDbRepository : IRepository { 
    public CachingDbRepository(IRepository repo){ } 

    public object GetStuff() { 
    //check the cache first 
    if(its_not_there) { 
     repo.GetStuff(); 
    } 
} 

Vì vậy, nếu tôi chỉ sử dụng thừa kế, tôi muốn có một DbRepositoryCachingDbRepository. DbRepository sẽ truy vấn từ cơ sở dữ liệu; CachingDbRepository sẽ kiểm tra bộ nhớ cache của nó và nếu dữ liệu không có ở đó, nó sẽ truy vấn cơ sở dữ liệu. Vì vậy, có thể thực hiện trùng lặp ở đây.

Bằng cách sử dụng mẫu trang trí, tôi vẫn có cùng số lớp, nhưng CachingDbRepository của bạn có số IRepository và gọi số GetStuff() để lấy dữ liệu từ repo cơ bản nếu nó không có trong bộ nhớ cache.

Vì vậy, số lượng các lớp là như nhau, nhưng việc sử dụng các lớp học có liên quan.CachingDbRepo gọi Repo đã được chuyển vào nó ... vì vậy nó giống như thành phần hơn thừa kế.

Tôi tìm thấy nó chủ quan khi quyết định khi nào chỉ sử dụng thừa kế trên trang trí.

Tôi hy vọng điều này sẽ hữu ích. Chúc may mắn!

7

Trong thứ tự ngược lại:

2) Với, nói, 10 khác nhau độc lập phần mở rộng, bất kỳ sự kết hợp của mà có thể là cần thiết tại thời gian chạy, 10 lớp học trang trí sẽ thực hiện công việc. Để bao gồm tất cả các khả năng của kế thừa bạn cần các lớp con. Và không có cách nào để nhận được sự thừa mã lớn.

1) Hãy tưởng tượng bạn có 1024 lớp con đó để chọn trong thời gian chạy. Hãy thử phác thảo mã cần thiết. Hãy nhớ rằng bạn có thể không thể ra lệnh cho các lệnh được chọn hoặc bị từ chối. Cũng nên nhớ rằng bạn có thể phải sử dụng một cá thể trong một thời gian trước khi mở rộng nó. Hãy tiếp tục, thử. Làm nó với trang trí là tầm thường bằng cách so sánh.

+0

Cảm ơn bạn, Beta. – user1559625

+0

* 1023, trong trường hợp lệnh không quan trọng. – Caleb

3

Bạn đúng rằng đôi khi chúng có thể rất giống nhau. Khả năng ứng dụng và lợi ích của một trong hai giải pháp sẽ phụ thuộc vào tình hình của bạn.

Những người khác đã đánh bại tôi để trả lời đầy đủ cho câu hỏi thứ hai của bạn. Tóm lại, bạn có thể kết hợp các trang trí để đạt được nhiều kết hợp hơn mà bạn không thể làm với thừa kế.

Như vậy tôi tập trung vào ngày đầu tiên:

Bạn không thể nghiêm túc nói thời gian biên dịch là xấu và thời gian chạy là tốt, nó chỉ là sự linh hoạt khác nhau. Khả năng thay đổi mọi thứ trong thời gian chạy có thể quan trọng đối với một số dự án vì nó cho phép thay đổi mà không biên dịch lại có thể chậm và yêu cầu bạn ở trong môi trường nơi bạn có thể biên dịch.

Ví dụ nơi bạn không thể sử dụng thừa kế, là khi bạn muốn thêm chức năng vào đối tượng được tạo ngay.Giả sử bạn được cung cấp một thể hiện của một đối tượng mà thực hiện một giao diện đăng nhập:

public interface ILog{ 
    //Writes string to log 
    public void Write(string message); 
} 

Bây giờ giả sử bạn bắt đầu một nhiệm vụ phức tạp có liên quan đến nhiều đối tượng và mỗi người trong số họ không đăng nhập vì vậy bạn vượt qua cùng các đối tượng khai thác gỗ. Tuy nhiên, bạn muốn mọi thư từ nhiệm vụ được đặt trước bằng tên tác vụ và Id nhiệm vụ. Bạn có thể truyền xung quanh một hàm, hoặc truyền theo Name và Id và tin tưởng mọi người gọi thực hiện theo quy tắc đang chờ xử lý thông tin đó hoặc bạn có thể trang trí đối tượng ghi nhật ký trước khi vượt qua và không phải lo lắng về các đối tượng khác đang làm nó đúng

public class PrependLogDecorator : ILog{ 

    ILog decorated; 

    public PrependLogDecorator(ILog toDecorate, string messagePrefix){ 
     this.decorated = toDecorate; 
     this.prefix = messagePrefix; 
    } 

    public void Write(string message){ 
     decorated.Write(prefix + message); 
    } 
} 

Xin lỗi về mã C# nhưng tôi nghĩ rằng nó vẫn sẽ truyền đạt ý tưởng cho những người biết C++

+1

Cảm ơn vossad01. Nhận xét của bạn về 'thêm chức năng cho một đối tượng instantiated' thực sự là tốt! – user1559625

+0

+1 cho "Một ví dụ mà bạn không thể sử dụng thừa kế, là khi bạn muốn thêm chức năng cho một đối tượng instantiated." –

Các vấn đề liên quan