[C++] Dziedziczenie w grach, a dynamic_cast
Ostatnio zmodyfikowano 2012-03-25 15:43
DejaVu Temat założony przez niniejszego użytkownika |
[C++] Dziedziczenie w grach, a dynamic_cast » 2012-03-25 12:58:24 Rzutowanie za pomocą dynamic_cast nie zawsze może działać, więc można ten problem ominąć następująco:
class IKlasa { public: enum TypE { E_GRACZ, E_BOT } virtual getType() = 0; };
class CGracz : public IKlasa { public: virtual getType() { return E_GRACZ; } static CGracz * cast( IKlasa * pKlasa ) { if( !pKlasa ) return NULL; if( pKlasa->getType() != E_GRACZ ) return null; return reinterpret_cast < CGracz *>( pKlasa ); } };
Korzystanie z klasy CGracz będzie wyglądało tak:
IKlasa * pKlasa = new CGracz();
CGracz * pGracz = CGracz::cast( pKlasa ); if( pGracz ) { }
/edit:
Później można wykonać coś takiego:
void executeGracz( CGracz * pGracz ) { }
BOOST_FOREACH( IKlasa * pItem, vKontenerElementow ) { if( !pItem ) continue; switch( pItem->getType() ) { case IKlasa::E_GRACZ: executeGracz( CGracz::cast( pItem ) ); break; } }
|
|
npHard |
» 2012-03-25 14:28:02 Warto dodać, że często korzystanie z dynamic_cast lub jakiegoś obejścia jak wyżej oznacza, że mamy design smell i powinniśmy się zastanowić, czy nie da się tego lepiej zamodelować. W tym przypadku dodanie jakiejkolwiek klasy do hierarchii wymaga:
1. edycji IKlasa
2. edycji wszystkich miejsc w których sprawdzany jest typ obiektu]
W tym przypadku lepiej zrobić tak:
class IKlasa { public: virtual void Execute() = 0; };
class CGracz : public IKlasa { public: void Execute() { } }; BOOST_FOREACH( IKlasa * pItem, vKontenerElementow ) { if( pItem ) { pItem->Execute(); } }
A jeśli mamy ustabilizowaną hierarchię klas a chcemy łatwo dodawać do niej nowe zachowania to moim zdaniem lepiej skorzystać ze wzorca projektowego Visitor( o ile nie pomyliłem nazwy). |
|
pekfos |
» 2012-03-25 15:11:23 1. edycji IKlasa 2. edycji wszystkich miejsc w których sprawdzany jest typ obiektu] |
1. Tylko typu wyliczeniowego. We wzorcu Visitor musiałbyś dodać nowa metodę dla każdego nowego typu. 2. Czyli np wadą zmiany deklaracji funkcji jest konieczność zmienienia wszystkich wywołań? |
|
DejaVu Temat założony przez niniejszego użytkownika |
» 2012-03-25 15:13:48 Tak, ale zazwyczaj jak robisz metodę wirtualną Execute to ona nie przyjmuje żadnych argumentów, a obiekt sam z siebie ma dostęp w metodzie Execute do wszystkich niezbędnych narzędzi (czyli np. innych obiektów).
/edit:
Przykład:
class IKlasa { virtual void Execute() = 0; };
class CKlasa : public IKlasa { CKlasa( CDane & dane ) : m_dane( dane ) { } virtual void Execute() { m_dane.costam(); } private: CDane & m_dane; };
CDane dane;
IKlasa * pKlasa = new CKlasa( dane ); pKlasa->Execute();
|
|
npHard |
» 2012-03-25 15:43:43 1. Tylko typu wyliczeniowego. We wzorcu Visitor musiałbyś dodać nowa metodę dla każdego nowego typu. |
Tak, ale jak mówiłem visitor jest dla stałych hierarchii, do których rzadko dodaje się nowe klasy. Poza tym w visitorze można stworzyć metodę typu catch-all, która będzie przyjmowała wskaźnik na obiekt typu IKlasa.
2. Czyli np wadą zmiany deklaracji funkcji jest konieczność zmienienia wszystkich wywołań? |
Oczywiście, że tak. Wyobraź sobie na prawdę duży projekt, w którym dana funkcja wywoływana jest 1000 razy. Zmieniamy deklarację i... mamy problem. Można oczywiście zrobić jakąś wyszukaną operację typu "przeszukaj wszystko i zamień", ale to też jest obarczone sporym prawdopodobieństwem błędów. W takim wypadku lepiej jest zrobić nową funkcję z nowym zestawem argumentów, a starą zmienić tak żeby tylko wywoływała tę nową... Kod należy pisać i projektować tak żeby jakaś nie wielka zmiana nie pociągała za sobą konieczności przejrzenia połowy projektu.
I nie powiedziałem, że dynamic_cast albo to rozwiązanie z enumem i GetType() jest złe. Po prostu nie powinno się go nadużywać. Wszystko jest dla ludzi. |
|
« 1 » |