Pracuję nad grą (piszę własny silnik fizyki) i staram się postępować zgodnie z dobrym projektem, pisząc ją. Ostatnio natknąłem się na wiele trudnych do znalezienia błędów, a żeby łatwiej je złapać, próbowałem pisać testy jednostkowe. Było to trudne, ponieważ wiele moich komponentów jest ściśle sprzężonych, szczególnie z modułem game
.Eliminowanie sprzężenia i stanu globalnego w "Game" singleton
W moim module game
eksportuję instancję klasy singleton, która przechowuje bieżący stan gry, czas, zdarzenia związane z grą itp. Jednak po przeczytaniu this i poprzez zbadanie, w jaki sposób zmniejszyć to sprzężenie, postanowiłem spróbować przepisać klasę tak, że nie jest już singletonem.
Chodzi o to, aby użyć klasy GameState
i omijać te obiekty wszędzie, tak aby testy jednostkowe mogły stworzyć minimalne stany do testów. Większość funkcji staje się po prostu funkcją stanu gry i zwraca nowy stan gry. Jednakże, Wystąpiły problemy z projektowaniem::
Pozycje i prędkość obiektów obiektu są właściwościami python, które są obliczane zgodnie z bieżącym czasem. Oznacza to, że nie mogę po prostu przekazać obiektu GameState
bez przepisywania go jako funkcji (co skutkuje icky składni). Przykładowy kod:
class Entity:
@property
def position(self):
"""Interpolate the position from the last known position from that time."""
# Here, the game class instance is referenced directly, creating coupling.
dt = game.game_time - self._last_valid_time
# x_f = x_i + v_i*t + 1/2 at^2
return self._position + self._velocity * dt + .5 * self._acceleration * dt**2
Zrobiłem kilka badań, jak rozwiązać ten problem, i przebiegł Dependency Injection. Zasadniczo mógłbym przekazać obiekt "gettera" GameState do inicjatora każdej jednostki. Wszystkie obiekty "getter" GameState po prostu zwrócą obecny stan. Przykład GameStateGetter
klasa:
class GameStateGetter:
_CurrentState = None
def update(self, new_state):
GameStateGetter._CurrentState = new_state
def __getattr__(self, name):
# GameState object properties are immutable, so this can't cause issues
return getattr(GameStateGetter._CurrentState, name)
Teraz za moje pytania.
- Czy używanie obiektów GameState "getter" byłoby dobrym pomysłem?
Jednym z problemów będzie interfejs do aktualizacji bieżącego stanu gry (zdefiniowałem metodę update
, ale wydaje się to dziwnym interfejsem). Ponadto, ponieważ problem ze zmiennymi globalnymi jest nieprzewidywalnym stanem programu, tak naprawdę nie zapobiegnie to.
- Czy istnieje inny sposób na „wstrzyknąć” zależność do klasy za
Entity
position
własnościgame
?
Idealnie, chcę zachować rzeczy proste. Klasa GameStateGetter
brzmi bardzo abstrakcyjnie (nawet jeśli implementacja jest prosta). Byłoby miło, gdyby moje właściwości mogły pośrednio przekazywać obecny stan.
Z góry dziękujemy za wszelką pomoc, jaką możesz zapewnić!
Bardziej powszechne jest określenie "operator". (Co powstrzymuje cię od mylenia obiektów dostawcy z metodami getter.) I tak, używanie ich jest dobrym pomysłem. Wstrzykiwanie na uzależnienie jest jak przemoc: jeśli to nie działa, prawdopodobnie nie używasz go w wystarczającym stopniu. –
@RobertRossney Thanks! Moje nowe zrozumienie Dependency Injection łączy się teraz z pewnym kodem, który widziałem wcześniej z klasami o nazwie " Provider". Być może z dodatkowymi informacjami może to zostać zaakceptowane jako odpowiedź! –
Darthfett