CodeGym /Blog Java /Random-PL /Wzorzec projektowy adaptera
Autor
Pavlo Plynko
Java Developer at CodeGym

Wzorzec projektowy adaptera

Opublikowano w grupie Random-PL
Cześć! Dzisiaj poruszymy ważny nowy temat: wzorce projektowe . Jakie są te wzorce? Myślę, że musisz znać wyrażenie „ nie wymyślaj koła na nowo ”. W programowaniu, podobnie jak w wielu innych dziedzinach, istnieje duża liczba typowych sytuacji. Wraz z rozwojem oprogramowania stworzono gotowe rozwiązania, które działają dla każdego z nich. Rozwiązania te nazywane są wzorcami projektowymi. Zgodnie z konwencją wzorzec to rozwiązanie sformułowane w następujący sposób: „jeśli musisz zrobić X w swoim programie, to jest to najlepszy sposób, aby to zrobić”. Istnieje wiele wzorów. Im dedykowana jest znakomita książka „Head First Design Patterns”, z którą zdecydowanie warto się zapoznać. Wzorzec projektowy adaptera - 2Mówiąc zwięźle, wzorzec składa się ze wspólnego problemu i odpowiadającego mu rozwiązania, które można uznać za rodzaj standardu. W dzisiejszej lekcji poznamy jeden z tych wzorców: Adapter. Jego nazwa mówi sama za siebie, a adaptery napotkałeś wiele razy w prawdziwym życiu. Niektóre z najczęstszych adapterów to czytniki kart, które są dostępne w wielu komputerach i laptopach. Wzorzec projektowy adaptera - 3Załóżmy, że mamy jakąś kartę pamięci. Więc w czym problem? Nie wie, jak wchodzić w interakcje z komputerem. Nie mają wspólnego interfejsu. Komputer ma port USB, ale nie możemy włożyć do niego karty pamięci. Karty nie można podłączyć do komputera, więc nie możemy zapisać naszych zdjęć, filmów i innych danych. Czytnik kart to adapter, który rozwiązuje ten problem. W końcu ma kabel USB! W przeciwieństwie do samej karty, czytnik kart można podłączyć do komputera. Dzielą wspólny interfejs z komputerem: USB. Zobaczmy, jak to wygląda w praktyce:

public interface USB { 

   void connectWithUsbCable(); 
}
To jest nasz interfejs USB z tylko jedną metodą łączenia przez USB.

public class MemoryCard { 

   public void insert() { 
       System.out.println("Memory card successfully inserted!"); 
   } 

   public void copyData() { 
       System.out.println("The data has been copied to the computer!"); 
   } 
}
To jest nasza klasa reprezentująca kartę pamięci. Ma już 2 metody, których potrzebujemy, ale oto problem: nie implementuje interfejsu USB. Nie można włożyć karty do portu USB.

public class CardReader implements USB { 

   private MemoryCard memoryCard; 

   public CardReader(MemoryCard memoryCard) { 
       this.memoryCard = memoryCard; 
   } 

   @Override 
   public void connectWithUsbCable() { 
       this.memoryCard.insert(); 
       this.memoryCard.copyData(); 
   } 
}
A oto nasz adapter! Co znaczyCardReaderclass zrobić i co dokładnie czyni go adapterem? To wszystko jest proste. Adaptowana klasa (MemoryCard) staje się jednym z pól adaptera. To ma sens. Kiedy w prawdziwym życiu umieszczamy kartę pamięci w czytniku kart, staje się ona również jego częścią. W przeciwieństwie do karty pamięci adapter współdzieli interfejs z komputerem. Posiada kabel USB, czyli można go podłączyć do innych urządzeń przez USB. Dlatego nasza klasa CardReader implementuje interfejs USB. Ale co dokładnie dzieje się w tej metodzie? Dokładnie to, co musi się wydarzyć! Adapter deleguje pracę na naszą kartę pamięci. Rzeczywiście, adapter sam nic nie robi. Czytnik kart nie posiada żadnej niezależnej funkcjonalności. Jego zadaniem jest jedynie połączenie komputera z kartą pamięci, aby karta mogła wykonać swoją pracę — kopiowanie plików!connectWithUsbCable()metoda), aby spełnić „potrzeby” karty pamięci. Stwórzmy program kliencki, który będzie symulował osobę, która chce skopiować dane z karty pamięci:

public class Main { 

   public static void main(String[] args) { 

       USB cardReader = new CardReader(new MemoryCard()); 
       cardReader.connectWithUsbCable(); 
   } 
}
Więc co dostaliśmy? Wyjście konsoli:

Memory card successfully inserted! 
The data has been copied to the computer!
Doskonały. Osiągnęliśmy nasz cel! Oto link do filmu z informacjami o wzorze adaptera:

Klasy abstrakcyjne Reader i Writer

Teraz wrócimy do naszej ulubionej czynności: poznawania kilku nowych klas do pracy z danymi wejściowymi i wyjściowymi :) Ciekawe, o ilu już się nauczyliśmy. Dzisiaj porozmawiamy o klasach Reader i Writer. Dlaczego akurat te zajęcia? Ponieważ są one powiązane z naszą poprzednią sekcją dotyczącą adapterów. Przeanalizujmy je bardziej szczegółowo. Zaczniemy od  Reader. Readerjest klasą abstrakcyjną, więc nie będziemy mogli jawnie tworzyć obiektów.   Ale tak naprawdę już go znasz! W końcu dobrze znasz klasy BufferedReaderi InputStreamReader, które są jej potomkami :)

public class BufferedReader extends Reader { 
… 
} 

public class InputStreamReader extends Reader { 
… 
}
Klasa InputStreamReaderjest klasycznym adapterem. Jak zapewne pamiętasz, możemy przekazać InputStreamobiekt do jego konstruktora. W tym celu zwykle używamy System.inzmiennej:

public static void main(String[] args) { 

   InputStreamReader inputStreamReader = new InputStreamReader(System.in); 
}
Ale co InputStreamReaderrobi? Jak każdy adapter, konwertuje jeden interfejs na inny.  W tym przypadku InputStreaminterfejs do Readerinterfejsu. Na początek mamy InputStreamklasę. Działa dobrze, ale można go używać tylko do odczytu pojedynczych bajtów. Ponadto mamy Readerklasę abstrakcyjną. Ma kilka bardzo przydatnych funkcji — wie, jak czytać znaki! Z pewnością potrzebujemy tej umiejętności. Ale tutaj mamy do czynienia z klasycznym problemem zwykle rozwiązywanym przez adaptery — niekompatybilnymi interfejsami. Co to znaczy? Rzućmy okiem na dokumentację Oracle. Oto metody klasy InputStream. Wzorzec projektowy adaptera - 4Zestaw metod jest dokładnie tym, czym jest interfejs. Jak widać, ta klasa maread()metoda (w rzeczywistości kilka wariantów), ale może odczytywać tylko bajty: albo pojedyncze bajty, albo kilka bajtów przy użyciu bufora. Ale ta opcja nam nie odpowiada — chcemy czytać znaki. Potrzebujemy funkcjonalności, która jest już zaimplementowana w Readerklasie abstrakcyjnej . Widzimy to również w dokumentacji. Wzorzec projektowy adaptera - 5Jednak interfejsy InputStreami  Readersą niekompatybilne! Jak widać, każda implementacja metody read()ma inne parametry i zwracane wartości. I tutaj potrzebujemy InputStreamReader! Będzie pełnić rolę adaptera między naszymi klasami. Podobnie jak w przykładzie z czytnikiem kart, który rozważaliśmy powyżej, instancję adaptowanej klasy umieszczamy „wewnątrz” klasy adaptera, czyli przekazujemy ją jej konstruktorowi. W poprzednim przykładzie umieściliśmy MemoryCardobiekt wewnątrz CardReader. Teraz przekazujemy InputStream obiekt do InputStreamReaderkonstruktora! Używamy naszej znanej System.inzmiennej jako InputStream:

public static void main(String[] args) { 

   InputStreamReader inputStreamReader = new InputStreamReader(System.in); 
}
I rzeczywiście, patrząc na dokumentację do InputStreamReader, widzimy, że adaptacja się powiodła :) Teraz mamy do dyspozycji metody odczytywania znaków. Wzorzec projektowy adaptera - 6I choć nasz System.inobiekt (strumień powiązany z klawiaturą) początkowo na to nie pozwalał, twórcy języka rozwiązali ten problem, implementując wzorzec adaptera. Klasa Readerabstrakcyjna, podobnie jak większość klas I/O, ma brata bliźniaka —  Writer. Ma tę samą dużą zaletę, co  Reader — zapewnia wygodny interfejs do pracy z postaciami. W przypadku strumieni wyjściowych problem i jego rozwiązanie wyglądają tak samo jak w przypadku strumieni wejściowych. Jest OutputStreamklasa, która może zapisywać tylko bajty, jest aWriterKlasa abstrakcyjna, która wie, jak pracować ze znakami, i istnieją dwa niekompatybilne interfejsy. Ten problem ponownie rozwiązuje wzorzec adaptera. Używamy klasy, aby łatwo dostosować  do siebie OutputStreamWriterdwa interfejsy klas Writer i  . OutputStreamPo przekazaniu OutputStreamstrumienia bajtów do konstruktora, możemy użyć an OutputStreamWriterdo zapisywania znaków, a nie bajtów!

import java.io.*; 

public class Main { 

   public static void main(String[] args) throws IOException { 

       OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt")); 
       streamWriter.write(32144); 
       streamWriter.close();
   } 
}
Wpisaliśmy znak o kodzie 32144 (綐) do naszego pliku, eliminując konieczność pracy z bajtami :) To tyle na dziś. Do zobaczenia na kolejnych lekcjach! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION