Immagina di dover utilizzare il codice di qualcun altro progettato come mostrato di seguito:
class Messy {
String concat(String param, String str) { /* ... */ }
boolean contains(String param, String s) { /* ... */ }
boolean isEmpty(String param) { /* ... */ }
boolean matches(String param, String regex) { /* ... */ }
boolean startsWith(String param, String prefix) { /* ... */ }
}
Ora immagina di scoprire che il tuo codice che dipende da esso è simile al seguente:
String process(String param) {
Messy messy = new Messy();
if (messy.contains(param, "whatever")) {
return messy.concat(param, "-contains");
}
if (messy.isEmpty(param)) {
return messy.concat(param, "-empty");
}
if (messy.matches(param, "[whatever]")) {
return messy.concat(param, "-matches");
}
if (messy.startsWith(param, "whatever")) {
return messy.concat(param, "-startsWith");
}
return messy.concat(param, "-whatever");
// WTF do I really need to repeat bloody "param" 9 times above?
}
... e che vuoi rendere più facile l'utilizzo, in particolare, per sbarazzarti dell'uso ripetitivo di parametri che non sono necessari per la tua applicazione.
Ok, quindi inizi a creare un livello anti-corruzione.
-
La prima cosa è assicurarsi che il tuo "codice principale" non faccia riferimento a Messy
direttamente. Ad esempio, disponi gestione delle dipendenze in modo che provi ad accedere a Messy
non riesce a compilare.
-
In secondo luogo, crei un modulo "layer" dedicato che è l'unico che accede a Messy
e lo espone al tuo "codice principale" in un modo che ha più senso per te.
Il codice del layer sarà simile al seguente:
class Reasonable { // anti-corruption layer
String param;
Messy messy = new Messy();
Reasonable(String param) {
this.param = param;
}
String concat(String str) { return messy.concat(param, str); }
boolean contains(String s) { return messy.contains(param, s); }
boolean isEmpty() { return messy.isEmpty(param); }
boolean matches(String regex) { return messy.matches(param, regex); }
boolean startsWith(String prefix) { return messy.startsWith(param, prefix); }
}
Di conseguenza, il tuo "codice principale" non incasina con Messy
, usando invece Reasonable
, come segue:
String process(String param) {
Reasonable reasonable = new Reasonable(param);
// single use of "param" above and voila, you're free
if (reasonable.contains("whatever")) {
return reasonable.concat("-contains");
}
if (reasonable.isEmpty()) {
return reasonable.concat("-empty");
}
if (reasonable.matches("[whatever]")) {
return reasonable.concat("-matches");
}
if (reasonable.startsWith("whatever")) {
return reasonable.concat("-startsWith");
}
return reasonable.concat("-whatever");
}
Nota, c'è ancora un po 'di confusione con Messy
, ma questo è ora nascosto ragionevolmente all'interno di Reasonable
, rendendo il tuo "codice principale" ragionevolmente pulito e privo di corruzione che verrebbe portato lì tramite l'uso diretto di Messy
stuff.
L'esempio sopra è basato sul modo in cui Anticorruption Layer è spiegato su wiki c2:
If your application needs to deal with a database or another application whose model is undesirable or inapplicable to the model you want within your own application, use an AnticorruptionLayer to translate to/from that model and yours.
L'esempio di nota è intenzionalmente semplificato e condensato per mantenere una spiegazione breve.
Se hai un mess-of-API più grande per coprire il livello anti-corruzione, lo stesso approccio si applica: in primo luogo, assicurati che il tuo "codice principale" non acceda roba corrotta direttamente e in secondo luogo, esponilo in un modo che è più conveniente nel tuo contesto di utilizzo.
Quando "ridimensiona" il tuo livello oltre un esempio semplificato sopra, considera che rendere conveniente l'API non è necessariamente un compito banale. Investi un sforzo su progetta il tuo livello nel modo giusto , verificane la sua uso previsto con unit test ecc.
In altre parole, assicurati che la tua API sia effettivamente un miglioramento su una che nasconde, assicurati di non introdurre solo un altro livello di danneggiamento.
Per completezza, nota una sottile ma importante differenza tra questo e i relativi modelli Adapter e Facade . Come indicato dal suo nome, il livello anticorruzione presuppone che l'API sottostante abbia problemi di qualità (è "danneggiato") e intende offrire una protezione dei problemi citati.
Puoi pensarci in questo modo: se puoi giustificare che il progettista di librerie sarebbe meglio esporre le sue funzionalità con Reasonable
invece di Messy
, questo significherebbe che stai lavorando sul livello di anticorruzione, facendo il loro lavoro , che fissa i loro errori di progettazione.
Al contrario, Adapter e Facade non fanno ipotesi sulla qualità del design sottostante. Questi potrebbero essere applicati all'API ben progettata per iniziare, adattandola solo per le tue esigenze specifiche.
In realtà, potrebbe anche essere più produttivo assumere che pattern come Adapter e Facade si aspettino che il codice sottostante sia ben progettato. Puoi pensare in questo modo: il codice ben progettato non dovrebbe essere troppo difficile da modificare per casi d'uso particolari. Se risulta che la progettazione dell'adattatore richiede uno sforzo maggiore del previsto, ciò potrebbe indicare che il codice sottostante è, beh, in qualche modo "danneggiato". In tal caso, puoi considerare la suddivisione del lavoro in fasi separate: in primo luogo, stabilire un livello di anticorruzione per presentare l'API sottostante in modo adeguatamente strutturato e successivamente, progettare l'adattatore / facciata su quel livello di protezione.