2015-06-02 42 views
6

Próbuję zrozumieć rzeczywisty wymóg stosowania szablonów do projektowania opartego na zasadach. Przechodząc przez nowe szablonowe szablony w C++, stwierdziłem, że projektowanie klas oparte na regułach jest wysoce sugerowanym sposobem projektowania, który umożliwia "podłączanie" różnych zachowań z klas polityk. Minimalny przykładem jest następujący (skrócona wersja wiki):Kiedy należy preferować projektowanie oparte na szablonach za pomocą projektowania opartego na dziedziczeniu niematerialnym

template <typename LanguagePolicy> 
class HelloWorld : private LanguagePolicy 
{ 
    using LanguagePolicy::message; 

public: 
    // Behaviour method 
    void run() const 
    { 
     // policy methods 
     cout << message(); 
    } 
}; 

class LanguagePolicyA 
{ 
protected: 
    std::string message() const 
    { 
     return "Hello, World!"; 
    } 
}; 
//usage 
HelloWorld<LanguagePolicyA> hello_worlda; 
hello_worlda.run(); // prints "Hello, World!" 

Szybka analiza pokazuje, że wystarczy, aby uzyskać różne metody wetknięcia message() jesteśmy dziedziczy od typu matrycy, której definicja może być zapewnione przez nikogo (i identyfikowane podczas kompilacji).

Ale ten sam poziom abstrakcji (i metod konfigurowalnych) można osiągnąć bez użycia kodu szablonu i prostego polimorfizmu czasu pracy, jak pokazano poniżej.

class HelloWorld 
{ 
    LanguagePolicy *lp; //list of all plugable class 
public: 
    HelloWorld(LanguagePolicy *lpn) { 
     lp = lpn; 
    } 

    // Behaviour method 
    void run() const 
    { 
     // policy methods 
     cout << lp->message(); 
    } 
}; 
class LanguagePolicy 
{ 
protected: 
    virtual std::string message() const; 
}; 

class LanguagePolicyA: LanguagePolicy 
{ 
protected: 
    std::string message() const 
    { 
     return "Hello, World!"; 
    } 
}; 
//usage 
HelloWorld helloworld(new LanguagePolicyA); 
helloworld.run(); 

Funkcjonalność i poziom abstrakcji mądry nie widzę wielkiej różnicy w dwa podejścia (choć drugie podejście ma kilka dodatkowych linii kodu dla LanguagePolicy, myślę, że jest to konieczne dla innych użytkowników znać interfejs, w przeciwnym razie zrozumienie LanguagePolicy zależy od dokumentacji). Ale myślę, że później będzie "czysty" (pochodzący od kogoś, kto nie używał zbyt wiele szablonów). Dzieje się tak, ponieważ osobiście moim zdaniem zajęcia niezwiązane z szablonem są czystsze, aby je przejrzeć i zrozumieć. Bardzo dobrym przykładem jest popularna biblioteka VTK (Visualization Tool Kit), która rozwiązuje wiele różnych problemów przy użyciu drugiego podejścia. Mimo że nie ma obszernych dokumentacji VTK, większość z nas - jej użytkownicy, może po prostu przejrzeć swoje diagramy klas (czasami są one dość duże) i wydedukować zachowania klas; i opracować wysoce konfigurowalne i skomplikowane potoki w naszej aplikacji (nie można obrazować VTK jako opartego na szablonach :)). Przeciwieństwem są biblioteki takie jak STL/BOOST, które moim zdaniem nie są w stanie zidentyfikować pracy klas bez użycia obszernej dokumentacji.

Moje pytanie brzmi: czy projekt oparty na szablonach jest naprawdę lepszy (tylko w tym scenariuszu opartym na zasadach) niż oparty na dziedziczeniu wirtualnym? Jeśli tak, kiedy i dlaczego?

Odpowiedz

1

Oba są prawidłowymi sposobami strukturyzacji, to w rzeczywistości zależy od wymagań. Na przykład.

Runtime vs polimorfizm czasu kompilacji.

Kiedy chcesz/może/chcesz osiągnąć polimorfizm?

Wydajność napowietrznych połączeń wirtualnych

Szablony generowania kodu, który nie ma indirections

Faktyczne wykorzystanie tej klasy.

Gdy musisz przechowywać kolekcje heterogeniczne, wymagana jest klasa podstawowa, więc musisz użyć dziedziczenia.

Bardzo dobra książka na temat projektowania oparte na regułach (nieco stary, ale mimo to dobrze) jest Modern C++ Design

+0

-> Szablon "symuluje" polimorfizm czasu pracy, generując klasy w czasie kompilacji. Dlaczego więc nie używać bezpośrednio środowiska uruchomieniowego? Dlaczego nas to obchodzi? -> Przyjrzę się, ale ile naprawdę kosztuje obciążenie wirtualne? Czy są wystarczająco znaczące, aby wywołać efekt? – krips89

+0

@ krips89 Drugi punkt. Występuje narzut wydajności związany z połączeniami wirtualnymi, co może być ważne w niektórych przypadkach. –

+0

@ krips89 To zależy, ale jeśli weźmiesz pod uwagę obojętność, bałagan z liniami pamięci podręcznej, możliwość wstawienia funkcji szablonowej, może zaistnieć korzyść z używania zasad. –

1

zależy od sytuacji chyba ...Możliwą wadą jest to, że za pomocą szablonów typ powinien być znany w czasie kompilacji:

HelloWorld<English> hw; // English is plugged at compile-time 

W drugim przykładzie, gdzie używasz bazy wskaźnik do tego wskaźnik może wskazywać na różnorodność klasy pochodne. Co dokładnie wskazuje na to, nie musi być znane podczas kompilacji i dlatego może być określone przez (user-) wejście w czasie wykonywania. Możliwą wadą tego podejścia jest obciążenie związane z połączeniem wirtualnym. W niektórych aplikacjach i na niektórych platformach może to być niepożądane.

+0

Tak; tak długo, jak rozumiem do tej pory, jedynym problemem, który pojawia się w drugim podejściu, jest "obciążenie związane z połączeniem wirtualnym". Jeśli spojrzę czysto z perspektywy projektu (bez dbania o koszty ogólne wdrożenia), drugie podejście wydaje się lepsze. – krips89

+0

To jedyny minus, jaki mogę teraz zauważyć. Ponadto abstrakcyjne bazy pomagają wymusić interfejs na klasach pochodnych, co jest miłe. Statyczne sprawdzanie interfejsu będzie prawdopodobnie dostępne w języku C++ 17, gdy koncepcje zostaną dodane do języka (awesome). – JorenHeit