2010-07-07 31 views
5

Tôi đang làm việc trên thư viện xác định giao diện ứng dụng cho một số dịch vụ. Dưới mui xe tôi phải xác nhận hợp lệ dữ liệu được cung cấp bởi người dùng và sau đó chuyển nó tới tiến trình "engine" bằng cách sử dụng lớp Connection từ một thư viện khác (lưu ý: lớp Connection không được biết đến với người dùng thư viện của chúng ta). Một trong những đồng nghiệp của tôi đề xuất sử dụng pImpl:Đây có phải là nơi tốt để sử dụng mẫu PIMPL không?

class Client { 
public: 
    Client(); 
    void sendStuff(const Stuff &stuff) {_pimpl->sendStuff(stuff);} 
    Stuff getStuff(const StuffId &id) {return _pimpl->getStuff(id);} 
private: 
    ClientImpl *_pimpl; 
} 

class ClientImpl { // not exported 
public: 
    void sendStuff(const Stuff &stuff); 
    Stuff getStuff(const StuffId &id); 
private: 
    Connection _connection; 
} 

Tuy nhiên, tôi thấy nó rất khó để kiểm tra - thậm chí nếu tôi liên kết các bài kiểm tra của tôi để thực hiện một số chế giễu của kết nối, tôi không có quyền truy cập dễ dàng để nó để thiết lập và xác nhận kỳ vọng. Tôi có thiếu điều gì đó, hoặc giải pháp sạch hơn và có thể kiểm tra hơn là sử dụng giao diện + nhà máy:

class ClientInterface { 
public: 
    void sendStuff(const Stuff &stuff) = 0; 
    Stuff getStuff(const StuffId &id) = 0; 
} 

class ClientImplementation : public ClientInterface { // not exported 
public: 
    ClientImplementation(Connection *connection); 
    // +implementation of ClientInterface 
} 

class ClientFactory { 
    static ClientInterface *create(); 
} 

Có bất kỳ lý do gì để đi với PIMPL trong tình huống này không?

+0

Tôi có thể sai, nhưng nếu bạn có một thành viên kiểu 'Kết nối' (và không phải' Kết nối * '), bạn phải bao gồm định nghĩa của nó trong tiêu đề của bạn và do đó' Kết nối' được biết đến với người dùng thư viện của bạn. – ereOn

+0

Xem http: // stackoverflow.com/questions/825018/pimpl-idiom-vs-pure-virtual-class-interface –

+0

@ereOn: trong tiêu đề Client tôi chỉ sử dụng khai báo phía trước của lớp ClientImpl (có thể, vì thành viên là một con trỏ) vì vậy tiêu đề ClientImpl có thể ẩn khỏi các ứng dụng thư viện của tôi, vì vậy tôi có thể sử dụng Connection làm thành viên của ClientImpl. – chalup

Trả lời

4

AFAIK lý do thông thường để sử dụng thành ngữ Pimpl là giảm phụ thuộc thời gian biên dịch/liên kết để triển khai lớp (bằng cách xóa toàn bộ chi tiết triển khai khỏi tệp tiêu đề công khai). Một lý do khác có thể là để cho phép lớp thay đổi hành vi của nó một cách năng động (hay còn gọi là State pattern).

Trường hợp thứ hai dường như không xảy ra ở đây và lần đầu tiên có thể đạt được với kế thừa + nhà máy. Tuy nhiên, như bạn đã lưu ý, giải pháp thứ hai là dễ dàng hơn nhiều để kiểm tra đơn vị, vì vậy tôi sẽ thích điều này.

+0

Một lý do khác để sử dụng thành ngữ Pimpl là tách các chi tiết triển khai khỏi giao diện công khai của một lớp: chỉ các thành viên công khai mới hiển thị trong tiêu đề lớp. –

+1

Lý do duy nhất để sử dụng thành ngữ Pimpl là khi bạn không thể sử dụng lớp cơ sở trừu tượng. Ví dụ khi bạn triển khai lớp có nghĩa là được khởi tạo như là giá trị trên stack hoặc là một thành viên của lớp khác. Trong tình huống đó bạn vẫn có thể có 'tường lửa biên dịch' nhờ pimpl. –

+0

Nếu tôi chỉ xuất giao diện trừu tượng + nhà máy, điều đó cũng không làm giảm phụ thuộc thời gian biên dịch/liên kết? – chalup

0

Có điều này là một nơi tốt để sử dụng mẫu Pimpl, và có nó sẽ rất khó để kiểm tra như là.

Vấn đề là hai khái niệm phản đối nhau:

  • Pimpl là về ẩn phụ thuộc từ khách hàng: điều này làm giảm biên dịch/thời gian liên kết và là tốt hơn từ một điểm ổn định ABI xem.
  • Đơn vị kiểm tra thường là về sự can thiệp của phẫu thuật trong sự phụ thuộc (sử dụng up giả, ví dụ)

Tuy nhiên, nó không có nghĩa là bạn nên hy sinh một cho khác. Nó chỉ có nghĩa là bạn nên thích ứng với mã của bạn.

Bây giờ, điều gì xảy ra nếu Connection được triển khai với cùng một thành ngữ?

class Connection 
{ 
private: 
    ConnectionImpl* mImpl; 
}; 

Và cung cấp thông qua một máy:

// Production code: 

Client client = factory.GetClient(); 

// Test code: 
MyTestConnectionImpl impl; 
Client client = factory.GetClient(impl); 

Bằng cách này, bạn có thể truy cập vào các chi tiết để kiểm tra của bạn thực hiện kết nối trong khi thử nghiệm của khách hàng mà không lộ việc thực hiện cho khách hàng hoặc phá vỡ ABI .

+0

Vấn đề là, những người trong chuỗi thức ăn không muốn bất kỳ loại phương pháp/lớp nhà máy nào cũng như bất kỳ nhà thầu nào có phụ thuộc - chỉ nên có Client :: Client() constructor ... – chalup

+0

Trong trường hợp này bạn có thể sử dụng nhà máy nội bộ (làm cho nó singleton) để tạo ra 'ClientImpl' đúng. Bằng cách này giao diện không thay đổi, nhưng các nhà xây dựng khách hàng không. –

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