I metodi statici sono cattivi, non perché siano "pre-OOP", ma proprio perché sono essi stessi delle dipendenze che non possono essere invertiti. Recentemente ho provato a testare una base di codice che includeva una chiamata come
void foo() {
...
Time now = Clock::getCurrentSystemTime();
...
}
Ha senso che questo è statico poiché (a) questo codice è sensibile alle prestazioni, e (b) può esserci solo un'ora corrente. Sfortunatamente questo significava che non potevo iniettare un Clock
del mio per questi test in modo da poter testare il comportamento del sistema attorno a certe volte. In altre parole, quel codebase ha violato il principio di inversione delle dipendenze.
L'inversione delle dipendenze è semplice: devo solo rendere esplicita la dipendenza e passarla come parametro all'unità di codice corrente (ad es. costruttore di oggetti o parametro di metodo):
type CurrentTimeProvider = () => Time;
void foo(CurrentTimeProvider currentTime) {
...
Time now = currentTime();
...
}
// in production:
foo(Clock::getCurrentSystemTime);
// in testing:
foo(() => Time("2000-01-01T00:00:00.000Z"));
Non ci sono metodi statici coinvolti qui in alcun modo significativo. Nei casi più complicati, introdurremmo probabilmente un contenitore di iniezione di dipendenza completo che si occupa della risoluzione di tutte le dipendenze. Il contenitore DI è di per sé un oggetto fabbrica astratto. Idealmente, sarebbe passato in giro esplicitamente. Poi:
type CurrentTimeProvider = () => Time;
void foo(DIContainer deps) {
...
Time now = deps.get(CurrentTimeProvider)();
...
}
DiContainer deps;
// in production:
deps.set(CurrentTimeProvider, Clock::getCurrentSystemTime);
foo(deps);
// in testing:
deps.set(CurrentTimeProvider, () => Time("2000-01-01T00:00:00.000Z"));
foo(deps);
Bene, ma le persone si stancano rapidamente di qualsiasi tipo di iniezione di dipendenza manuale. Quindi, supponendo che una dipendenza non invertibile su un sistema di input delle dipendenze sia OK, potremmo rendere statico l'API del contenitore DI (o rendere il contenitore un singleton, che è essenzialmente la stessa cosa). L'esempio precedente potrebbe cambiare in:
type CurrentTimeProvider = () => Time;
void foo() {
...
Time now = DIContainer.get(CurrentTimeProvider)();
...
}
// in production:
DIContainer.set(CurrentTimeProvider, Clock::getCurrentSystemTime);
foo();
// in testing:
DIContainer.set(CurrentTimeProvider, () => Time("2000-01-01T00:00:00.000Z"));
foo();
In realtà, è abbastanza elegante e sufficientemente equilibrato. E 'un design OOP abbagliante? Probabilmente no, ma non ti imbatterai in alcun tipo di problema con questo (a meno che tu non provi a prendere in giro il sistema di dipendenze stesso). Se non ti piace un sistema di DI così globale (e non mi piace neanche), puoi scegliere una delle altre strategie disponibili disponibili per DI (di nuovo: parametro esplicito / iniezione costruttore o iniezione di un contenitore DI / oggetto di fabbrica). In una visualizzazione indipendente dalla lingua, l'unica cosa nel tuo programma che non può essere statica è la funzione del punto di ingresso del tuo programma / main
.