2008-10-26 30 views
14

Tại nơi làm việc của tôi, chúng ta có xu hướng sử dụng iostream, chuỗi, vector, đồ và lẻ thuật toán hoặc hai. Chúng tôi đã không thực sự tìm thấy nhiều tình huống mà kỹ thuật mẫu là một giải pháp tốt nhất cho một vấn đề.Bạn tìm thấy các mẫu hữu ích ở đâu?

Điều tôi đang tìm kiếm ở đây là ý tưởng và tùy chọn mã mẫu cho biết cách bạn sử dụng kỹ thuật mẫu để tạo giải pháp mới cho sự cố bạn gặp phải trong cuộc sống thực.

Để hối lộ, hãy chờ một phiếu bầu cho câu trả lời của bạn.

Trả lời

7

Tôi đã sử dụng rất nhiều mẫu mã, chủ yếu ở Boost và STL, nhưng tôi đã hiếm khi có một nhu cầu để ghi bất kỳ.

Một trong các trường hợp ngoại lệ, cách đây vài năm, nằm trong chương trình điều khiển các tệp EXE Windows PE định dạng. Công ty muốn thêm hỗ trợ 64 bit, nhưng lớp ExeFile mà tôi đã viết để xử lý các tệp chỉ hoạt động với các tệp 32 bit. Mã cần thiết để thao tác phiên bản 64 bit về cơ bản giống hệt nhau, nhưng nó cần sử dụng một loại địa chỉ khác (64 bit thay vì 32 bit), điều này khiến cho hai cấu trúc dữ liệu khác nhau.

Dựa trên việc sử dụng một mẫu duy nhất của STL để hỗ trợ cả std::stringstd::wstring, tôi quyết định thử làm ExeFile mẫu với cấu trúc dữ liệu khác nhau và loại địa chỉ làm thông số.Có hai nơi mà tôi vẫn phải sử dụng #ifdef WIN64 dòng (yêu cầu xử lý hơi khác nhau), nhưng nó không thực sự khó khăn để làm. Chúng tôi đã hỗ trợ đầy đủ 32 và 64 bit trong chương trình đó ngay bây giờ và việc sử dụng mẫu có nghĩa là mọi sửa đổi chúng tôi đã thực hiện vì tự động áp dụng cho cả hai phiên bản.

+0

Ya. ví dụ tuyệt vời. Cảm ơn. – EvilTeach

10

thông tin chung về các mẫu:

Templates có ích bất cứ lúc nào bạn cần phải sử dụng mã tương tự nhưng hoạt động trên các kiểu dữ liệu khác nhau, nơi mà các loại được biết tại thời gian biên dịch. Và cũng có khi bạn có bất kỳ loại vật chứa nào.

Cách sử dụng rất phổ biến chỉ dành cho mọi loại cấu trúc dữ liệu. Ví dụ: Danh sách được liên kết đơn lẻ, danh sách được liên kết kép, cây, lần thử, hashtables, ...

Một cách sử dụng rất phổ biến khác là phân loại thuật toán.

Một trong những ưu điểm chính của việc sử dụng mẫu là bạn có thể xóa trùng lặp mã. Sao chép mã là một trong những điều lớn nhất bạn nên tránh khi lập trình.

Bạn có thể triển khai hàm Max làm cả macro hoặc mẫu, nhưng triển khai mẫu sẽ an toàn và do đó tốt hơn.

Và bây giờ vào những thứ mát:

Xem thêm template metaprogramming, đó là một cách để đánh giá trước mã tại thời gian biên dịch chứ không phải tại thời gian chạy. Lập trình meta mẫu chỉ có các biến bất biến, và do đó các biến của nó không thể thay đổi. Vì lập trình meta mẫu này có thể được xem như là một loại lập trình chức năng.

Xem ví dụ về lập trình meta mẫu từ Wikipedia. Nó cho thấy làm thế nào các mẫu có thể được sử dụng để thực thi mã tại thời gian biên dịch. Do đó trong thời gian chạy bạn có một hằng số được tính toán trước.

template <int N> 
struct Factorial 
{ 
    enum { value = N * Factorial<N - 1>::value }; 
}; 

template <> 
struct Factorial<0> 
{ 
    enum { value = 1 }; 
}; 

// Factorial<4>::value == 24 
// Factorial<0>::value == 1 
void foo() 
{ 
    int x = Factorial<4>::value; // == 24 
    int y = Factorial<0>::value; // == 1 
} 
+1

Vì vậy, bạn đã sử dụng mẫu cho giai thừa trong đời thực? – Roddy

+0

Không phải trong cuộc sống thực :) Nhưng ý tưởng có thể được mở rộng thành nhiều lĩnh vực của cuộc sống thực. –

+0

Vì vậy, bạn có một ví dụ mà bạn đã PERSONALLY sử dụng? – Roddy

2

Mẫu Tôi tiêu thụ theo quy định là vô số các lớp chứa, tăng các con trỏ thông minh, scopeguards, một vài thuật toán STL. mẫu

kịch bản trong đó tôi đã viết:

  • container tùy chỉnh
  • quản lý bộ nhớ, thực hiện an toàn loại và ctor/DTor gọi trên đầu trang của allocators * trống
  • thực hiện chung cho quá tải wiht khác nhau các loại, ví dụ

    bool ContainsNan (float *, int) bool ContainsNan (double *, int)

mà cả hai chỉ cần gọi một (địa phương, ẩn) chức năng helper

template <typename T> 
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ; 

thuật toán cụ thể mà độc lập với loại, miễn là loại có các thuộc tính nhất định, ví dụ tuần tự nhị phân.

template <typename T> 
void BinStream::Serialize(T & value) { ... } 

// to make a type serializable, you need to implement 
void SerializeElement(BinStream & strean, Foo & element); 
void DeserializeElement(BinStream & stream, Foo & element) 

Không giống như chức năng ảo, mẫu cho phép tối ưu hóa khác diễn ra.


Nói chung, các mẫu cho phép thực hiện một khái niệm hoặc thuật toán cho nhiều loại và có sự khác biệt đã được giải quyết tại thời điểm biên dịch.

1

Tôi sử dụng mẫu để chỉ định các loại đối tượng chức năng. Tôi thường viết mã lấy một đối tượng hàm làm đối số - một hàm để tích hợp, một hàm để tối ưu hóa, v.v. - và tôi thấy các mẫu thuận tiện hơn là thừa kế. Vì vậy, mã của tôi nhận được một đối tượng hàm - chẳng hạn như một trình tích hợp hoặc trình tối ưu hóa - có một tham số mẫu để xác định loại đối tượng hàm mà nó hoạt động trên đó.

7

Một nơi mà tôi sử dụng mẫu để tạo mã của riêng mình là triển khai các lớp chính sách như được mô tả bởi Andrei Alexandrescu trong thiết kế C++ hiện đại. Hiện tại tôi đang làm việc trên một dự án bao gồm một tập hợp các lớp tương tác với BEA \ h \ h \ h màn hình Tuxedo TP của Oracle.

Một cơ sở đó Tuxedo cung cấp là hàng đợi persistant giao dịch, vì vậy tôi có một TpQueue lớp tương tác với hàng đợi:

class TpQueue { 
public: 
    void enqueue(...) 
    void dequeue(...) 
    ... 
} 

Tuy nhiên như hàng đợi là giao dịch tôi cần phải quyết định những gì hành vi giao dịch Tôi muốn; điều này có thể được thực hiện một cách riêng biệt bên ngoài lớp TpQueue nhưng tôi nghĩ rằng nó rõ ràng hơn và ít lỗi hơn nếu mỗi trường hợp TpQueue có chính sách riêng của mình về giao dịch.Vì vậy, tôi có một tập các lớp TransactionPolicy như:

class OwnTransaction { 
public: 
    begin(...) // Suspend any open transaction and start a new one 
    commit(..) // Commit my transaction and resume any suspended one 
    abort(...) 
} 

class SharedTransaction { 
public: 
    begin(...) // Join the currently active transaction or start a new one if there isn't one 
    ... 
} 

Và lớp TpQueue được viết lại như

template <typename TXNPOLICY = SharedTransaction> 
class TpQueue : public TXNPOLICY { 
    ... 
} 

Vì vậy, bên TpQueue tôi có thể gọi bắt đầu(), hủy bỏ(), cam () khi cần thiết nhưng có thể thay đổi hành vi dựa trên đường tôi tuyên bố dụ:

TpQueue<SharedTransaction> queue1 ; 
TpQueue<OwnTransaction> queue2 ; 
2

Chúng tôi sử dụng COM và chấp nhận một con trỏ đến một đối tượng mà có thể thực hiện giao diện khác trực tiếp hoặc qua [IServiceProvider] (http://msdn.microsoft.com/en-us/library/cc678965(VS.85).aspx) điều này đã nhắc tôi tạo chức năng giống như trình trợ giúp này.

// Get interface either via QueryInterface of via QueryService 
template <class IFace> 
CComPtr<IFace> GetIFace(IUnknown* unk) 
{ 
    CComQIPtr<IFace> ret = unk; // Try QueryInterface 
    if (ret == NULL) { // Fallback to QueryService 
     if(CComQIPtr<IServiceProvider> ser = unk) 
      ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret); 
    } 
    return ret; 
} 
1

Lý do rõ ràng (như ngăn chặn mã trùng lặp bằng cách hoạt động trên các loại dữ liệu khác nhau) sang một bên, có một mẫu thiết kế dựa trên chính sách này. Tôi đã đặt câu hỏi về policies vs strategies.

Bây giờ, điều gì tiện lợi về tính năng này. Hãy xem xét bạn đang viết một giao diện cho người khác sử dụng. Bạn biết rằng giao diện của bạn sẽ được sử dụng, bởi vì nó là một mô-đun trong miền riêng của nó. Nhưng bạn không biết làm thế nào mọi người sẽ sử dụng nó. Thiết kế dựa trên chính sách tăng cường mã của bạn để tái sử dụng trong tương lai; nó làm cho bạn độc lập với các kiểu dữ liệu mà một triển khai cụ thể dựa vào. Mã chỉ là "slurped in". :-)

Đặc điểm là một ý tưởng tuyệt vời. Họ có thể đính kèm hành vi, dữ liệu và typedata cụ thể vào một mô hình. Các đặc điểm cho phép tham số hóa đầy đủ của tất cả ba trường này. Và tốt nhất của nó, đó là một cách rất tốt để làm cho mã tái sử dụng.

4

Tôi đã sử dụng các mẫu (với sự trợ giúp của Boost.Fusion) để đạt được các số nguyên kiểu an toàn cho thư viện siêu đồ họa mà tôi đang phát triển. Tôi có một ID cạnh (hyper) và một ID đỉnh là cả hai số nguyên. Với các mẫu, đỉnh và các ID siêu cạnh tranh đã trở thành các loại khác nhau và sử dụng một khi người khác được mong đợi tạo ra một lỗi biên dịch thời gian. Đã lưu cho tôi rất nhiều đau đầu mà tôi có thể có với thời gian chạy gỡ lỗi.

+0

Thú vị. Tôi đã làm một cái gì đó tương tự bằng cách sử dụng thừa kế của một lớp cơ sở với một thành viên duy nhất được bảo vệ 'int', nhưng một lớp mẫu đã có thể lưu cho tôi một chút trùng lặp mã nếu tôi nghĩ về nó! – Roddy

3

Đây là một ví dụ từ một dự án thực sự. Tôi có các chức năng getter như sau:

bool getValue(wxString key, wxString& value); 
bool getValue(wxString key, int& value); 
bool getValue(wxString key, double& value); 
bool getValue(wxString key, bool& value); 
bool getValue(wxString key, StorageGranularity& value); 
bool getValue(wxString key, std::vector<wxString>& value); 

Và sau đó là một biến thể với giá trị 'mặc định'. Nó trả về giá trị cho khóa nếu nó tồn tại, hoặc giá trị mặc định nếu nó không. Mẫu đã lưu tôi không phải tự tạo 6 hàm mới.

template <typename T> 
T get(wxString key, const T& defaultValue) 
{ 
    T temp; 
    if (getValue(key, temp)) 
     return temp; 
    else 
     return defaultValue; 
} 
+0

quá đúng. tôi đã làm điều tương tự cho wxConfig :) –

1

một lần tôi nhìn thấy đoạn mã sau:

void doSomethingGeneric1(SomeClass * c, SomeClass & d) 
{ 
    // three lines of code 
    callFunctionGeneric1(c) ; 
    // three lines of code 
} 

lặp đi lặp lại mười lần:

void doSomethingGeneric2(SomeClass * c, SomeClass & d) 
void doSomethingGeneric3(SomeClass * c, SomeClass & d) 
void doSomethingGeneric4(SomeClass * c, SomeClass & d) 
// Etc 

Mỗi chức năng có cùng 6 dòng mã sao chép/dán, và mỗi lần gọi khác function callFunctionGenericX với cùng một hậu tố số.

Không có cách nào để cấu trúc lại toàn bộ mọi thứ. Vì vậy, tôi giữ refactoring địa phương.

tôi đã thay đổi mã theo cách này (từ bộ nhớ):

template<typename T> 
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t) 
{ 
    // three lines of code 
    t(c) ; 
    // three lines of code 
} 

Và sửa đổi mã hiện với:

void doSomethingGeneric1(SomeClass * c, SomeClass & d) 
{ 
    doSomethingGenericAnything(c, d, callFunctionGeneric1) ; 
} 

void doSomethingGeneric2(SomeClass * c, SomeClass & d) 
{ 
    doSomethingGenericAnything(c, d, callFunctionGeneric2) ; 
} 

vv

này hơi highjacking mẫu điều, nhưng cuối cùng, tôi đoán nó tốt hơn so với chơi với con trỏ hàm typedefed hoặc sử dụng macro.

1

Đã được đề cập rằng bạn có thể sử dụng mẫu làm lớp chính sách để làm điều gì đó. Tôi sử dụng rất nhiều.

Tôi cũng sử dụng chúng, với sự trợ giúp của bản đồ bất động sản (see boost site for more information on this), để truy cập dữ liệu theo cách tổng quát. Điều này tạo cơ hội để thay đổi cách bạn lưu trữ dữ liệu, mà không bao giờ phải thay đổi cách bạn truy xuất dữ liệu đó.

1

Cá nhân tôi đã sử dụng Mẫu mẫu tò mò định kỳ làm phương tiện thực thi một số hình thức thiết kế từ trên xuống và triển khai từ dưới lên. Một ví dụ sẽ là một đặc tả cho một trình xử lý chung, trong đó các yêu cầu nhất định trên cả biểu mẫu và giao diện được thực thi trên các kiểu có nguồn gốc tại thời gian biên dịch. Có vẻ như sau:

template <class Derived> 
struct handler_base : Derived { 
    void pre_call() { 
    // do any universal pre_call handling here 
    static_cast<Derived *>(this)->pre_call(); 
    }; 

    void post_call(typename Derived::result_type & result) { 
    static_cast<Derived *>(this)->post_call(result); 
    // do any universal post_call handling here 
    }; 

    typename Derived::result_type 
    operator() (typename Derived::arg_pack const & args) { 
    pre_call(); 
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args); 
    post_call(temp); 
    return temp; 
    }; 

};

Something như thế này có thể được sử dụng sau đó để đảm bảo xử lý của bạn xuất phát từ mẫu này và thực thi thiết kế từ trên xuống và sau đó cho phép từ dưới lên tùy biến:

struct my_handler : handler_base<my_handler> { 
    typedef int result_type; // required to compile 
    typedef tuple<int, int> arg_pack; // required to compile 
    void pre_call(); // required to compile 
    void post_call(int &); // required to compile 
    int eval(arg_pack const &); // required to compile 
}; 

này sau đó cho phép bạn có đa hình chung các hàm chỉ xử lý handler_base <> các loại có nguồn gốc:

template <class T, class Arg0, class Arg1> 
typename T::result_type 
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) { 
    return handler(make_tuple(arg0, arg1)); 
}; 
Các vấn đề liên quan