Myślenie warstwowe

Ten wpis jest częścią mojego starego bloga, prowadzonego w latach 2005-2007 pod adresem dragonee.jogger.pl. Został on zachowany w celach archiwizacyjnych i niekoniecznie reprezentuje moje bieżące stanowisko na dany temat.

We wpisie “Programistyczna kanapka, czyli wprowadzenie do warstw abstrakcji” wymieniłem możliwości płynące z projektowania warstwowego. Dziś skupię się na metodach, dzięki którym łatwo będzie nam tworzyć nowe projekty w tym trybie.

Wyobraź sobie warstwę

Warstwa abstrakcji charakteryzuje się tym, że nie zależy od innego kodu. W wolnym tłumaczeniu oznacza to mniej więcej tyle, że możesz wydzielić ją spośród kodu i korzystać z jej możliwości w innym środowisku.

Doskonałym przykładem na zobrazowanie powyższego jest system operacyjny. Jeżeli potraktować go jako pojedynczą, wielką warstwę abstrakcji, daje nam ogromną zaletę – nie musimy zastanawiać się, w jaki sposób zapisuje się plik na dysku, a jak w pamięci USB Flash, aby go zapisać. Jednocześnie projektant aplikacji nie musi obsługiwać procesora ręcznie, aby napisać kolejny edytor tekstu. A jeśli chcemy posłuchać muzyki, nie patrzymy na zapis nutowy piosenki i nie próbujemy zagrać go na instrumencie (mimo to polecam), a wrzucamy mptrójkę na listę odtwarzania i nie martwimy się, że ta mptrójka nie wie, jaki typ karty dźwiękowej posiadamy.

Komputer składa się ze sprzętu, zer i jedynek. Dziś jednak odnotowuję, że moja klawiatura nie ma dwóch klawiszy. A przecież tyle by wystarczyło, aby komunikować się z komputerem. Więc po co ich tyle?

Aby upraszczać. Tak samo, jak komunikacja za pomocą zer i jedynek była dla ówczesnych naukowców bardzo niewygodna, tak dzisiejsza klawiatura łacińska i zestaw klawiszy pozwala mi pisać ten tekst, abym nie potrzebował sprawdzać, jak prezentuje się poszczególna litera w zapisie bitowym. Czyli jakiś miły i mądry człowiek wprowadził zamianę liter na bity (a więc i nową warstwę abstrakcji), abym ja nie musiał się męczyć, używając dwóch klawiszy.

Podmień swą kanapkę na realny problem

Problem pierwszy – zapis danych

Każdy program od czegoś się zaczyna. Tym czymś w prawie każdym przypadku jest wczytanie danych. Wczytanie danych różni się w zależności od tego, skąd je wczytujemy i jak je chcemy wykorzystać. Do tego dochodzi ich zapis – w jakiś sposób chcemy przecież odzyskać przetworzone dane z naszego programu.

Przypuśćmy, że operujemy jednym, prostym typem danych, pochodzącym z kilku źródeł. Źródłem może być najzwyczajniejszy plik, baza danych, czy odległy serwer, z którym komunikujemy się poprzez sieć. Wszystkie te źródła wykazują jedną cechę wspólną. Nadal zapisywane do nich i odczytywane z nich są dane tego samego rodzaju.

Jedno z rozwiązań pozwala wbudować obsługę wszystkiego w główny kod, ale co, jeśli zapragniemy wykonywać podprogram przekazując mu te dane? Nagle okaże się, że wprowadzenie wszystkich potrzebnych zmian stanie się dość niewygodne. Nie dość, że będziemy musieli dodać naszą funkcjonalność w kilku miejscach, to na dodatek będziemy poprawiać wszystkie funkcje sprawdzające, aby uznały nową metodę za poprawną.

Spróbujmy inaczej. Dla każdego ze źródeł zdefiniujmy ten sam zestaw metod. W naszym przykładzie będzie to: zapisz i wczytaj. Zapisowi przekażemy porcję danych, od operacji wczytania będziemy wymagać porcji danych. Teraz stworzymy funkcję, która będzie odpowiedzialna za pobieranie wszystkich danych, niezależnie od źródła. W niej to odniesiemy się do poszczególnych “wczytaj”. Zwracać będzie całkowite dane gotowe do przetwarzania. Dla globalnej funkcji zapisania podamy przetworzone dane z listą celów, do których powinny się one udać. Funkcja ta będzie odwoływać się do odpowiedniej podfunkcji “zapisz”, zależnie od celu.

W tym wypadku część przetwarzająca nie będzie musiała zastanawiać się, jak zapisać dane, a całość kodu odpowiedzialnego za wczytywanie i zapis danych wewnątrz niej skróci się do oczekiwanych kilku linijek. Dodanie nowej funkcjonalności nie będzie dla nas trudne, a jeśli w kodzie warstwy abstrakcji stworzymy wykrywacz nowych funkcjonalności, to i tego kodu nie będziemy musieli zmieniać.

Problem drugi – interfejs

Dobrym pomysłem, aby przeprowadzić szybkie testy na nowej funkcjonalności jest usunięcie jak największej ilości niepotrzebnych bajerów i uruchomienie jej w minimalnej konfiguracji. Wtedy jesteśmy w stanie precyzyjniej określić problem i rozprawić się z nim szybciej.

Inaczej – tworzymy narzędzie X. Narzędzie X jest domyślnie w trybie okienkowym, bo tak ładniej i klikalniej. Po pewnym czasie potrzebujemy wersji konsolowej, bo możemy odpalić X w skrypcie i ustawić automatyczne uruchamianie co jakiś czas.

Jeszcze inaczej – narzędzie tworzymy pod biblioteką graficzną X, która nie jest dostępna na systemie Y, na którym pracuje nasz przyjaciel i właśnie pisze, że bardzo przydałoby mu się nasze narzędzie…

Rozwiązaniem na to jest użycie warstwy abstrakcji. Jądro aplikacji przestanie się martwić o wyświetlanie okienka, a Ty zbudujesz tyle interfejsów, ile zechcesz.

Inne przykłady

Używanie pluginów łączy się z warstwami abstrakcji – każdy z nich może zwiększyć możliwości naszego programu, a wszystkie korzystają z jednego zestawu funkcji.

Korzystanie z kilku podobnych struktur (na przykład kolejki i stosu) można łatwo uprościć tworząc odpowiednie funkcje, które będą pobierać obiekty ze struktur niezależnie od tego, o którą poprosisz.

Rozszerzanie działania aktualnie istniejących obiektów. Taka niedokładna, nieprzemyślana, lub błędna (#) alokacja pamięci w języku C często kończy się wyciekami pamięci, czy, w gorszym przypadku podwójnym zwolnieniem, lub używaniem niezaalokowanej pamięci. Zestaw funkcji, które sprawdzałyby te problemy, w mniejszym, czy w większym stopniu zabezpieczyłyby program, szczególnie ten w wersji rozwojowej.

Wniosek

Zakładając, że dany kod będziemy (my lub inni) rozwijać, warto jest wykorzystać ideę warstwy abstrakcji. Jeśli nie przydadzą się one nam, to osobom, która będą korzystać z naszego programu kiedyś.

Tags:
3 komentarze
  1. Grudzień 6, 2006
  2. Grudzień 6, 2006
  3. Grudzień 7, 2006

Leave a Reply

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *