2013-04-16 30 views
9

Tôi đang viết một phần mềm chung sẽ được tải lên nhiều biến thể khác nhau của cùng một phần cứng cơ bản. Tất cả chúng đều có cùng một bộ xử lý, nhưng với các thiết bị ngoại vi khác nhau và các chức năng riêng của chúng cần phải được thực hiện. Phần mềm sẽ biết biến thể nào sẽ chạy bằng cách đọc giá trị chuyển đổi phần cứng.Tạo kiểu động trong C++

Dưới đây là thực hiện hiện tại của tôi trong một nutshell:

class MyBase 
{ 
public: 
    MyBase() { } 
    virtual run() = 0; 
} 


class VariantA : public MyBase 
{ 
public: 
    VariantA() { } 
    virtual run() 
    { 
     // Run code specific to hardware Variant-A 
    } 
} 


class VariantB : public MyBase 
{ 
public: 
    VariantB() { } 
    virtual run() 
    { 
     // Run code specific to hardware Variant-B 
    } 
} 


void main() 
{ 
    MyBase* variant; 
    uint_8 switchValue = readSwitchValue(); 

    switch(switchValue) 
    { 
    case 0: 
     variant = new VariantA(); 
     break; 

    case 1: 
     variant = new VariantB(); 
     break; 
    } 

    variant->run(); 
} 

Bây giờ này chỉ hoạt động tốt. Tôi đọc giá trị phần cứng và sử dụng câu lệnh switch để tạo lớp tương ứng mới.

Vấn đề là có rất nhiều biến thể mà tôi phải giải quyết. Hiện nay khoảng 15, với tiềm năng để thêm một 20-30 khác trong tương lai gần. Tôi đã thực sự đến với các câu lệnh chuyển đổi khinh thường chạy cho hàng trăm dòng, vì vậy tôi thực sự đang tìm kiếm một cách tốt hơn để làm điều này, có thể thông qua các mẫu.

Tôi muốn có thể sử dụng giá trị phần cứng của mình để tra cứu một loại và sử dụng loại đó để tạo đối tượng mới của tôi. Lý tưởng nhất là khi tôi thêm một biến thể mới, tôi tạo lớp mới, thêm loại lớp đó vào bảng tra cứu của tôi với giá trị phần cứng phù hợp và rất tốt để đi.

Điều này có thể thực hiện được không? Giải pháp tốt ở đây là gì?

+0

Cá nhân, Tôi nghĩ rằng một khối "chuyển đổi/trường hợp" để tạo ra các lớp thích hợp có lẽ là một giải pháp tối ưu. Chỉ cần đặt câu lệnh trường hợp của bạn trong phương thức "factory" tĩnh trả về tham chiếu đến lớp cụ thể. IMHO ... Đây là một ví dụ tốt: http://stackoverflow.com/questions/7468104/factory-method-design-pattern – paulsm4

+0

Phần cứng chỉ có thể biết được khi chạy? –

+1

