@Autor tematu:
@Dr Zielu:
Autor tematu ma uzasadnione obawy, Zielu. To nie tak, że jeśli tylko w projekcie używasz klas, to znaczy, że od razu korzystasz z dobrodziejstw (czy tzw. "ducha") programowania OO. Można używać klas i pisać tak naprawdę w totalnie nieobiektowy sposób!
Np. jeśli prawie wszystkie funkcje wrzucisz w klasę Game i będziesz miał pełno rzeczy typu:
Kopiuj
void Game::draw(Entity entity) {
}
void Game::makePlayerLookAt(Player player, Entity target) {
}
To nie będzie to napisane w prawdziwie obiektowy, właściwy sposób.
To nie kwestia stylu. Raczej: zasad dość powszechnie używanych za właściwe.
Powyższy przykład powinien raczej zostać napisany tak:
Kopiuj
void Entity::draw() {
}
void Player::lookAt(Entity target) {
}
Wspomniane zasady dotyczą m.in eliminowania tzw. "brzydkich zapachów" (pisze o tym np. Kent Beck).
Jednym z brzydkich zapasów jest "zazdrość o kod". Mamy z nim do czynienia, gdy metoda f() obiektu A intensywnie korzysta z danych obiektu B, np. przekazanego jako parametr. Taka sytuacja sugeruje, że metoda f() tak naprawdę powinna być składową obiektu B -- skoro tak bardzo potrzebuje jego danych. Dlatego w moim przykładzie przeniosłem draw() z klasy Game do Entity.
Kolejny z brzydkich zapachów to "duża klasa". Klasy powinny mieć wyważony rozmiar. Niedawno podsłyszałem w pracy, jak kumpel złapał się za głowę, bo ktoś commitował kilka klas mających ponad 300 linii kodu -- co w jego odczuciu było stanowczo zbyt wielkimi klockami.
W moim przykładzie, a także w przykładzie autora tematu, taką wielką klasą śmierdzi mi Game -- i to na kilometr. Może szybko stać się klasą, która robi wszystko. I po cholerę nam wtedy klasy, jeśli sporą część kodu mamy tak naprawdę w jednej z nich? Wrzucamy tam wszystko z lenistwa: nie chce nam się myśleć, gdzie tak naprawdę powinna się znaleźć nasza metoda, no ale od czego mamy wielki, uniwersalny wór na funkcje...
Jak mam odpowiedzieć autorowi?
Cóż, gdyby funkcja nazywała się lookAt(), to na pewno powinna być w klasie Player. W przypadku zabijania, które "wykonuje" jeden obiekt, ale które tak naprawdę bardziej wpływa na drugi obiekt (cel), sytuacja może być odrobinkę bardziej kontrowersyjna. Może by tak zaimplementować funkcję i sprawdzić, na jakich danych najczęściej operuje? Obu obiektów porówno? Jakie jeszcze metody wywołuje?
Generalnie, najlepiej by było, żeby metoda operowała na danych i metodach swojej własnej klasy. Ściślej: swojego własnego obiektu.
Jakbym miał zgadywać na pałę, bez wiedzy odnośnie implementacji i bez szerszego kontekstu, to raczej sugerowałbym sygnaturę "Player:kill(Player target)". Nie nazywałbym parametru po prostu "player", bo w C++ od tego mamy typ, natomiast chcemy przekazać, że to parametr jest zabijany.
Sytuacja w grze może być jednak inna. Niestety, mam za małe doświadczenie w pisaniu gier żeby zgadywać na rozsądnym poziomie. W momencie zabicia kogoś możemy chcieć uaktualnić planszę z punktami, lub nawet sprawdzić, czy w tym momencie gra się nie skończyła (np. w trybie Insta Death / "do złotej fragi").