Immagina di dover usare 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 si desidera semplificare l'utilizzo, in particolare, per eliminare l'uso ripetitivo di parametri che non sono necessari per l'applicazione.
Bene, allora inizi a costruire un livello anticorruzione.
La prima cosa è assicurarsi che il "codice principale" non si riferisca Messy
direttamente. Ad esempio, si organizza la gestione delle dipendenze in modo tale che il tentativo di accedere Messy
non riesca a compilare.
In secondo luogo, crei un modulo "layer" dedicato che è l'unico ad accedervi Messy
ed esponilo al tuo "codice principale" in un modo che abbia più senso per te.
Il codice del livello sarebbe 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 si scherza Messy
, usando Reasonable
invece, 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 che c'è ancora un po 'di confusione, Messy
ma ora è nascosto ragionevolmente in profondità Reasonable
, rendendo il tuo "codice principale" ragionevolmente pulito e privo di corruzione che verrebbe portato lì dall'uso diretto di Messy
cose.
L'esempio sopra si basa su come viene spiegato il livello di anticorruzione nel wiki di c2:
Se l'applicazione deve gestire un database o un'altra applicazione il cui modello è indesiderabile o inapplicabile al modello desiderato all'interno della propria applicazione, utilizzare un AnticorruptionLayer per tradurre da / verso quel modello e il proprio.
Nota esempio è intenzionalmente reso semplice e condensato per mantenere la spiegazione breve.
Se hai un più ampio mess-of-API da coprire dietro il livello anticorruzione, si applica lo stesso approccio: in primo luogo, assicurati che il tuo "codice principale" non acceda direttamente alle cose danneggiate e in secondo luogo, esponilo in un modo che sia più conveniente nel contesto di utilizzo.
Quando "ridimensionate" il livello oltre un esempio semplificato sopra, tenete presente che rendere conveniente la vostra API non è necessariamente un'attività banale. Investi uno sforzo per progettare il tuo livello nel modo giusto , verificane l'uso previsto con unit test ecc.
In altre parole, assicurati che la tua API sia effettivamente un miglioramento rispetto a quella che nasconde, assicurati di non introdurre solo un altro livello di corruzione.
Per completezza, notare una differenza sottile ma importante tra questo e i relativi schemi Adapter and Facade . Come indicato dal suo nome, il livello anticorruzione presuppone che l' API sottostante presenti problemi di qualità (è "corrotto") e intende offrire una protezione dei problemi citati.
Puoi pensarlo in questo modo: se puoi giustificare che il progettista di biblioteche starebbe meglio esponendo la sua funzionalità con Reasonable
invece di Messy
, ciò significherebbe che stai lavorando su un livello anticorruzione, facendo il loro lavoro, correggendo i loro errori di progettazione.
Al contrario, Adapter e Facade non fanno ipotesi sulla qualità del design sottostante. Questi potrebbero essere applicati all'API che è ben progettata per cominciare, adattandola alle tue esigenze specifiche.
In realtà, potrebbe anche essere più produttivo supporre che modelli come Adapter e Facade si aspettino che il codice sottostante sia ben progettato. Puoi pensarlo in questo modo: un codice ben progettato non dovrebbe essere troppo difficile da modificare per un caso d'uso particolare. Se si scopre che la progettazione dell'adattatore richiede più sforzo del previsto, ciò potrebbe indicare che il codice sottostante è, in qualche modo, "corrotto". In tal caso, puoi considerare di suddividere il lavoro in fasi separate: in primo luogo, stabilisci un livello anticorruzione per presentare l'API sottostante in modo adeguatamente strutturato e, successivamente, disegna l'adattatore / la facciata su quel livello di protezione.