Czy gry detaliczne używają „inwersji kontroli” i „zastrzyku zależności”?

30

Wielu bardziej uważnych programistów, których znam, przechodzi na odwrócenie kontroli i wstrzykiwanie zależności w celu obsługi odniesień do obiektów. Patrząc z perspektywy gier Flash, nie znam wszystkich tajników studiów AAA, więc: czy są one używane w świecie gier detalicznych?

Iain
źródło
Myślę, że w tym świecie wydajność ma znaczenie przede wszystkim, ponieważ większa liczba użytkowników będzie dotknięta niską wydajnością niż programiści złym kodem.
Bart van Heukelom,
Wstrzykiwanie zależności brzmi jak system oparty na komponentach, ale czy miałbyś przykład, w jaki sposób można odwrócić kontrolę?
ADB
Ponieważ większość tego wątku wydaje się być źle poinformowana o tym, czym jest IoC i co rozwiązuje, a OP tak naprawdę nie pyta, że ​​przede wszystkim wskażę
Boris Callens
Tak, robią. W rzeczywistości Rare rozmawiał o tym, jak wdrażają swoje testy w Sea of ​​Thieves - youtube.com/watch?v=KmaGxprTUfI&t=1s . Niestety nie mieli zbyt wiele do powiedzenia na temat Inversion of Control w Unreal Engine, napisałem jednak projekt, który pokazuje, jak to działa: github.com/jimmyt1988/UE-Testing
Jimmyt1988

Odpowiedzi:

45

Nazywacie to „starannym opracowywaniem oprogramowania”, ja nazywam „bolesną nadinżynierią”. Nie oznacza to, że inwersja kontroli jest zła - w rzeczywistości jej podstawowa definicja jest dobra - ale rozprzestrzenianie się całych ram i metod pracy nad osiągnięciem tego wszystkiego jest mało szalone, szczególnie w połączeniu ze sposobem, w jaki ludzie wyrzucają śmieci idealnie dobre i czyste interfejsy, aby móc wstrzykiwać wymienne komponenty, których 99% czasu nigdy nie wymienisz. Jest to coś, co może wywodzić się wyłącznie ze środowiska korporacyjnego Java i cieszę się, że nie ma tak dużego wsparcia w innych miejscach.

Często argumentem jest to, że nawet jeśli nie zamieniasz komponentów, chcesz móc je testować w izolacji z próbnymi obiektami i tym podobnymi. Obawiam się jednak, że nigdy nie kupię argumentu, że warto nadmuchać i skomplikować interfejs, aby móc go lepiej przetestować. Testowanie potwierdza tylko jedno - że testy działają. Z drugiej strony czyste i minimalne interfejsy mają długą drogę do udowodnienia, że Twój kod działa.

Krótka odpowiedź: tak, ale nie w sposób, w jaki myślisz. Czasami, gdy potrzebujesz zachowania wymiennego, przekazujesz obiekt do konstruktora, który dyktuje część zachowania nowego obiektu. O to chodzi.

Kylotan
źródło
5
+1 - Zgadzam się, że społeczność Java wydaje się nadmiernie skomplikować, co wydaje się bardzo prostym pomysłem.
Chris Howe
4
Pomysł nie musi koniecznie wymieniać różnych implementacji w produkcji, ale że możesz chcieć wprowadzić specjalne implementacje, aby ułatwić automatyczne testowanie. Pod tym względem DI / IoC z pewnością może być przydatny w grach, ale chcesz zachować jego rozsądny zasięg. Nie powinieneś potrzebować więcej niż kilku jednorazowych rozdzielczości usług na wysokim poziomie - nie chcesz być usługami dla każdego małego obiektu w grze, a już na pewno nie dla każdej ramki lub czegoś podobnego.
Mike Strobel,
2
@Thomas Dufour: koncepcja testu nigdy niczego nie udowodni. To tylko punkt danych. Możesz zdać test 100 000 razy, nie udowadniając, że przejdzie on test 100001. Dowód pochodzi z logicznej analizy przedmiotu. Twierdziłbym, że te pojemniki IoC i tym podobne ułatwiają testowanie kosztem utrudniania udowodnienia poprawności i uważam, że to zła rzecz.
Kylotan
13
@Kylotan koncepcja testu nigdy nie próbuje niczego udowodnić . Próbuje wykazać, że zachowanie przy danych wejściach jest zgodne z oczekiwaniami. Im więcej zachowań jest poprawnych, tym większe jest Twoje przekonanie, że ogólna funkcja jest poprawna, ale nigdy nie jest w 100%. Stwierdzenie, że minimalny interfejs dowodzi, że coś jest śmieszne, jest śmieszne.
dash-tom-bang
5
@Alex Schearer: Obawiam się, że nigdy nie mogę zaakceptować faktu, że konieczność użycia sformalizowanego systemu kontenerów IoC i zwykle tworzenie dodatkowych argumentów konstruktora lub klas fabrycznych w celu ułatwienia testowania w jakikolwiek sposób sprawia, że ​​kod jest lepiej uwzględniany, a na pewno nie czyniąc to luźniejszym sprzężonym. W rzeczywistości prawie depcze po kapsułkowaniu, co jest kluczowym aspektem OOP. Zamiast polegać na TDD w celu napędzania ich projektu i mieć nadzieję, że pojawi się dobry wynik, ludzie mogą po prostu przestrzegać zasad SOLID.
Kylotan
17

Wzór strategii , kompozycja , wstrzykiwanie zależności są bardzo ściśle powiązane.

Ponieważ wzorzec strategii jest formą wstrzykiwania zależności, na przykład silniki takie jak Unity są całkowicie oparte na tej zasadzie. Ich użycie komponentów (wzorzec strategii) jest głęboko osadzone w całym silniku.

Jedną z głównych korzyści poza ponownym użyciem komponentów jest unikanie przerażających głębokich hierarchii klas.

Oto artykuł Micka Westa, który opowiada o tym, jak wprowadził ten system do serii gier Tony Hawk od Neversoft.

Rozwijaj swoją hierarchię

Do niedawna programiści gier konsekwentnie stosowali głęboką hierarchię klas do reprezentowania bytów gier. Fala zaczyna się przesuwać z tego zastosowania głębokich hierarchii do różnych metod, które składają się na obiekt jednostki gry jako agregacja składników ...

David Young
źródło
2
+1, Evolve Your Hierarchy to świetny link, o którym całkowicie zapomniałem. Slajdy Scott Bilas też są dobre - poprawny link do nich to ( scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf ). Jest jeszcze jeden zestaw powiązanych slajdów, ale zapomniałem, gdzie ...
Leander
Również artykuł w Games Gems 5 (jak sądzę) autorstwa Bjarne Rene.
Chris Howe
13

Wydaje się, że istnieje wiele zamieszania na temat wzoru Inversion of Control (IoC). Wiele osób utożsamiło go ze Wzorcem Strategii lub Modelem Składowym, ale te porównania nie oddają w rzeczywistości tego, o co chodzi w IoC. IoC naprawdę dotyczy sposobu uzyskania zależności. Dam ci przykład:

class Game {
    void Load() {
        this.Sprite.Load(); // loads resource for drawing later
    }
}

class Sprite {
    void Load() {
        FileReader reader = new FileReader("path/to/resource.gif");
        // load image from file
    }
}

W powyższym jasne jest, że Sprite.Loadma zależność od FileReader. Aby przetestować metodę, potrzebujesz:

  1. System plików na miejscu
  2. Plik testowy do załadowania z systemu plików
  3. Możliwość wyzwalania typowych błędów systemu plików

Pierwsze dwa są oczywiste, ale jeśli chcesz mieć pewność, że obsługa błędów działa zgodnie z oczekiwaniami, naprawdę potrzebujesz również # 3. W obu przypadkach potencjalnie spowolniłeś swoje testy, ponieważ muszą teraz przejść na dysk i prawdopodobnie sprawiłeś, że środowisko testowe jest bardziej skomplikowane.

Celem IoC jest oddzielenie użycia zachowania od jego budowy. Zauważ, że różni się to od wzorca strategii. W przypadku wzorca strategii celem jest kapsułkowanie zachowania, które można ponownie wykorzystać, aby można go było łatwo przedłużyć w przyszłości; nie ma nic do powiedzenia na temat budowy strategii.

Gdybyśmy przepisali Sprite.Loadpowyższą metodę, prawdopodobnie otrzymalibyśmy:

class Sprite {
    void Load(IReader reader) {
        // load image through reader
    }
}

Teraz oddzieliliśmy konstrukcję czytnika od jego użycia. Dlatego podczas testowania można zamienić czytnik testowy. Oznacza to, że środowisko testowe nie potrzebuje już systemu plików, plików testowych i może łatwo symulować zdarzenia błędów.

Zauważ, że zrobiłem dwie rzeczy podczas mojego przepisywania. Stworzyłem interfejs, IReaderktóry zawierał pewne zachowania - tj. Wdrożyłem wzorzec strategii. Ponadto przeniosłem odpowiedzialność za stworzenie odpowiedniego czytelnika na inną klasę.

Może nie potrzebujemy nowej nazwy wzoru do opisania powyższego. Uderza mnie to jako połączenie strategii i wzorców fabrycznych (dla kontenerów IoC). Biorąc to pod uwagę, nie jestem pewien, z jakich powodów ludzie sprzeciwiają się temu wzorowi, ponieważ jest jasne, że rozwiązuje on prawdziwy problem, a na pewno nie jest dla mnie oczywiste, co to ma wspólnego z Javą.

Alex Schearer
źródło
2
Alex: nikt nie sprzeciwia się przekazywaniu już utworzonych obiektów, aby w razie potrzeby sterować niestandardowymi funkcjami. Wszyscy to robią, tak jak w twoim przykładzie. Problem polega na tym, że ludzie używają całych struktur, które istnieją wyłącznie w celu obsługi tych rzeczy, i religijnie kodują każdy aspekt funkcjonalności, aby polegać na tej funkcjonalności. Najpierw dodają IReader jako parametr do konstrukcji Sprite, a następnie potrzebują specjalnych obiektów SpriteWithFileReaderFactory, aby to ułatwić itp. Takie podejście pochodzi z Javy i takich rzeczy, jak kontenery Spring IoC itp.
Kylotan
2
Ale nie mówimy o kontenerach IoC - przynajmniej nie widzę tego w oryginalnym poście. Mówimy tylko o samym wzorze. Argument, jak mogę najlepiej powiedzieć, brzmi: „Ponieważ niektóre narzędzia na wolności są złe, nie powinniśmy używać podstawowych pojęć”. Uderza mnie to, jak wylewam dziecko z kąpielą. Osiągnięcie właściwej równowagi między prostotą, robieniem rzeczy a utrzymywalnością / testowalnością jest trudnym problemem, który najlepiej najlepiej potraktować w poszczególnych projektach.
Alex Schearer
Podejrzewam, że wiele osób sprzeciwia się IoC, ponieważ oznacza to:
phtrivier
4

Powiedziałbym, że jest to jedno z wielu narzędzi i jest używane od czasu do czasu. Jak powiedział tenpn, każda metoda, która wprowadza vtables (i ogólnie każdą dodatkową pośrednią) może mieć negatywny wpływ na wydajność, ale jest to coś, o co powinniśmy się martwić tylko w przypadku kodu niskiego poziomu. W przypadku kodu strukturalnego wysokiego poziomu, który tak naprawdę nie stanowi problemu, a IoC może mieć pozytywne korzyści. Wszystko, aby zmniejszyć zależności między klasami i uczynić kod bardziej elastycznym.

Chris Howe
źródło
2
Mówiąc dokładniej, martwię się karami vtable za każdy kod, który jest nazywany wiele razy ramką. Może to być niski poziom.
tenpn
4

Muszę się zgodzić z Kylotanem w tej sprawie. „Wstrzykiwanie zależności” to brzydkie rozwiązanie Java dla brzydkiej wady koncepcyjnej Javy, a jedynym powodem, dla którego ktoś w ogóle patrzy na to w innych językach, jest fakt, że Java staje się pierwszym językiem dla wielu ludzi, a tak naprawdę nie powinno.

Z drugiej strony odwrócenie kontroli jest użytecznym pomysłem, który istnieje od dawna i jest bardzo pomocny, gdy jest właściwie wykonany. (Nie jest to metoda Java / Dependency Injection). Prawdopodobnie robisz to cały czas, jeśli pracujesz w rozsądnym języku programowania. Kiedy po raz pierwszy przeczytałem o całej tej „MKOlowej” sprawie, o której wszyscy bzykali, byłem całkowicie rozczarowany. Odwrócenie kontroli to nic innego jak przekazanie wskaźnika funkcji (lub wskaźnika metody) do procedury lub obiektu, aby pomóc dostosować jego zachowanie.

To pomysł, który istnieje od lat 50. XX wieku. Jest w standardowej bibliotece C (przychodzi na myśl qsort) i wszędzie w Delphi i .NET (programy obsługi / delegatów zdarzeń). Umożliwia staremu kodowi wywoływanie nowego kodu bez konieczności ponownej kompilacji starego kodu i cały czas jest wykorzystywany w silnikach gier.

Mason Wheeler
źródło
2
Byłem też z „tak to jest to, co ludzie są podekscytowani?” kiedy czytam o DI, ale faktem jest, że większość programistów gier mogła się z tego dowiedzieć i dowiedzieć się, w jaki sposób miałoby to zastosowanie do naszej pracy.
dash-tom-bang
2

Nie z mojego doświadczenia. Szkoda, bo kod gry musi być bardzo elastyczny, a takie podejście zdecydowanie by pomogło.

Jednak trzeba powiedzieć, że obie metody mogłyby, w C ++, wprowadzić wirtualne tabele tam, gdzie ich wcześniej nie było, przynosząc ze sobą odpowiedni spadek wydajności.

tenpn
źródło
1

Nie jestem profesjonalnym twórcą gier i tylko raz próbowałem zaimplementować IoC w C ++, więc to tylko spekulacje.

Podejrzewam jednak, że twórcy gier byliby podejrzliwi wobec IoC, ponieważ oznacza to:

1 / projektowanie wielu interfejsów i wielu małych klas

2 / mając prawie każde ostatnio wywołane połączenie funkcji

Teraz może to być zbieg okoliczności, ale oba wydają się być nieco bardziej skomplikowane i / lub problematyczne dla wydajności w C ++ (który jest szeroko stosowany w grze, prawda?) Niż w Javie, gdzie Internet Rzeczy stał się powszechny (Prawdopodobnie ponieważ było to stosunkowo łatwe, pomóż ludziom zaprojektować bardziej rozsądne hierarchie obiektów, a ponieważ niektórzy wierzyli, że może zmienić pisanie aplikacji w Javie w pisanie aplikacji w XML, ale to kolejna debata: P)

Interesują mnie komentarze, jeśli to w ogóle nie ma sensu;)

phtrivier
źródło