JavaRush /Blog Java /Random-PL /Adapter wzorca projektowego

Adapter wzorca projektowego

Opublikowano w grupie Random-PL
Cześć! Dzisiaj poruszymy nowy ważny temat – wzorce, czyli inaczej – wzorce projektowe . Czym są wzorce? Myślę, że znasz wyrażenie „nie wymyślaj koła na nowo”. W programowaniu, podobnie jak w wielu innych dziedzinach, występuje duża liczba typowych sytuacji. Dla każdego z nich w procesie tworzenia oprogramowania stworzono gotowe działające rozwiązania. To są wzorce projektowe. Relatywnie rzecz biorąc, wzorzec jest pewnym przykładem oferującym rozwiązanie sytuacji typu: „jeśli twój program musi coś zrobić, jak najlepiej to zrobić”. Wzorców jest mnóstwo, poświęcona jest im znakomita książka „Studying Design Patterns”, z którą zdecydowanie warto się zapoznać. Wzór projektowy „Adapter” - 2Najprościej mówiąc, wzór składa się ze wspólnego problemu i jego rozwiązania, co można już uznać za swego rodzaju standard. Na dzisiejszym wykładzie zapoznamy się z jednym z takich wzorców zwanym „Adapterem”. Jego nazwa jest wymowna i nie raz zetknąłeś się z adapterami w prawdziwym życiu. Jednym z najpopularniejszych adapterów są czytniki kart, które są wyposażone w wiele komputerów i laptopów. Wzór projektowy „Adapter” - 3Wyobraźmy sobie, że mamy jakąś kartę pamięci. Jaki jest problem? Faktem jest, że nie wie, jak współdziałać z komputerem. Nie mają wspólnego interfejsu. Komputer ma złącze USB, ale nie można do niego włożyć karty pamięci. Karty nie można włożyć do komputera, przez co nie będziemy mogli 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 włożyć do komputera. Posiadają wspólny interfejs z komputerem - USB. Zobaczmy, jak by to wyglądało na przykładzie:
public interface USB {

   void connectWithUsbCable();
}
To jest nasz interfejs USB, którego jedyną metodą jest podłączenie kabla USB:
public class MemoryCard {

   public void insert() {
       System.out.println("Карта памяти успешно вставлена!");
   }

   public void copyData() {
       System.out.println("Данные скопированы на компьютер!");
   }
}
To jest nasza klasa, która implementuje mapę pamięci. Ma już 2 metody, których potrzebujemy, ale tutaj jest problem: nie implementuje interfejsu USB. Karty nie można włożyć do gniazda 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 robi ta klasaCardReader i dlaczego jest adapterem? To proste. Dopasowana klasa (mapa pamięci) staje się jednym z pól adaptera. Jest to logiczne, ponieważ w prawdziwym życiu również wkładamy kartę do czytnika kart i on również staje się jego częścią. W odróżnieniu od karty pamięci, adapter posiada wspólny interfejs z komputerem. Posiada kabel USB, co oznacza, że ​​można go podłączyć do innych urządzeń poprzez USB. Dlatego w programie nasza klasa CardReaderimplementuje interfejs USB. Ale co dzieje się w tej metodzie? I tam dzieje się dokładnie to, czego potrzebujemy! Adapter deleguje pracę naszej karcie pamięci. Przecież sam adapter nic nie robi, czytnik kart nie ma żadnej niezależnej funkcjonalności. Jego zadaniem jest jedynie połączenie komputera i karty pamięci, aby karta mogła wykonać swoją pracę i kopiować pliki! Nasz adapter pozwala na to poprzez udostępnienie własnego interfejsu (metody connectWithUsbCable()) na „potrzeby” karty pamięci. Stwórzmy jakiś program kliencki, który będzie symulował osobę chcącą skopiować dane z karty pamięci:
public class Main {

   public static void main(String[] args) {

       USB cardReader = new CardReader(new MemoryCard());
       cardReader.connectWithUsbCable();

   }
}
Co w rezultacie otrzymaliśmy? Wyjście konsoli:
Карта памяти успешно вставлена!
Данные скопированы на компьютер!
Świetnie, nasze zadanie zostało pomyślnie zakończone! Oto kilka dodatkowych łączy z informacjami o wzorcu Adaptera:

Klasy abstrakcyjne Reader i Writer

Teraz powrócimy do naszego ulubionego zajęcia: nauczymy się kilku nowych klas do pracy z danymi wejściowymi i wyjściowymi :) Ciekawe, ilu z nich już się nauczyliśmy? Dziś porozmawiamy o zajęciach Readeri Writer. Dlaczego o nich? Ponieważ będzie to powiązane z naszą poprzednią sekcją - adaptery. Przyjrzyjmy się im bardziej szczegółowo. Zacznijmy od Reader„a. Readerjest klasą abstrakcyjną, więc nie będziemy mogli jawnie utworzyć jej obiektów. Ale tak naprawdę już go znasz! W końcu klasy, które dobrze znasz, BufferedReaderInputStreamReaderjego spadkobiercami :)
public class BufferedReader extends Reader {}

public class InputStreamReader extends Reader {}
Zatem klasa InputStreamReaderjest klasycznym adapterem . Jak zapewne pamiętasz, możemy przekazać obiekt jego konstruktorowi InputStream. Najczęściej używamy do tego zmiennej System.in:
public static void main(String[] args) {

   InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
Co to robi InputStreamReader? Jak każdy adapter, konwertuje jeden interfejs na inny. W tym przypadku interfejs InputStream'a do interfejsu Reader'a. Na początku mieliśmy zajęcia InputStream. Działa dobrze, ale może czytać tylko pojedyncze bajty. Dodatkowo mamy klasę abstrakcyjną Reader. Ma doskonałą funkcjonalność, której naprawdę potrzebujemy - potrafi czytać znaki! Oczywiście bardzo potrzebujemy tej możliwości. Ale tutaj mamy do czynienia z klasycznym problemem, który zwykle rozwiązują adaptery - niekompatybilnością interfejsów. Jak się to objawia? Spójrzmy bezpośrednio na dokumentację Oracle. Oto metody klas InputStream. Wzór projektowy „Adapter” - 4Zestaw metod to interfejs. Jak widać, read()ta klasa ma metodę (nawet w kilku wersjach), ale może czytać tylko bajty: albo pojedyncze bajty, albo kilka bajtów przy użyciu bufora. Ta opcja nam nie odpowiada – chcemy czytać znaki. Funkcjonalność, której potrzebujemy, jest już zaimplementowana w klasie abstrakcyjnejReader . Można to również zobaczyć w dokumentacji. Wzór konstrukcyjny adaptera – 5Jednakże interfejsy InputStream„a” i Reader„a” są niekompatybilne! Jak widać we wszystkich implementacjach metod read()różnią się zarówno przekazywane parametry, jak i zwracane wartości. I tu tego potrzebujemy InputStreamReader! Będzie pełnił rolę pośrednika pomiędzy naszymi klasami. Podobnie jak w przykładzie z czytnikiem kart, który omówiliśmy powyżej, obiekt klasy „dostosowanej” przekazujemy „wewnętrznie”, czyli konstruktorowi klasy adaptera. W poprzednim przykładzie przekazaliśmy obiekt MemoryCardwewnątrz CardReader. Teraz przekazujemy obiekt InputStreamkonstruktorowi InputStreamReader! Jako jakość InputStreamużywamy znanej już zmiennej System.in:
public static void main(String[] args) {

   InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
I rzeczywiście: przeglądając dokumentację InputStreamReaderprzekonamy się, że „adaptacja” przebiegła pomyślnie :) Teraz mamy do dyspozycji metody, które pozwalają nam czytać znaki. Wzór konstrukcyjny adaptera – 6I choć początkowo nasz obiekt System.in(wątek przywiązany do klawiatury) na to nie pozwalał, tworząc wzorzec Adaptera , twórcy języka rozwiązali ten problem. Klasa abstrakcyjna Reader, jak większość klas we/wy, ma brata bliźniaka - Writer. Ma tę samą dużą zaletę Reader- zapewnia wygodny interfejs do pracy z symbolami. W przypadku strumieni wyjściowych problem i jego rozwiązanie wyglądają tak samo jak w przypadku strumieni wejściowych. Istnieje klasa OutputStream, która może zapisywać tylko bajty; Istnieje klasa abstrakcyjna Writer, która może pracować z symbolami i istnieją dwa niezgodne interfejsy. Problem ten został ponownie pomyślnie rozwiązany przez wzorzec adaptera. Za pomocą klasy OutputStreamWritermożemy łatwo „dopasować” Writerdo OutputStreamsiebie interfejsy dwóch klas. A otrzymawszy OutputStreamw konstruktorze strumień bajtów, za jego pomocą OutputStreamWritermożemy jednak zapisywać znaki, a nie bajty!
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();
   }
}
Do naszego pliku wpisaliśmy znak o kodzie 32144 - 綐, eliminując w ten sposób potrzebę pracy z bajtami :) To wszystko na dziś, do zobaczenia na kolejnych wykładach! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION