🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Proxy Pattern w C# — anatomia GoF i kontrola dostępu

Proxy Pattern — zastępnik między klientem a obiektem kontrolujący dostęp (cache, lazy, auth) w C#

Kolejny wzorzec z serii opartej na Anatomii wzorca — 8 elementach opisu GoF. Tym razem Proxy — obiekt-zastępnik, który staje między klientem a właściwym obiektem, żeby kontrolować dostęp do niego: opóźnić kosztowną inicjalizację, dodać cache albo sprawdzić uprawnienia.

1. Intent (Zamierzenie)

Dostarcza zastępnik lub pełnomocnika innego obiektu, aby kontrolować dostęp do niego.

Krótko: klient myśli, że rozmawia z prawdziwym obiektem, a rozmawia z proxy o tym samym interfejsie — a proxy decyduje, kiedy i jak przekazać wywołanie dalej.

2. Also Known As (Inne nazwy)

Surrogate (namiastka, pełnomocnik) — bo proxy występuje w imieniu prawdziwego obiektu.

3. Motivation (Motywacja)

GoF opisuje edytor dokumentów, który nie chce ładować wszystkich osadzonych obrazów przy otwarciu pliku — kosztownych w pamięci i czasie. Zamiast prawdziwych obrazów wstawia ich proxy, które ładują obraz dopiero, gdy trzeba go narysować.

W .NET znasz ten wzorzec z codziennej pracy: Entity Framework Core generuje proxy encji do leniwego ładowania powiązań (kolekcja dogrywana z bazy dopiero przy pierwszym dostępie), a proxy cache’ujące zwraca zapamiętany wynik zamiast ponownie odpytywać wolne API.

4. Applicability (Kiedy stosować)

Stosuj Proxy, gdy potrzebujesz pośrednika bogatszego niż zwykła referencja:

  • virtual proxy — odracza kosztowną inicjalizację, dopóki obiekt nie jest naprawdę potrzebny,
  • protection proxy — kontroluje dostęp na podstawie uprawnień,
  • caching proxy — zapamiętuje wyniki kosztownych operacji,
  • remote proxy — reprezentuje lokalnie obiekt żyjący w innym procesie/na innej maszynie.

5. Structure (Struktura)

┌──────────┐        ┌──────────────────┐
│  Client   │ ─────> │   <<interface>>  │
└──────────┘        │      Subject      │
                     │──────────────────│
                     │ +Request()        │
                     └──────────────────┘
                            △        △
                 ┌──────────┘        └──────────┐
       ┌──────────────┐              ┌──────────────────┐
       │  RealSubject  │  <─ ─ ─ ─ ─ │      Proxy         │
       │──────────────│   przekazuje │───────────────────│
       │ +Request()    │              │ -realSubject       │
       └──────────────┘              │ +Request()          │
                                      └──────────────────┘

Proxy i RealSubject dzielą ten sam interfejs Subject, więc klient nie odróżnia jednego od drugiego. Proxy trzyma referencję do RealSubject i przekazuje mu wywołania, gdy uzna to za stosowne.

6. Participants (Uczestnicy)

  • Subject — wspólny interfejs RealSubject i Proxy; dzięki niemu proxy jest wymienne z prawdziwym obiektem.
  • RealSubject — właściwy obiekt, do którego proxy kontroluje dostęp.
  • Proxy — trzyma referencję do RealSubject, implementuje ten sam interfejs i steruje dostępem (leniwie tworzy, cache’uje, sprawdza uprawnienia).

7. Collaborations (Współpraca)

Proxy przekazuje żądania do RealSubject wtedy i tak, jak wynika to z jego rodzaju — np. dopiero po utworzeniu obiektu (virtual) albo po sprawdzeniu uprawnień (protection).

Klient operuje na interfejsie Subject, nie wiedząc, czy trafił na proxy, czy na prawdziwy obiekt.

8. Consequences (Konsekwencje)

Plus: dodaje kontrolę dostępu (leniwość, cache, uprawnienia) bez zmiany RealSubject ani kodu klienta. Plus: RealSubject i klient nic o proxy nie wiedzą. Minus: dodatkowa warstwa pośrednictwa może wprowadzić opóźnienie i komplikację; przy remote proxy dochodzi zawodność sieci ukryta za zwykłym wywołaniem metody.