có xem [answer] cụ thể này (http://stackoverflow.com/questions/15977617/polymorphism-with-new-data-members/15978673#15978673), giải thích cách để xây dựng một nhà máy đối tượng bằng cách đăng ký các nhà thầu . Có thể đáng xem xét ý tưởng ban đầu được đề cập trong bài đăng. – didierc

Trả lời

13

Như đã nêu, bạn tạo một nhà máy, nhưng không nhất thiết phải có câu lệnh chuyển đổi ngây thơ. Những gì bạn có thể làm là tạo một lớp mẫu để tạo đối tượng có liên quan và tự động thêm chúng vào nhà máy của bạn.

class VariantinatorBase { 
    public: 
    VariantinatorBase() {} 
    virtual ~VariantinatorBase() {} 
    virtual std::unique_ptr<Variant> Create() = 0; 
}; 

template< class T > 
class Variantinator : public VariantinatorBase { 
    public: 
    Variantinator() {} 
    virtual ~Variantinator() {} 
    virtual std::unique_ptr<Variant> Create() { return new T; } 
}; 

Bây giờ bạn có một nhà máy sản xuất lớp học cho phép bạn đăng ký chúng.

class VariantFactory 
{ 
    public: 
    VariantFactory() 
    { 
     // If you want, you can do all your Register() calls in here, and even 
     // make the Register() function private. 
    } 

    void Register(uint_8 type, std::unique_ptr<VariantinatorBase> creator) 
    { 
     m_switchToVariant[type] = creator; 
    } 

    std::unique_ptr<Variant> Create(uint_8 type) 
    { 
     TSwitchToVariant::iterator it = m_switchToVariant.find(type); 
     if(it == m_switchToVariant.end()) return nullptr; 
     return it->second->Create(); 
    } 

    private: 
    typedef std::map<uint_8, std::unique_ptr<VariantinatorBase> > TSwitchToVariant; 
    TSwitchToVariant m_switchToVariant; 
}; 

Vào đầu chương trình của bạn, tạo ra các nhà máy và đăng ký loại của bạn:

VariantFactory factory; 
factory.Register(0, new Variantinator<VariantA>); 
factory.Register(1, new Variantinator<VariantB>); 
factory.Register(2, new Variantinator<VariantC>); 

Rồi sau đó, bạn muốn gọi vào nó:

std::unique_ptr<Variant> thing = factory.Create(switchValue); 
+0

Kiểu dáng đẹp. Tránh sự cần thiết cho một hàm thành viên tĩnh mà chỉ đơn giản là 'return new ' trong mọi lớp và cho phép trình biên dịch xử lý các phần tẻ nhạt. +1 –

+0

Chúc mừng. Có thể cần một số tweeks để làm cho nó biên dịch - Tôi chỉ đóng búa này vào trình soạn thảo câu trả lời mà không cần suy nghĩ nhiều. Nhưng tôi đã sử dụng mẫu này trước đây và có thể chứng minh cho sức mạnh và sự đơn giản của nó. – paddy

+0

Tôi nên chỉ ra rằng các chức năng 'Register' và' Variantinator' có thể là riêng tư, và bạn có thể thực hiện tất cả việc đăng ký trong constructor của nhà máy. Bạn cũng có thể thêm một chút templating để làm cho một giải pháp nhà máy hoàn toàn chung chung. – paddy

3

Bạn đang tìm kiếm một nhà máy

http://www.oodesign.com/factory-pattern.html

Một nhà máy là một module phần mềm (một phương pháp, một lớp) với mục đích duy nhất là để tạo ra các đối tượng phù hợp với công việc. Ví dụ sử dụng lớp nhà máy:

class VariantFactory 
{ 
    MyBase* CreateObject(uint_8 value); 
} 

Và phương pháp CreateObject có thể được điền để cung cấp cho bạn loại đối tượng bạn cần.

Trong trường hợp lựa chọn rất nhỏ các đối tượng có cấu trúc đơn giản, một câu lệnh chuyển đổi đơn giản có thể đủ. Ngay sau khi bạn nhận được rất nhiều đối tượng hoặc những người yêu cầu xây dựng chi tiết hơn, một nhà máy là khá hữu ích.

+0

Có nhiều điều hơn thế nữa, vì bạn đang di chuyển lệnh chuyển từ 'main' sang' VariantFactory :: CreateObject' –

+0

Ben, đúng - đó là lý do tại sao tôi liên kết với chính mẫu đó và thêm một vài bình luận. – Silas

+0

Tôi không thấy làm thế nào điều này là tốt hơn bởi vì vẫn sẽ có một tuyên bố chuyển đổi lớn bên trong nhà máy. Tôi chỉ đi qua chủ đề này mà có một câu hỏi tương tự và câu trả lời thú vị. http: // stackoverflow.com/questions/11831284/dynamic-mapping-of-enum-value-int-to-type –

2

Tôi đã đưa ra nhận xét này; hãy biến nó thành một câu trả lời:

Cá nhân, tôi nghĩ khối "chuyển đổi/trường hợp" để tạo lớp thích hợp có lẽ là giải pháp tối ưu. Chỉ cần đặt câu lệnh trường hợp của bạn trong phương thức "factory" tĩnh trả về tham chiếu đến lớp cụ thể. IMHO ...

Dưới đây là một ví dụ điển hình: factory method design pattern

Class Book : public Product 
{ 
}; 

class Computer : public Product 
{ 
}; 

class ProductFactory 
{ 
public: 
    virtual Product* Make(int type) 
    { 
    switch (type) 
    { 
     case 0: 
     return new Book(); 
     case 1: 
     return new Computer(); 
     [...] 
    } 
    } 
} 

Call it like this: 

ProductFactory factory = ....; 
Product* p1 = factory.Make(0); // p1 is a Book* 
Product* p2 = factory.Make(1); // p2 is a Computer* 
// remember to delete p1 and p2 

Lưu ý rằng để đáp ứng xuất sắc nhất của ông, Smink cũng gợi ý một số giải pháp thay thế thiết kế khác nữa.

BOTTOM LINE: Không có gì vốn có "sai" với khối chuyển đổi/trường hợp. Ngay cả đối với một chuyển đổi với nhiều tùy chọn trường hợp.

IMHO ...

PS: Điều này thực sự không tạo ra một "loại động". Thay vào đó, đó là "tạo ra một loại tĩnh động". Điều đó cũng sẽ đúng nếu bạn sử dụng một mẫu hoặc một giải pháp enum. Nhưng một lần nữa - tôi rất thích "chuyển đổi/trường hợp".

+0

Không có gì sai với điều này (hoặc với 'switch') nhưng cá nhân, tôi thấy tẻ nhạt và dễ bị lỗi này. –

+0

Đồng ý. Một nơi nào đó mã phải quyết định loại đối tượng nào cần tạo và cách đơn giản nhất để làm điều đó là với một chuỗi if-else hoặc một câu lệnh switch. Chắc chắn, bạn có thể tạo một đăng ký ẩn quyết định trong mã tra cứu, nhưng cuối cùng là che khuất những gì đang xảy ra. –

1

Cập nhật: Tôi rời khỏi giải pháp ban đầu của tôi ở đây về hậu thế, nhưng xem xét giải pháp được cung cấp bởi lúa để vượt trội và ít bị lỗi hơn. Chỉ với một vài cải tiến nhỏ, tôi nghĩ rằng nó thực sự là về tốt như bạn có thể có thể nhận được.


xem xét thiết kế này:

class VariantA : public MyBase 
{ 
    static MyBase *CreateMachineInstance() { return new VariantA; } 
}; 

class VariantB : public MyBase 
{ 
    static MyBase *CreateMachineInstance() { return new VariantB; } 
}; 

Bây giờ, tất cả bạn cần là một std::map có sử dụng một uint_8 là chìa khóa và bản đồ nó vào một con trỏ hàm (trở về MyBase). Chèn các mã định danh vào bản đồ (chỉ mỗi định danh cho hàm tạo máy phù hợp) và sau đó đọc mã và chỉ sử dụng bản đồ để tìm máy bạn đang sử dụng.

Điều này dựa trên khái niệm/mô hình được gọi là "nhà máy" nhưng có thể bị phá vỡ một chút nếu các nhà thầu máy của bạn yêu cầu các đối số khác nhau hoặc bạn cần thực hiện khởi tạo/hoạt động trên mỗi máy - và từ những gì bạn đề cập bạn có thể.

Nếu trường hợp này xảy ra, bạn vẫn có thể sử dụng mẫu này nhưng bạn sẽ phải thực hiện một số điều chỉnh và kiến ​​trúc sư một chút nhưng bạn sẽ kết thúc với một số nhiều hơn sạch hơn và dễ dàng hơn để tăng thêm và duy trì.

-3
#include <stdio.h> 
#include <string.h> 
#include <iostream> 
using namespace std; 

template<class T,class T1> 
class HeroHonda 
{ 
private: 
    T millage; 
    T1 *options; 

public: 
    HeroHonda() { 
     puts("constructed"); 
     options=new T1[20]; 

     strcpy(options,"Good millage,Powerstart"); 
     millage=110; 
    } 

    virtual T features() { 
     cout<<options<<"millage is"<<millage<<endl; 
     return 1; 
    } 

    // virtual T Extrafeatures() = 0; 

    ~HeroHonda() { 
     cout<<"destructor"<<endl; 
     delete [] options;  
    } 
}; 

int main() 
{ 
    HeroHonda <int,char> *Ptr=new HeroHonda <int,char>; 
    Ptr->features(); 
    delete Ptr; 
} 
Các vấn đề liên quan