adapter-vga-scart-hama-tp_7060507946481607330f-01Na co dzień spotykasz różnego rodzaju adaptery. Laptop z USA podłączony do prądu w Polsce potrzebuje adaptera. Zobacz, że idea wzorca adaptera w programowaniu jest bardzo podobna.

Przykład z życia wzięty</h2>
Pamiętasz telefony Sony Ericsson? Ten producent jak i kilku innych do ładowania i podłączenia różnych akcesoriów korzystało z jednego gniazda. Nie będziemy teraz mówili o słuszności ani też o wszystkich możliwych problemach z tego wynikających. Jednak pewnie nie tylko mnie strasznie irytowała sytuacja w której popsuły się oryginalne słuchawki. Nie można było od tak po prostu iść do sklepu i kupić standardowych słuchawek.

Dlaczego ?</h3>
pol_pl_Sluchawki-Sony-Ericsson-K550i-K750i-K800i-C702-C902-C905-K850i-W890i-W995-HPM-61-521_5

Dlatego! Słuchawki nie były podłączane za pomocą standardowej wtyczki jack 3,5 mm ale przez uniwersalne szerokie gniazdo charakterystyczne dla tego producenta.

Było oczywiście na to rozwiązanie. Można było dokupić za kila złotych adapter, który wyglądał tak:

adapter-sluchawkowy-sony_415

Genialne! Prawda?!</strong>

Jednak nie ma tu żadnej magii, trzy styki w tej szerokiej wtyczce były wyjściem audio, wystarczyło je odpowiednio połączyć z wtykiem jack 3,5 mm i voila.

FBP6MRFH0KL7OM1.LARGE

Adapter w programowaniu obiektowym pełni podobną rolę. Jest przejściówką pomiędzy jednym interfejsem a drugim.

Jak to wygląda w praktyce</h2>
Posiadamy portal internetowy z ogłoszeniami samochodowymi. Większość z naszych ogłoszeń wstawiana jest przez detalicznych klientów, którzy do wprowadzania ogłoszeń korzystają z standardowego formularza.

Pojawiła się okazja podjąć współpracę z dużą siecią komisów samochodowych. Chcą zamieszczać swojego ogłoszenia na naszej stronie internetowej. Będą zamieszczać kilkanaście ogłoszeń dziennie. Zamiast korzystać z formularza na stronie chcieliby przesyłań nam pliki JSON generowane przez ich wewnętrzną aplikację. Pojawia się kolejny model wprowadza danych, który nie jest zgodny z  naszym dotychczasowym

Przykładowa implementacja</h2>
Na początek kod bazowy przed modyfikacjami.

Model bazodanowy ogłoszenia.

[github file = "/sebcza/adapter-pattern-sample/blob/68eea4002b3a57fdebbef9a1abd0ad75f590f01f/SampleAdapter/Model/Advert.cs"]

AdvertViewModel, reprezentujący dane pochodzące z formularza. W zasadzie jest to obiekt DTO z modelu bazodanowego.

[github file="/sebcza/adapter-pattern-sample/blob/68eea4002b3a57fdebbef9a1abd0ad75f590f01f/SampleAdapter/ViewModel/AdvertViewModel.cs"]

AdvertService to kolejna klasa. Tu ma się dziać cała logika. Nie podoba mi się, że realizowane jest tutaj bezpośrednio mapowanie. Przez to nie jest zachowana zasada single responsibility principle. W projekcie bardziej komercyjnym do mapowania obiektów użyłbym automappera. Przydało by również wyeliminować ten problem.

[github file="/sebcza/adapter-pattern-sample/blob/68eea4002b3a57fdebbef9a1abd0ad75f590f01f/SampleAdapter/Service/AdvertService.cs"]

Na koniec jeszcze program konsolowy, który przetestuje nasze klasy.

[github file="/sebcza/adapter-pattern-sample/blob/68eea4002b3a57fdebbef9a1abd0ad75f590f01f/SampleAdapter/Program.cs"]

Spróbujmy coś z tym zrobić. Chcemy dodawać ogłoszenia przychodzące w formacie JSON od naszego partnera. Gdybym chciał zrobić to na szybko, to wyglądało by to tak:

Na początek korzystając z jakiego narzędzia online, z przykładowego JSONa, wygenerowałbym model (np. json2csharp.com)

[github file="/sebcza/adapter-pattern-sample/blob/02b795c22351b87384eebddbea6f70f295633c75/SampleAdapter/JsonModel/AdvertCompanyNameJson.cs"]

Potem jedna dodatkowa metoda do AdvertService

[github file="/sebcza/adapter-pattern-sample/blob/02b795c22351b87384eebddbea6f70f295633c75/SampleAdapter/Service/AdvertService.cs"]

I gotowe! Commit i do domu!

Nie tak szybko!</strong>

Na pewno liczysz na to, że twoja aplikacja będzie się rozwijać. Masz pierwszego partnera. A co się stanie jeżeli zaczniesz mieć ich więcej?  W tym prostym przypadku interfejs różnił się nieznacznie, więc implementacja i konwersja nie zajęły zbyt dużo miejsca ani czasu. Dodaj jeszcze kilku partnerów i metod ich obsługujących  i będzie można się zgubić w AdvertService. Metody te będą robiły to samo. O ile przeciążanie metod będzie się sprawdzać do kilku dodatkowych metod wprowadzania danych, o tyle nie sprawdzi się przy chociażby 20 partnerach. Nie wspomnę już o single responsibility principle, którego nadal brakuje. Aby zaradzić naszym  problemom zaimplementujmy wzorzec adapter.

Tak się złożyło, że w moim prostym przykładzie docelowym interfejsem jest model bazodanowy. Ale nie musi tak być.

Po jednym adapterze na każdy z sposobów wprowadzania ogłoszeń do systemu.

[github file="/sebcza/adapter-pattern-sample/blob/master/SampleAdapter/Adapter/AdvertAdapterForm.cs"]

[github file="/sebcza/adapter-pattern-sample/blob/master/SampleAdapter/Adapter/AdvertAdapterCompanyName.cs"]

Mała modyfikacja klasy AdvertService

[github file="/sebcza/adapter-pattern-sample/blob/master/SampleAdapter/Service/AdvertService.cs"]

Na koniec mały pokaz użycia.

[github file="/sebcza/adapter-pattern-sample/blob/master/SampleAdapter/Program.cs"]

Taka implementacja zapewnia nam single responsibility principle oraz daje pewność, że dodawanie kolejnych sposobów na wprowadzanie ogłoszeń nie spowoduje "spuchnięcia" klasy AdvertService.

Nie ukrywam, że zaprezentowany przykład jest dość prosty. Adapter to nie tylko dopasowanie danych wejściowych.

Kiedy przydaje się Adapter?</h3>
Załóżmy, że pracujemy nad aplikacją, której jedną z funkcjonalności jest wysyłanie maili. Wykorzystujemy bibliotekę mail.dll. Podczas audytu bezpieczeństwa okazało się, że biblioteka ta posiada poważny błąd związany z bezpieczeństwem. W całym projekcie jest wiele miejsc, gdzie korzystaliśmy z tej biblioteki a okazuje się, że musimy ją wymienić na mailX.dll .

Ten problem możemy rozwiązać na dwa sposoby:
- Żmudna modyfikacji całej aplikacji i wymiana biblioteki
lub
- Napisanie adaptera, który sprawi, że mailX.dll będzie zachowywał się tak jak mail.dll.

Wszystkie przykłady użyte w tym artykule dostępne są na githube.com