So. Hab effektiv kaum einen Fortschritt gemacht. Denn ich hab meine quick’n’dirty variante jetzt in sauber implementiert. Und dabei versuche ich so gut es geht dem zu folgen was ich hier gesehen habe:
Von dem her werde ich später auch wirklich viele Interfaces haben. Auch wenn ich mit denen sonst immer eher sparsam umgehe - hier macht es irgendwie mehr Sinn.
Und zwar aus zwei Gründen:
- In C# kann man Klassen nicht so einfach mocken
- Ich weiß wirklich sehr wenig manchmal darüber, wer die Implementierung bereit stellen wird (dementsprechend wäre eine konkrete Klasse schlecht, wenn ich ein Monobehavior brauch).
Auf jeden Fall bekomme ich jetzt meine Funktionalitäten viel besser gekapselt. Hier mal ein Beispiel, wie mein BluePrintProvider ausschaut:
namespace Game.Modes {
public class BluePrintProvider : MonoBehaviour {
private SignalBus _signalBus;
private IMapService _mapService;
private IBuildConfigLookup _lookup;
private GameObject _bluePrint;
[Inject]
public void Init(SignalBus signalBus, IMapService mapService, IBuildConfigLookup lookup) {
_signalBus = signalBus;
_mapService = mapService;
_lookup = lookup;
_signalBus.Subscribe<EnterBuildModeSignal>(OnEnterBuildMode);
_signalBus.Subscribe<ExitBuildModeSignal>(OnExitBuildMode);
}
private void OnEnterBuildMode(EnterBuildModeSignal signal) {
if (_lookup.TryLookup(signal.BuildingType, out var config)) {
_bluePrint = _mapService.PlaceObject(config.Selected);
}
}
private void OnExitBuildMode() {
Destroy(_bluePrint);
}
}
}
In meinen anderen Projekten wären die Interfaces wohl alle nicht vorhanden und die komplette Logik wäre erstmal im BluePrintProvider selber gelandet. Was er macht? Erkläre ich kurz.
SignalBuss
Der SignalBus ist eigentlich ein EventBus. Ich kann mich für bestimmte Events (hier: Signale) registrieren und bekomme dann mit, wenn was passiert.
Der SignalBus wird von Zenject bereit gestellt.
MapService
Der MapService erstellt mir Instanzen von den übergebenen Objekten und platziert diese in der Szene. Wer Unity kennt, für den klingt das erstmal nach einem Aufruf von Object.Instantiate. Da dieser Aufruf das testen schwer machen kann, wäre es deswegen schon wert das auszulagern, aber nein - es passiert etwas mehr.
Der MapService wird von einem MonoBehavior bereit gestellt, denn ich brauche Informationen aus der Szene:
- Kameraposition
- Das GameObject welches als Parent dienen soll
Die Kameraposition brauche ich, damit ich das Objekt immer im Sichtfeld vom Benutzer platzieren kann. Nachdem das geschehen ist, passt der Service auch gleichzeitig noch die Position am Raster an.
LookupService
Das ist ein richtig tolles Ding. Denn das schaut nach ob es zu einem GebäudeTyp im Resourcenordner eine BuildConfig gibt. Falls ja liefert es true zurück und gibt die Config zurück (der zweite Parameter ist ein out-param). In der BuildConfig sind momentan 3 Infos enthalten:
- GebäudeTyp (Factory, House oder Straße)
- Prefab des Gebäude
- Prefab des Gebäude als BluePrint
Die Implementierung von diesem Service ist in diesem Fall kein MonoBehavior. Denn ich brauche keine Informationen aus der Szene. Kann es aber wunderbar mittels Zenject verteilen.
Was mir hierbei extrem gut gefällt ist, dass ich wirklich kleine Klassen hab die sehr stark dem SRP folgen. Dementsprechend waren meine Tests bisher auch alle sehr einfach.
Dadurch ließ sich auch der BluePrintProvider (welcher noch nicht fertig ist) bisher sehr einfach bauen. Seltsamerweise war mir noch nicht komplett klar, wie oder was der BluePrintProvider tun soll. Als ich die Klasse vor mir hatte, drehten sich meine Gedanken mehr um die Szene und wo ich den Provider platziere. Als ich mich dann entschloss mit dem Test anzufangen war es ganz einfach. Ich wusste was er tun soll und schrieb das runter. Der Test hat also wirklich geholfen Fokus zu bekommen.
Zudem konnte ich (dank der Interfaces) den Provider schreiben obwohl mir noch eine Implementierung für den IMapService gefehlt hatte.
Also alles in allem entdecke ich hier gerade DI wieder neu. Ich hätte nicht erwartet, dass es solch einen merklichen Unterschied schon jetzt macht. Begeistert bin ich aber auf jeden Fall .