Wyobraź sobie, że musisz użyć kodu innej osoby, który został zaprojektowany w sposób pokazany poniżej:
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) { /* ... */ }
}
Teraz wyobraź sobie, że dowiadujesz się, że Twój zależny od niego kod wygląda następująco:
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?
}
... i chcesz ułatwić korzystanie, w szczególności, aby pozbyć się powtarzalnego używania parametrów, które po prostu nie są potrzebne w Twojej aplikacji.
Okej, więc zacznij budować warstwę antykorupcyjną.
Po pierwsze, upewnij się, że twój „główny kod” nie odnosi się Messy
bezpośrednio. Na przykład organizujesz zarządzanie zależnościami w taki sposób, aby próba uzyskania dostępu Messy
nie powiodła się.
Po drugie, tworzysz dedykowany moduł „warstwy”, który jako jedyny uzyskuje dostęp Messy
i udostępniasz go swojemu „głównemu kodowi” w sposób, który jest dla ciebie bardziej sensowny.
Kod warstwy wyglądałby następująco:
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); }
}
W rezultacie Twój „główny kod” nie zadziera z Messy
, używając Reasonable
zamiast tego około:
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");
}
Zauważ, że wciąż jest trochę bałaganu z Messy
tym bałaganem, ale teraz jest on ukryty dość głęboko w środku Reasonable
, dzięki czemu Twój „główny kod” jest dość czysty i wolny od uszkodzeń, które zostałyby wprowadzone przez bezpośrednie użycie Messy
rzeczy.
Powyższy przykład oparty jest na wyjaśnieniu warstwy antykorupcyjnej na wiki wiki c2:
Jeśli twoja aplikacja musi obsługiwać bazę danych lub inną aplikację, której model jest niepożądany lub nie ma zastosowania do modelu, który chcesz w swojej własnej aplikacji, skorzystaj z AnticorruptionLayer, aby przetłumaczyć na / z tego modelu i twojego.
Uwaga: przykład został celowo uproszczony i skrócony, aby wyjaśnienie było krótkie.
Jeśli masz większy bałagan interfejsu API, który można pokryć za warstwą antykorupcyjną, obowiązuje to samo podejście: po pierwsze, upewnij się, że „główny kod” nie ma bezpośredniego dostępu do uszkodzonych rzeczy, a po drugie, ujawnij go w sposób bardziej wygodne w kontekście użytkowania.
Kiedy „skalujesz” warstwę poza uproszczony przykład powyżej, weź pod uwagę, że uczynienie twojego API wygodnym niekoniecznie jest trywialnym zadaniem. Zainwestować wysiłek, aby zaprojektować warstwę właściwą drogę , zweryfikować jego przeznaczeniem z testów jednostkowych itp
Innymi słowy, upewnij się, że Twój interfejs API jest rzeczywiście lepszy od tego, który ukrywa, i upewnij się, że nie wprowadzasz kolejnej warstwy korupcji.
Dla kompletności zauważ subtelną, ale ważną różnicę między tym a pokrewnymi wzorami Adapter i Fasada . Jak wskazuje jego nazwa, warstwa antykorupcyjna zakłada, że u podstaw interfejsu API występują problemy z jakością (jest „uszkodzona”) i zamierza oferować ochronę wspomnianych problemów.
Można myśleć o tym w ten sposób: jeśli można uzasadnić, że projektant biblioteki byłoby lepiej odsłaniając jego funkcjonalność Reasonable
, a nie Messy
oznaczałoby to, że pracujesz na warstwie antykorupcyjnej, czyniąc ich pracę, ustalając swoje błędy projektowe.
W przeciwieństwie do tego, Adapter i Fasada nie przyjmują założeń dotyczących jakości projektu bazowego. Można je zastosować do interfejsu API, który jest dobrze zaprojektowany na początek, po prostu dostosowując go do konkretnych potrzeb.
W rzeczywistości bardziej produktywne byłoby założenie, że wzorce, takie jak Adapter i Fasada, oczekują, że kod bazowy będzie dobrze zaprojektowany. Możesz o tym pomyśleć w ten sposób: dobrze zaprojektowany kod nie powinien być zbyt trudny do dostosowania do konkretnego przypadku użycia. Jeśli okaże się, że projektowanie twojego adaptera wymaga więcej wysiłku niż się spodziewano, może to oznaczać, że kod źródłowy jest w jakiś sposób „uszkodzony”. W takim przypadku możesz rozważyć podzielenie zadania na oddzielne fazy: po pierwsze, utwórz warstwę antykorupcyjną, aby zaprezentować podstawowy interfejs API w odpowiednio ustrukturyzowany sposób, a następnie zaprojektuj adapter / fasadę na tej warstwie ochronnej.
In other words, make sure that your API is indeed an improvement over one it hides, make sure that you don't just introduce another layer of corruption.
Ta cała sekcja zasługuje na odważny tag.Aby zacytować inne źródło:
Eric Evans, Domain Driven Design, 16. drukowanie, strona 365
Najważniejsze jest to, że po każdej stronie warstwy antykorupcyjnej stosowane są różne terminy. Kiedyś pracowałem nad systemem logistyki transportu. Rundy musiały zostać zaplanowane. Trzeba było wyposażyć pojazd w magazynie, pojechać do różnych miejsc klientów i serwisować je oraz odwiedzić inne miejsca, takie jak przystanek tankowania. Ale z wyższego poziomu chodziło o planowanie zadań. Dlatego sensowne było oddzielenie bardziej ogólnych warunków planowania zadań od bardzo szczegółowych warunków logistyki transportu.
Tak więc izolacja warstw antykorupcyjnych nie polega tylko na ochronie przed niechlujnym kodem, ale na oddzieleniu różnych domen i upewnieniu się, że pozostaną one oddzielone w przyszłości.
źródło
Adapter
Gdy masz niekompatybilne interfejsy, które wykonują podobną logikę, aby dostosować jeden do drugiego, abyś mógł używać implementacji jednego z rzeczami, które oczekują drugiego.
Przykład:
Masz obiekt, który chce samochodu, ale masz tylko klasę 4WheelVehicle, więc tworzysz CarBuiltUsing4WheelVehicle i używasz go jako swojego samochodu.
Fasada
Gdy masz skomplikowane / mylące / gigantyczne API i chcesz uprościć / uprościć / zmniejszyć. Stworzysz fasadę, aby ukryć złożoność / zamieszanie / dodatki i ujawnić tylko nowe proste / jasne / małe API.
Przykład:
Korzystasz z biblioteki, która ma 100 metod, i aby wykonać określone zadanie, musisz wykonać szereg inicjalizacji, łączenia, otwierania / zamykania rzeczy, aby w końcu móc robić to, co chciałeś, a wszystko czego chciałeś to 1 cecha wszystkie 50 bibliotek może to zrobić, więc tworzysz fasadę, która ma tylko jedną metodę, której potrzebujesz, i która wykonuje całą inicjalizację, czyszczenie itp.
Warstwa antykorupcyjna
Jeśli masz system spoza domeny, a potrzeby Twojej firmy wymagają współpracy z tą inną domeną. Nie chcesz wprowadzać tej drugiej domeny do swojej, a zatem ją korumpować, więc przetłumaczysz pojęcie swojej domeny na inną domenę i odwrotnie.
Przykład:
Jeden system wyświetla klienta z nazwą i listą ciągów, po jednym dla każdej transakcji. Profile są postrzegane jako samodzielne klasy o nazwie, a Transakcje - jako samodzielne klasy zawierające ciąg znaków, a Klient - jako profil i zbiór Transakcji.
Tworzysz więc warstwę ACL, która pozwoli tłumaczyć między klientem a klientem innego systemu. W ten sposób nigdy nie musisz korzystać z klienta innego systemu, musisz po prostu powiedzieć ACL: „daj mi klienta z profilem X, a ACL każe drugiemu systemowi dać klientowi o nazwie X.name i zwraca jesteś klientem o profilu X.
====================
Wszystkie trzy są względnie podobne, ponieważ wszystkie są wzorcami pośrednimi. Ale dotyczą one różnych struktur, klas / obiektów w porównaniu do interfejsów API w porównaniu do modułów / podsystemów. Możesz połączyć je wszystkie, jeśli zajdzie taka potrzeba. Podsystem ma złożony interfejs API, więc budujesz dla niego FACADE, używa on innego modelu, więc dla każdej reprezentacji danych, która nie pasuje do twojego modelu, przetłumaczysz te dane z powrotem na sposób, w jaki je modelujesz. Wreszcie, być może interfejsy są również niekompatybilne, więc użyłbyś ADAPTERÓW, aby dostosować się od jednego do drugiego.
źródło
Wiele odpowiedzi tutaj mówi, że listy ACL „nie tylko” dotyczą zawijania niechlujnego kodu. Chciałbym pójść dalej i powiedzieć, że wcale tak nie jest, a jeśli tak, to jest to dodatkowa korzyść.
Warstwa antykorupcyjna polega na mapowaniu jednej domeny na drugą, aby usługi korzystające z drugiej domeny nie musiały być „zepsute” przez koncepcje z pierwszej. Listy ACL służą do modelowania domen, czym są adaptery do klas, dzieje się to po prostu na innym poziomie. Adapter jest prawdopodobnie najważniejszym wzorcem projektowym - używam go cały czas - ale ocenianie owiniętej klasy jako bałaganu lub nie jest nieistotne. Tak właśnie jest, potrzebuję tylko innego interfejsu.
Koncentrowanie się na bałaganie jest mylące i nie rozumie, o co chodzi w DDD. Listy ACL dotyczą radzenia sobie z niedopasowaniami pojęciowymi, a nie złej jakości.
źródło