2013-04-16 14 views
9

Piszę część ogólnego oprogramowania, które zostanie załadowane na wiele różnych wariantów tego samego podstawowego sprzętu. Wszystkie mają ten sam procesor, ale z różnymi urządzeniami peryferyjnymi i ich własnymi funkcjami, które trzeba wykonać. Oprogramowanie będzie wiedzieć, który wariant powinien działać, czytając wartość przełącznika sprzętowego.Tworzenie dynamicznego typu w C++

Oto moja aktualna implementacja w pigułce:

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(); 
} 

Teraz działa to dobrze. Odczytuję wartość sprzętową i używam instrukcji switch do utworzenia nowej odpowiedniej klasy.

Problem polega na tym, że mam do wyboru wiele wariantów. Obecnie około 15, z potencjałem, aby dodać kolejne 20-30 w najbliższej przyszłości. Naprawdę nienawidzę instrukcji switch, które działają setki wierszy, więc naprawdę szukam lepszego sposobu na zrobienie tego, prawdopodobnie poprzez szablony.

Chcę móc używać mojej wartości sprzętowej do wyszukiwania typu i używać tego typu do tworzenia nowego obiektu. Idealnie, gdy dodaję nowy wariant, tworzę nową klasę, dodam ten typ klasy do mojej tablicy odnośników z jej dopasowaną wartością sprzętową i dobrze jest iść.

Czy to w ogóle możliwe? Jakie jest dobre rozwiązanie?

+0

Osobiście Myślę, że blok "switch/case" do stworzenia odpowiedniej klasy jest prawdopodobnie optymalnym rozwiązaniem. Wystarczy umieścić instrukcję case w statycznej "fabrycznej" metodzie, która zwraca odniesienie do konkretnej klasy. IMHO ... Oto dobry przykład: http://stackoverflow.com/questions/7468104/factory-method-design-pattern – paulsm4

+0

Czy sprzęt można poznawać tylko w środowisku wykonawczym? –

+1

spójrz na tę konkretną [odpowiedź] (http://stackoverflow.com/questions/15977617/polymorphism-with-new-data-members/15978673#15978673), która opisuje sposób budowy fabryki obiektów poprzez rejestrację konstruktorów . Warto spojrzeć na oryginalny pomysł wspomniany w poście. – didierc

Odpowiedz

13

Jak wspomniano, tworzysz fabrykę, ale niekoniecznie z instrukcjami naiwnych przełączników. Możesz zrobić klasę szablonu, aby utworzyć odpowiedni obiekt i dynamicznie dodać go do swojej fabryki.

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; } 
}; 

Teraz masz fabrykę klas, która pozwala je zarejestrować.

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; 
}; 

Na początku programu, należy utworzyć fabrykę i zarejestrować swoje typy:

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

Później, chcesz wezwać go:

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

Elegancki. Pozwala uniknąć statycznej funkcji składowej, która po prostu "zwraca nową " w każdej klasie i pozwala kompilatorowi radzić sobie z nużącymi częściami. +1 –

+0

Pozdrawiam. Może potrzebować kilku tweeksów, żeby go skompilować - po prostu wbiłem to do edytora odpowiedzi bez większego zastanowienia. Ale użyłem tego wzoru wcześniej i mogę ręczyć za jego moc i prostotę. – paddy

+0

Należy zwrócić uwagę, że funkcja 'Register' i' Variantinator' mogą być prywatne, a rejestracja w fabrycznym konstruktorze jest możliwa. Możesz również dodać trochę więcej szablonów, aby uczynić to całkowicie ogólnym rozwiązaniem fabrycznym. – paddy

3

Szukasz fabryce

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

Fabryka jest moduł oprogramowania (metoda, klasa), którego jedynym celem jest stworzenie odpowiedniego obiektu dla danego zadania. Przykład z klasą fabryczną:

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

Można również wypełnić metodę CreateObject, aby uzyskać żądany typ obiektu.

W przypadku bardzo małego wyboru obiektów o prostej konstrukcji wystarczy zwykła instrukcja zmiany. Jak tylko pojawi się wiele obiektów lub te, które wymagają bardziej szczegółowej konstrukcji, fabryka jest bardzo przydatna.

+0

Jest w tym coś więcej, ponieważ prawie przenosisz instrukcję switcha z 'main' na' VariantFactory :: CreateObject' –

+0

Ben, to prawda - dlatego też podłączyłem się do samego wzorca i dodałem kilka innych komentarze. – Silas

+0

Nie widzę, jak to jest lepsze, ponieważ w fabryce nadal będzie ogromna instrukcja przełączania. Właśnie natknąłem się na ten wątek, który ma podobne pytanie i ciekawe odpowiedzi. http: // stackoverflow.com/questions/11831284/dynamic-mapping-of-enum-value-int-to-type –

2

Zrobiłem to komentarz; zmieńmy to w odpowiedź:

Osobiście uważam, że blok "switch/case" do stworzenia odpowiedniej klasy jest prawdopodobnie optymalnym rozwiązaniem. Wystarczy umieścić instrukcję case w statycznej "fabrycznej" metodzie, która zwraca odniesienie do konkretnej klasy. IMHO ...

Oto dobry przykład: 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 

Należy zauważyć, że w jego najbardziej doskonałe odpowiedzi smink sugeruje również kilka innych alternatywnych rozwiązań, zbyt.

LINIA DOLNA: Nie ma nic z natury "nie tak" z przełącznikiem/blokadą obudowy. Nawet dla przełącznika z wieloma opcjami obudowy.

IMHO ...

PS: To naprawdę nie jest stworzenie "dynamiczny typ". Raczej "dynamiczne tworzenie statycznego typu". Byłoby to równie prawdziwe, gdybyś użył szablonu lub rozwiązania enum. Ale znowu - ja zdecydowanie wolę "przełącznik/case".

+0

Nie ma w tym nic złego (lub z 'switch'), ale osobiście uważam to za nużące i podatne na błędy. –

+0

Uzgodnione. Gdzieś kod musi zdecydować, jaki obiekt utworzyć, a najprostszy sposób to zrobić za pomocą łańcucha if-else lub instrukcji switch. Oczywiście, możesz utworzyć rejestr, który ukrywa decyzję w kodzie odnośnika, ale w ostatecznym rozrachunku zaciemnia to, co się dzieje. –

1

Aktualizacja: Wyjeżdżam moje oryginalne rozwiązanie tutaj dla potomności, ale rozważyć rozwiązanie dostarczone przez Paddy być lepszy i mniej podatne na błędy. Tylko kilka drobnych ulepszeń wydaje mi się, że jest tak dobra, jak tylko można.


Rozważmy następujący wzór:

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

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

Teraz, wszystko czego potrzebujesz to std::map który wykorzystuje uint_8 jako klucz i mapuje je do wskaźnika funkcji (powrót MyBase). Wprowadź identyfikatory na mapie (wskazując każdemu odpowiednią funkcję tworzenia maszyny), a następnie odczytaj kod i po prostu skorzystaj z mapy, aby znaleźć urządzenie, z którego korzystasz.

Jest to luźno oparte na koncepcji/wzorze zwanym "fabryką", ale może się lekko zepsuć, jeśli konstruktory maszyn wymagają różnych argumentów lub musisz przeprowadzić dodatkową inicjalizację/operacje na maszynie - iz tego, co wymieniłeś, brzmi jak możesz.

Jeśli tak jest, nadal możesz używać tego wzorca, ale będziesz musiał dokonać pewnych poprawek i trochę zmienić trochę, ale skończysz z czymś, co będzie łatwiejsze i łatwiejsze do poprawienia.

-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; 
} 
Powiązane problemy