Pytania rekrutacyjne Wzorce architektoniczne
47 pytań — 20 odpowiedzi dostępnych od razu
Architektura warstwowa
Czym jest architektura warstwowa i jakie są trzy podstawowe warstwy aplikacji biznesowej?
Architektura warstwowa to technika dekompozycji systemu na poziomy ułożone jeden nad drugim, w której każda warstwa korzysta z usług warstwy znajdującej się bezpośrednio pod nią. Trzy podstawowe warstwy aplikacji biznesowej to:
- Prezentacja (presentation) — obsługa interakcji ze światem zewnętrznym: wyświetlanie informacji i tłumaczenie poleceń użytkownika (kliknięć, żądań HTTP) na operacje wykonywane niżej.
- Logika domenowa (domain / business logic) — właściwa „praca" aplikacji: obliczenia, walidacje, reguły biznesowe oraz decydowanie, jakie operacje na danych uruchomić.
- Źródło danych (data source) — komunikacja z bazą, kolejkami komunikatów i innymi systemami; najczęściej trwałe przechowywanie danych.
flowchart TD
P["Prezentacja"] --> D["Logika domenowa"]
D --> S["Źródło danych"]
S --> DB[("Baza danych")]
Warto odróżnić warstwę (layer) od tier — „tier" sugeruje rozdział fizyczny na osobne maszyny, podczas gdy warstwy to podział logiczny: wszystkie trzy mogą działać w jednym procesie na jednym komputerze.
Dziś odpowiada to typowemu backendowi: prezentacja to kontrolery REST/GraphQL/MVC, logika domenowa to serwisy i model domenowy, a źródło danych to repozytoria i ORM.
Jaka zasada rządzi kierunkiem zależności między warstwami i dlaczego jest tak ważna?
Zależności biegną tylko w jedną stronę — w dół. Warstwa wyższa zna i wywołuje niższą, ale warstwa niższa nigdy nie może zależeć od wyższej. W szczególności logika domenowa i źródło danych nie mogą odwoływać się do kodu prezentacji.
Dlaczego to kluczowe:
- pozwala podmienić lub dodać prezentację (web, mobilna, CLI, API) na tym samym fundamencie,
- zmiany w prezentacji nie pociągają zmian w głębszych warstwach,
- każdą warstwę można rozumieć i testować w izolacji.
W praktyce warstwy bywają „nieprzezroczyste" (warstwa 4 zna 3, ale nie sięga wprost do 2). Dopuszczalnym kompromisem jest sytuacja, gdy prezentacja pobiera dane bezpośrednio przez źródło danych, a logika domenowa dopiero nimi manipuluje — mniej „czyste", ale często praktyczne.
Dziś ta zasada jest rdzeniem Dependency Inversion oraz architektury heksagonalnej (porty i adaptery): domena nie zależy od frameworka ani od warstwy I/O.
Jakie są zalety i wady dzielenia aplikacji na warstwy?
| Zalety | Wady |
|---|---|
| Każdą warstwę można zrozumieć osobno | Zmiany kaskadowe — dodanie pola wymaga zmian w każdej warstwie |
| Wymienialność implementacji bez wpływu na resztę | Narzut wydajnościowy (transformacja danych między warstwami) |
| Warstwy to dobre miejsce na standaryzację | Trudna decyzja: jakie warstwy i jaka odpowiedzialność każdej |
| Raz zbudowana warstwa służy wielu warstwom wyższym | Nadmiar warstw potrafi zaciemnić, gdzie naprawdę jest dana logika |
Najtrudniejszą decyzją jest ustalenie, jakie warstwy mieć i za co każda odpowiada. Mimo narzutu enkapsulacja zwykle daje zysk netto — np. dobrze wyizolowana warstwa transakcji potrafi przyspieszyć całość. Formę separacji dobiera się do skali: od podprocedur, przez osobne klasy, po osobne pakiety — ale jakaś separacja powinna być zawsze.
Dziś ten sam kompromis widać w sporze monolit vs warstwy vs mikrousługi — każda dodatkowa granica kosztuje.
Jak rozpoznać, że dany fragment kodu to logika domenowa, a nie prezentacja?
Pomaga nieformalny test „wycieku": wyobraź sobie dodanie radykalnie innej prezentacji (np. interfejsu tekstowego do aplikacji webowej) albo podmianę relacyjnej bazy na plik. Jeśli musiałbyś zduplikować funkcjonalność, to w tym miejscu logika domenowa wyciekła tam, gdzie nie powinna.
Przykład: produkty mają być wyróżnione na czerwono, gdy sprzedaż wzrosła o ponad 10%. Porównanie „ten miesiąc vs poprzedni" to logika domenowa — należy do metody zwracającej wartość logiczną (czy wyróżnić). To, jak wyróżnić (czerwony kolor), to już prezentacja. Rozdzielamy „czy jest coś do pokazania" od „jak to pokazać".
To rozróżnienie nie jest dogmatem — czasem drobny fragment w UI jest rozsądny. Ale świadomość, gdzie przebiega granica, chroni przed duplikacją i ułatwia podmianę warstw.
Dziś to ta sama dyscyplina, która każe trzymać reguły biznesowe poza komponentami widoku i poza zapytaniami SQL.
Gdzie uruchamiać poszczególne warstwy — na kliencie czy na serwerze?
- Źródło danych — praktycznie zawsze na serwerze (wyjątek: kopia na mocnym kliencie dla pracy offline).
- Prezentacja — zależy od typu UI: rich client działa na kliencie, aplikacja webowa na serwerze. W modelu B2C nie ma wyboru — całość na serwerze, klient dostaje HTML (kosztem rundy do serwera przy każdej akcji).
- Logika domenowa — najlepsza dla utrzymania jest w całości na serwerze. Przeniesienie na klienta uzasadnia tylko responsywność lub praca offline. Jeśli część musi być na kliencie, lepiej dać tam całość logiki niż ją dzielić — podział między klient a serwer to zwykle „najgorsze z obu światów" (nie wiadomo, co gdzie jest).
Praktyczna reguła: wdrażaj jako aplikację webową, gdy tylko się da, a po rich client sięgaj, gdy musisz. Cały kod jednego węzła trzymaj w jednym procesie — rozpraszanie, jawna wielowątkowość i niedopasowanie obiektowo-relacyjne to „wzmacniacze złożoności" (complexity boosters) o realnej cenie.
Dziś jest to napięcie SSR/cienki klient kontra SPA z rozbudowaną logiką w przeglądarce oraz ostrożność z dzieleniem aplikacji na osobne procesy.
Organizacja logiki domenowej
Na czym polega wzorzec Transaction Script i kiedy jest dobrym wyborem?
Transaction Script organizuje logikę biznesową proceduralnie: jedna procedura („skrypt") obsługuje pojedyncze żądanie z prezentacji — pobiera dane, waliduje, liczy, zapisuje do bazy i odsyła odpowiedź. Wspólne fragmenty wydziela się do podprocedur. Granice transakcji są oczywiste: początek i koniec skryptu.
Zalety: prostota, znajomy model proceduralny, niski próg wejścia, naturalna współpraca z relacyjną bazą (przez Row Data Gateway lub Table Data Gateway).
Wada: przy rosnącej złożoności logika degeneruje się w duplikację i „splątaną sieć procedur" — trudność rośnie niemal wykładniczo. Dlatego nadaje się do prostych przypadków (np. katalog z koszykiem i bazowym cennikiem).
Dziś to proceduralne, często „anemiczne" warstwy serwisowe — cała logika w metodzie klasy *Service, a encje pełnią rolę nośników danych.
Czym jest Domain Model i jakie wiążą się z nim koszty?
Domain Model to sieć powiązanych obiektów łączących dane i zachowanie, gdzie każdy obiekt odpowiada znaczącemu pojęciu domeny. Logika jest rozproszona na obiekty, które jej dotyczą; zamiast rozbudowywać warunki if/else, dodaje się nowe obiekty-strategie (polimorfizm). Złożoność przenosi się z algorytmów do relacji między obiektami.
Z modelem wiążą się dwa koszty:
- krzywa uczenia — wymaga „przesunięcia paradygmatu"; źle zrobiony model bywa katastrofą,
- złożone mapowanie obiektowo-relacyjne — stąd cały zestaw wzorców O/R.
Mimo to świetnie radzi sobie ze złożoną, często zmieniającą się logiką: po opanowaniu nawet proste problemy robi się łatwo, a koszt rośnie łagodnie wraz ze złożonością.
Dziś to bogate encje JPA/Hibernate z prawdziwą logiką w metodach (nie anemiczne) oraz podejście DDD.
Czym różni się Table Module od Domain Model?
Kluczowa różnica dotyczy ziarnistości:
- Domain Model ma jeden obiekt na rekord i pełne możliwości obiektowe (dziedziczenie, relacje instancja-instancja, strategie).
- Table Module ma jedną instancję klasy na całą tabelę — obsługuje wszystkie wiersze, nie ma tożsamości pojedynczych obiektów, więc operację na konkretnym wierszu wykonuje się, przekazując identyfikator.
Table Module pracuje na strukturze tabelarycznej, najczęściej na Record Set (wynik zapytania imitujący tabelę). To „środek drogi": więcej struktury niż Transaction Script, ale bez pełnej mocy obiektowości. Sprawdza się tam, gdzie całe środowisko kręci się wokół Record Set i ma do niego bogate narzędzia.
Dziś to podejście wokół DataSet/DataTable w .NET (ADO.NET); w nowoczesnej Javie rzadkie.
Jak wybrać między Transaction Script, Table Module a Domain Model?
Decyduje przede wszystkim złożoność logiki domenowej, a w drugiej kolejności dostępne narzędzia. Przy prostej logice Transaction Script i Table Module są tańsze. W miarę wzrostu złożoności oba „uderzają w ścianę" — dokładanie funkcji staje się coraz trudniejsze. Domain Model startuje drożej (krzywa uczenia + mapowanie O/R), ale jego koszt rośnie łagodnie.
flowchart TD
A["Jak złożona jest logika domenowa?"] -->|prosta| B["Transaction Script"]
A -->|umiarkowana, środowisko oparte o Record Set| C["Table Module"]
A -->|złożona, często zmienna| D["Domain Model"]
B -.->|narasta złożoność i duplikacja| D
Złożoności logiki nie da się zmierzyć — decyduje osąd doświadczonych osób. Zespół znający Domain Model obniża próg wejścia; Table Module jest atrakcyjny tylko przy realnym wsparciu Record Set. Wybór nie jest nieodwracalny, ale zmiana jest trudna, więc warto przemyśleć go wcześnie. Wzorce można też mieszać.
Dziś to żywy spór „anemiczny model + grube serwisy" kontra DDD z bogatym modelem domenowym.
Po co stosować Service Layer i jak rozdzielić w nim logikę aplikacyjną od domenowej?
Service Layer definiuje granicę aplikacji — zbiór dostępnych operacji (API) koordynujący odpowiedź na każde żądanie: transakcje, bezpieczeństwo, koordynację wielu zasobów. Nakłada się go na Domain Model lub Table Module; prezentacja rozmawia z domeną wyłącznie przez tę warstwę.
Kluczowe jest rozróżnienie dwóch rodzajów logiki:
- logika domenowa — czysto dziedzinowa (np. reguły obliczania wartości),
- logika aplikacyjna / workflow — odpowiedzialności aplikacji (powiadomienia, publikacja zdarzeń, koordynacja). Nie powinna trafiać do czystych klas domeny, bo obniża ich reużywalność i miesza dwa poziomy.
Dwa warianty implementacji:
- Domain Facade — cienkie fasady bez logiki, jedynie ustanawiające granicę i operacje,
- Operation Script — grubsze klasy realizujące logikę aplikacyjną i delegujące logikę domenową do obiektów.
Service Layer warto wprowadzić, gdy jest więcej niż jeden typ klienta albo gdy operacje obejmują wiele zasobów transakcyjnych.
Dziś to warstwa @Service w Springu z deklaratywnymi transakcjami @Transactional, warstwa aplikacyjna w DDD, a na zewnątrz osobny Remote Facade/API.
Wzorce dostępu do danych
Czym jest niedopasowanie obiektowo-relacyjne (impedance mismatch)?
To fundamentalna rozbieżność między modelem obiektowym a relacyjnym. Obiekty mają referencje, kolekcje w polach i dziedziczenie; relacje mają klucze obce, znormalizowane tabele i nie znają kolekcji ani dziedziczenia. Stąd m.in. odwrócenie kierunku powiązań: w obiektach Order trzyma kolekcję pozycji, a w bazie to wiersz pozycji ma klucz obcy do zamówienia.
Koszt mapowania szacuje się na około jedną trzecią wysiłku programistycznego i ponosi się go przez całe życie aplikacji. Dlatego logikę dostępu (SQL) wydziela się do osobnych klas, oddzielonych od logiki domenowej.
Bazy obiektowe usuwają ten problem, ale nie wyparły relacyjnych — głównie z powodu ryzyka i dojrzałości technologii relacyjnej (standard SQL, sprawdzone narzędzia).
Dziś właśnie z tego powodu istnieją ORM-y (Hibernate/JPA, Entity Framework), które automatyzują mapowanie.
Czym różni się Table Data Gateway od Row Data Gateway?
Oba ukrywają SQL i nie zawierają logiki domenowej, ale różnią się ziarnistością:
- Table Data Gateway — jedna instancja na całą tabelę, zwykle bezstanowa; metody
find/insert/update/delete; wynik zwraca jako Record Set lub DTO. Idealny w parze z Table Module i w środowiskach opartych o Record Set. - Row Data Gateway — jedna instancja na wiersz; wygląda jak rekord (każde pole odpowiada kolumnie); findery najlepiej wydzielić do osobnych klas, by umożliwić podmianę źródła w testach. Typowo używany z Transaction Script.
Granica wobec Active Record jest jasna: gateway zawiera wyłącznie dostęp do danych. Gdy zacznie się w nim gromadzić logika domenowa, Row Data Gateway zaczyna się przekształcać w Active Record.
Dziś Table Data Gateway przypomina DAO lub DataSet z ADO.NET; oba wzorce są żmudne w pisaniu, więc są dobrymi kandydatami do generowania z metadanych.
Czym jest Active Record i kiedy się sprawdza?
Active Record to obiekt opakowujący wiersz tabeli, który łączy dostęp do bazy z logiką domenową — obiekt „wie, jak się zapisać i wczytać". Klasy odwzorowują strukturę tabel niemal jeden do jednego (schemat izomorficzny), mają statyczne findery oraz metody insert/update.
Główna zaleta to prostota — łatwo to zbudować i zrozumieć. Sprawdza się przy niezbyt złożonej logice działającej w obrębie pojedynczego rekordu (CRUD, proste walidacje i obliczenia) oraz gdy schemat bazy jest zbliżony do modelu.
Wady: sprzęga projekt obiektów z projektem bazy, a gdy logika rośnie (relacje, kolekcje, dziedziczenie), wzorzec zaczyna się sypać — wtedy przechodzi się na Data Mapper.
Dziś to dokładnie Rails ActiveRecord, Laravel Eloquent, Django ORM czy Sequelize.
Na czym polega Data Mapper i co daje?
Data Mapper to warstwa mapperów przenosząca dane między obiektami a bazą przy zachowaniu pełnej niezależności: obiekty domeny nie wiedzą o istnieniu bazy (zero SQL, zero wiedzy o schemacie), baza nie wie o obiektach, a sama domena nie wie o mapperze. Dzięki temu model i schemat bazy mogą ewoluować niezależnie.
Powstaje, gdy model obiektowy i relacyjny zaczynają się rozjeżdżać. Ceną jest dodatkowa warstwa, więc opłaca się przy bogatym Domain Model. Współpracuje z Identity Map (tożsamość), Unit of Work (zapis w transakcji) oraz Lazy Load (powstrzymanie pobierania całego grafu). Aby domena nie zależała od mappera, findery deklaruje się przez Separated Interface w pakiecie domeny.
Praktyczna rada: pełnej warstwy mapującej nie buduje się samodzielnie — lepiej użyć gotowego narzędzia.
Dziś to Hibernate/JPA, Doctrine, MyBatis czy NHibernate.
Active Record kontra Data Mapper — kiedy który?
Decyduje złożoność logiki oraz zgodność modelu ze schematem.
| Kryterium | Active Record | Data Mapper |
|---|---|---|
| Złożoność logiki | niska/średnia | wysoka |
| Zgodność klas i tabel | izomorficzna | rozjeżdżająca się |
| Zależność modelu od bazy | sprzęga model z bazą | model nieświadomy bazy |
| Testy bez bazy | trudniejsze | łatwe (podmiana warstwy) |
| Złożoność implementacji | niska | wysoka (zwykle gotowe narzędzie) |
Typowa ewolucja: Row Data Gateway → (dodanie logiki) → Active Record → (rosnąca złożoność) → Data Mapper. Active Record wybierzemy, gdy model jest prosty, a baza pod kontrolą zespołu; Data Mapper, gdy model jest bogaty albo mapujemy do istniejącej, niezależnej bazy.
Dziś Active Record to Rails/Eloquent, a Data Mapper to Hibernate/JPA.
Wzorce behawioralne mapowania O/R
Czym jest Unit of Work i jaki problem rozwiązuje?
Unit of Work śledzi wszystkie obiekty odczytane i zmienione w ramach transakcji biznesowej (zbiory: nowe, zmienione, usunięte), a przy commit() zapisuje całość w jednej transakcji systemowej — we właściwej kolejności (integralność referencyjna) i z kontrolą współbieżności.
Rozwiązuje to dwa problemy: zapisywanie przy każdej zmianie powodowałoby mnóstwo drobnych zapytań i wymagałoby trzymania otwartej transakcji przez cały czas interakcji. Tutaj programista nie wywołuje zapisów jawnie — cała wiedza o tym, co i w jakiej kolejności zapisać, jest w jednym miejscu. Rejestracja obiektów może być jawna (przez wołającego), wbudowana w obiekt lub realizowana przez kontroler śledzący odczyty.
sequenceDiagram
participant K as Kod aplikacji
participant U as Unit of Work
participant DB as Baza
K->>U: registerNew(zamówienie)
K->>U: registerDirty(klient)
K->>U: registerRemoved(pozycja)
K->>U: commit()
U->>DB: BEGIN
U->>DB: INSERT zamówienie
U->>DB: UPDATE klient
U->>DB: DELETE pozycja
U->>DB: COMMIT
Dziś to sesja Hibernate / EntityManager w JPA (persistence context) oraz DbContext w EF — z automatycznym wykrywaniem zmian (dirty checking), porządkowaniem operacji i batchowaniem.
Po co stosować Identity Map?
Identity Map gwarantuje, że jeden wiersz bazy odpowiada jednemu obiektowi w pamięci w obrębie sesji. Bez tego dwa odczyty tego samego rekordu dałyby dwa różne obiekty, a ich niezależne zapisy rozspójniłyby dane — „człowiek z dwoma zegarkami nigdy nie wie, która godzina". Przy każdym odczycie najpierw sprawdza się mapę; trafienie zwraca istniejący obiekt, brak — sięga do bazy.
Mapa pełni też rolę cache, ale jej głównym celem jest poprawność tożsamości, a nie wydajność. Niepotrzebna jest dla obiektów niezmiennych i Value Objects (porównywanych przez wartość). Nie rozwiązuje konfliktów między sesjami — od tego są offline locki.
Dziś to cache pierwszego poziomu (L1) w Hibernate/JPA, zapewniający tożsamość referencyjną w obrębie sesji (ten sam wiersz to ten sam obiekt).
Czym jest Lazy Load i jakie ma warianty?
Lazy Load to obiekt, który nie zawiera jeszcze wszystkich danych, ale wie, jak je dobrać — ładuje je dopiero, gdy są realnie potrzebne, zamiast wciągać cały graf powiązanych obiektów. Cztery typowe warianty:
- lazy initialization — getter sprawdza, czy pole jest puste, i w razie potrzeby je ładuje,
- virtual proxy — pusty obiekt-namiastka wyglądający jak docelowy; ładuje go przy pierwszym wywołaniu metody,
- value holder — obiekt opakowujący wartość, pobieraną przy pierwszym dostępie,
- ghost — prawdziwy obiekt w stanie częściowym (na starcie tylko z identyfikatorem), doczytujący resztę przy dostępie do pól.
Wzorzec dodaje złożoność, więc stosuje się go tylko tam, gdzie naprawdę pomaga; zwykle wystarczają dwa stopnie „lenistwa": pełne wczytanie i wczytanie minimalne do identyfikacji na liście.
Dziś to leniwe asocjacje JPA (FetchType.LAZY), proxy Hibernate oraz dobrze znany LazyInitializationException przy dostępie poza sesją.
Czym jest problem N+1 (ripple loading) i jak go rozwiązać?
Problem N+1 powstaje, gdy wypełnimy kolekcję obiektami Lazy Load i zaczniemy po niej iterować: jedno zapytanie pobiera listę, a potem każdy element wywołuje osobne zapytanie o swoje dane — łącznie 1 + N zapytań. Przy większych kolekcjach to zabija wydajność.
Rozwiązanie polega na zmianie ziarnistości lenistwa: nie buduj kolekcji z leniwych obiektów, lecz uczyń całą kolekcję leniwą i wczytuj jej zawartość jednym zapytaniem (zwykle przez JOIN). Reguły praktyczne:
- pobieraj wiele wierszy naraz; nigdy nie odpytuj tej samej tabeli wiersz po wierszu,
- łącz dane wielu tabel jednym zapytaniem; około 3–4 złączeń to zwykle optimum,
- zawsze profiluj na docelowej bazie i danych.
Dziś remedium to JOIN FETCH, @BatchSize, grafy encji (@EntityGraph) oraz świadome strategie pobierania w JPA.
Wzorce strukturalne mapowania O/R
Czym jest Identity Field i jak generować klucze główne?
Identity Field to przechowanie klucza wiersza w polu obiektu, by powiązać tożsamość obiektu w pamięci z wierszem w bazie. Pojawia się tu wiele decyzji:
- klucz bezznaczeniowy (surogatowy) zamiast znaczącego — naturalne identyfikatory (np. numery dokumentów) bywają zmienne i niepewne,
- prosty zamiast złożonego — jednolita obsługa w całym kodzie,
- typ szybki w porównaniu (zwykle liczba całkowita),
- unikalność w całej bazie ułatwia jedną Identity Map i obsługę dziedziczenia.
Generowanie kluczy:
- auto-increment bazy — proste, ale często nie znasz wartości przed zatwierdzeniem (problem przy wstawianiu rekordu nadrzędnego i zależnych),
- sekwencja/licznik bazy — można pobrać wiele kluczy naraz, działa w osobnej transakcji,
- własna tabela kluczy — rezerwacja puli kluczy, najlepiej w osobnej, krótkiej transakcji.
Dziś to @Id z @GeneratedValue (strategie IDENTITY/SEQUENCE/TABLE) oraz @EmbeddedId/@IdClass dla klucza złożonego.
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziMapowanie metadanych i zapytania
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziPrezentacja webowa
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziWspółbieżność
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziStan sesji
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziDystrybucja
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziWzorce bazowe
Odpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedziOdpowiedź dostępna w pełnej wersji. Odblokuj 27 pozostałych odpowiedzi i ucz się z pełnego zestawu.
Odblokuj odpowiedzi