Proxy w C# — od diagramu do kodu

### Scenariusz

Serwis pogodowy odpytuje wolne, płatne API. Ten sam kod pyta o pogodę wielokrotnie w krótkim czasie — chcemy cache’ować wynik, nie zmieniając ani serwisu, ani kodu klienta.

### Subject i RealSubject

public interface IPogodaService
{
    string Pobierz(string miasto);
}

public class ApiPogodaService : IPogodaService // RealSubject — kosztowny
{
    public string Pobierz(string miasto)
    {
        Console.WriteLine($"[API] realne zapytanie o {miasto}...");
        Thread.Sleep(1000);            // symulacja wolnego API
        return $"Pogoda dla {miasto}: 21°C";
    }
}

### Caching Proxy

public class CachingPogodaProxy : IPogodaService
{
    private readonly IPogodaService _real;
    private readonly Dictionary<string, string> _cache = new();

    public CachingPogodaProxy(IPogodaService real) => _real = real;

    public string Pobierz(string miasto)
    {
        if (_cache.TryGetValue(miasto, out var zapamietane))
            return zapamietane;            // trafienie w cache — bez zapytania do API

        var wynik = _real.Pobierz(miasto); // przekazanie do RealSubject
        _cache[miasto] = wynik;
        return wynik;
    }
}
IPogodaService pogoda = new CachingPogodaProxy(new ApiPogodaService());

pogoda.Pobierz("Kraków"); // [API] realne zapytanie... (1 s)
pogoda.Pobierz("Kraków"); // natychmiast z cache, bez API

Klient wciąż widzi IPogodaService — nie wie, że między nim a API stanął cache. ApiPogodaService też nie wie o istnieniu proxy.

Idiom C# — Lazy&lt;T&gt; jako wbudowany virtual proxy

Dla najczęstszego zastosowania (leniwe tworzenie kosztownego obiektu) C# ma gotowy virtual proxy — Lazy<T>:

private readonly Lazy<PolaczenieZBaza> _polaczenie =
    new(() => new PolaczenieZBaza()); // utworzone dopiero przy pierwszym .Value

public void Uzyj() => _polaczenie.Value.Wykonaj(); // tu następuje inicjalizacja

Lazy<T> to Proxy „z pudełka”: obiekt powstaje przy pierwszym dostępie do .Value, nie wcześniej. Leniwe ładowanie relacji w EF Core działa na tej samej idei — tylko proxy generuje framework.

Pułapka: Proxy to nie Decorator

Proxy i Decorator mają tę samą strukturę (owijają obiekt tego samego interfejsu) — i to częste pytanie na rozmowie. Różni je cel:

  • Decorator — *dodaje zachowanie* do obiektu (np. kompresję, szyfrowanie); zwykle owijasz wielokrotnie, składając funkcje.
  • Proxy — *kontroluje dostęp* do obiektu (leniwość, cache, uprawnienia); zwykle jeden, zarządza cyklem życia lub tożsamością RealSubject.

Ta sama forma, inna intencja: „co ten obiekt robi z wywołaniem” — wzbogaca je (Decorator) czy reguluje dostęp do oryginału (Proxy).

Wariant: rodzaje proxy

Powyższy przykład to caching proxy. Ta sama struktura obsłuży protection proxy (sprawdź uprawnienia przed _real.Request()), virtual proxy (utwórz _real dopiero przy pierwszym wywołaniu) i remote proxy (przekaż wywołanie przez sieć). Kod szkieletu jest identyczny — różni się tylko to, *co* proxy robi, zanim (lub czy w ogóle) przekaże żądanie dalej.

Podsumowanie

Proxy stawia między klientem a obiektem zastępnik o tym samym interfejsie, który kontroluje dostęp: odracza kosztowną inicjalizację, cache’uje wyniki, sprawdza uprawnienia albo reprezentuje obiekt zdalny — a robi to bez zmiany prawdziwego obiektu i kodu klienta. W C# najczęstszy wariant (leniwość) masz gotowy jako Lazy<T>, a leniwe ładowanie EF Core to Proxy generowany przez framework. Od Decoratora odróżnia go intencja: Decorator dodaje zachowanie, Proxy reguluje dostęp — mimo identycznej struktury.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →