Zlikvidujte vzor - Dispose pattern - Wikipedia

v objektově orientované programování, zlikvidovat vzor je návrhový vzor pro řízení zdrojů. V tomto vzoru a zdroj je držen objekt, a uvolněna voláním konvenční metoda - obvykle volal zavřít, zlikvidovat, volný, uvolnit, uvolnění v závislosti na jazyce - který uvolňuje jakékoli prostředky, které objekt drží. Mnoho jazyků nabízí jazykové konstrukce, aby se v běžných situacích nemuselo explicitně volat metoda dispose.

Vzor vyřazení se primárně používá v jazycích, jejichž běhové prostředí mít automatický sběr odpadu (viz motivace níže).

Motivace

Zabalení zdrojů do objektů

Zabalení prostředků do objektů je objektově orientovanou formou zapouzdření a je základem dispozičního vzoru.

Zdroje jsou obvykle reprezentovány rukojeti (abstraktní odkazy), konkrétně obvykle celá čísla, která se používají ke komunikaci s externím systémem, který zdroj poskytuje. Například soubory poskytuje operační systém (konkrétně souborový systém ), který v mnoha systémech představuje otevřené soubory s a deskriptor souboru (celé číslo představující soubor).

Tyto popisovače lze použít přímo, uložením hodnoty do proměnné a jejím předáním jako argument funkcím, které používají prostředek. Je však často užitečné abstrahovat od samotného popisovače (například pokud různé operační systémy představují soubory odlišně) a ukládat další pomocná data pomocí popisovače, takže popisovače lze uložit jako pole v záznam, spolu s dalšími údaji; pokud je to v neprůhledný datový typ, pak to poskytuje skrývání informací a uživatel je odebrán ze skutečného vyjádření.

Například v Vstup / výstup souboru C., soubory jsou reprezentovány objekty SOUBOR typ (matoucí název „úchyty pilníků ": jedná se o abstrakci na jazykové úrovni), která ukládá popisovač (operačního systému) do souboru (například a deskriptor souboru ), spolu s pomocnými informacemi, jako je I / O režim (čtení, zápis) a pozice v proudu. Tyto objekty jsou vytvořeny voláním fopen (z objektového hlediska, a konstruktor ), který získá zdroj a vrátí na něj ukazatel; zdroj je uvolněn voláním fclose na ukazatel na SOUBOR objekt.[1] V kódu:

SOUBOR *F = fopen(název souboru, režimu);// Udělejte něco s f.fclose(F);

Všimněte si, že fclose je funkce s a SOUBOR * parametr. V objektově orientovaném programování je to místo metoda instance na objekt souboru, jako v Pythonu:

F = otevřeno(název souboru)# Udělejte něco s f.F.zavřít()

Jedná se přesně o dispoziční vzor a liší se pouze v syntaxi a struktuře kódu[A] z tradičního otevírání a zavírání souborů. Další zdroje lze spravovat přesně stejným způsobem: získávat je v konstruktoru nebo továrně a uvolňovat je explicitně zavřít nebo zlikvidovat metoda.

Okamžité uvolnění

Základním problémem, který si uvolnění prostředků klade za cíl vyřešit, je to, že zdroje jsou drahé (může například existovat omezení počtu otevřených souborů), a proto by měly být okamžitě uvolněny. Dále je někdy nutná nějaká finalizační práce, zejména pro I / O, například vyprazdňování vyrovnávacích pamětí, aby se zajistilo, že jsou všechna data skutečně zapsána.

Pokud je prostředek neomezený nebo skutečně neomezený a není nutná žádná explicitní finalizace, není důležité jej uvolňovat a ve skutečnosti krátkodobé programy často zdroje výslovně neuvolňují: vzhledem k krátké době běhu je nepravděpodobné, že vyčerpají zdroje , a spoléhají se na runtime systém nebo operační systém provést jakoukoli finalizaci.

Obecně však musí být spravovány prostředky (zejména u programů s dlouhou životností, programů, které používají mnoho zdrojů, nebo kvůli bezpečnosti, aby bylo zajištěno, že budou data zapsána). Explicitní likvidace znamená, že finalizace a uvolnění prostředků je deterministické a rychlé: zlikvidovat metoda se nedokončí, dokud nejsou hotové.

Alternativou k požadavku na explicitní likvidaci je svázat správu zdrojů životnost objektu: zdroje jsou získávány během vytváření objektů a propuštěn během ničení předmětů. Tento přístup je znám jako Akvizice zdrojů je inicializace (RAII) idiom a používá se v jazycích s deterministickou správou paměti (např. C ++ ). V tomto případě je ve výše uvedeném příkladu prostředek získán při vytvoření objektu souboru a při rozsahu proměnné F je ukončen, objekt souboru, který F odkazuje na je zničeno a v rámci toho je zdroj uvolněn.

RAII spoléhá na to, že životnost objektu je deterministická; s automatickou správou paměti životnost objektu programátor se nestará: objekty jsou zničeny v určitém okamžiku poté, co se již nepoužívají, ale když je odebrán. Životnost ve skutečnosti často není deterministická, i když může být, zejména pokud počítání referencí se používá. Ve skutečnosti v některých případech neexistuje záruka, že objekty budou vůbec být dokončen: když program skončí, nemusí dokončit objekty a místo toho nechat operační systém znovu získat paměť; je-li požadována finalizace (např. k vyprázdnění vyrovnávacích pamětí), může dojít ke ztrátě dat.

Nepřipojením správy prostředků k životnosti objektu tedy vzor nakládání umožňuje zdroje být okamžitě vydán a přitom poskytnout flexibilitu implementace správy paměti. Náklady na to spočívají v tom, že prostředky musí být spravovány ručně, což může být zdlouhavé a náchylné k chybám.

Předčasný odchod

Klíčovým problémem se vzorem vyřazení je, že pokud zlikvidovat metoda není volána, zdroj je propuštěn. Běžnou příčinou toho je předčasný odchod z funkce kvůli předčasnému návratu nebo výjimce.

Například:

def func(název souboru):    F = otevřeno(název souboru)    -li A:        vrátit se X    F.zavřít()    vrátit se y

Pokud se funkce vrátí při prvním návratu, soubor se nikdy neuzavře a dojde k úniku prostředku.

def func(název souboru):    F = otevřeno(název souboru)    G(F)  # Udělejte něco s f, což může vyvolat výjimku.    F.zavřít()

Pokud intervenující kód vyvolá výjimku, funkce se ukončí dříve a soubor se nikdy neuzavře, takže dojde k úniku prostředku.

Obojí zvládne a zkuste ... konečně konstrukce, která zajišťuje, že klauzule nakonec je vždy provedena při ukončení:

def func(název souboru):    Snaž se:        F = otevřeno(název souboru)        # Dělej něco.    Konečně:        F.zavřít()

Obecněji:

Zdroj zdroj = getResource();Snaž se {    // Zdroj byl získán; provádět akce se zdrojem.    ...} Konečně {    // Uvolnit prostředek, i když byla vyvolána výjimka.    zdroj.zlikvidovat();}

The zkuste ... konečně konstrukt je nezbytný pro správné bezpečnost výjimek, protože Konečně blok umožňuje provádění logiky vyčištění bez ohledu na to, zda je vyvolána výjimka nebo ne Snaž se blok.

Jednou z nevýhod tohoto přístupu je, že vyžaduje, aby programátor explicitně přidal vyčištění kódu v Konečně blok. To vede k nafouknutí velikosti kódu a pokud tak neučiníte, povede to k úniku prostředků v programu.

Jazykové konstrukce

Aby bylo bezpečné použití dispozičního vzoru méně podrobné, má několik jazyků nějakou zabudovanou podporu pro prostředky uchovávané a vydané ve stejném blok kódu.

The C# jazykové vlastnosti použitím prohlášení [2] který automaticky volá Zlikvidujte metoda na objektu, který implementuje IDisposable rozhraní:

použitím (Zdroj zdroj = GetResource()){    // Provádění akcí s prostředkem.    ...}

což se rovná:

Zdroj zdroj = GetResource()Snaž se {    // Provádění akcí s prostředkem.    ...}Konečně {    // Zdroj možná nebyl získán nebo již uvolněn    -li (zdroj != nula)         ((IDisposable)zdroj).Zlikvidujte(); }

Podobně Krajta jazyk má a s příkaz, který lze použít k podobnému efektu s a kontextový manažer objekt. The protokol správce kontextu vyžaduje implementaci __enter__ a __výstup__ metody, které jsou automaticky volány s konstrukt příkazu, aby se zabránilo duplikaci kódu, ke kterému by jinak došlo u souboru Snaž se/Konečně vzor.[3]

s resource_context_manager() tak jako zdroj:    # Proveďte akce se zdrojem.    ...# Proveďte další akce, u kterých je zaručeno uvolnění prostředku....

The Jáva jazyk zavedl novou syntaxi s názvem Snaž se-s-prostředky v Javě verze 7.[4] Může být použit na objekty, které implementují rozhraní AutoCloseable (které definuje metodu close ()):

Snaž se (Výstupní proud X = Nový Výstupní proud(...)) {    // Udělejte něco s x} chytit (IOException např) {    // Zpracovat výjimku  // Prostředek x je automaticky uzavřen} // Snaž se

Problémy

Kromě klíčového problému správného řízení zdrojů za přítomnosti návratů a výjimek a správy prostředků založených na haldě (odstraňování objektů v jiném rozsahu, než kde jsou vytvořeny) existuje mnoho dalších složitostí spojených se vzorem vyřazení. Těmto problémům se do značné míry vyhýbá RAII. Při běžném jednoduchém používání však tyto složitosti nevznikají: získejte jeden zdroj, něco s ním udělejte, automaticky jej uvolněte.

Zásadním problémem je, že mít zdroj už není invariant třídy (prostředek je zadržován od vytvoření objektu, dokud není vyřazen, ale objekt je v tomto okamžiku stále aktivní), takže zdroj nemusí být k dispozici, když se ho objekt pokusí použít, například při pokusu o čtení z uzavřeného souboru. To znamená, že všechny metody na objektu, které používají prostředek, potenciálně selžou, konkrétně obvykle vrácením chyby nebo vyvoláním výjimky. V praxi je to menší, protože použití zdrojů může obvykle selhat také z jiných důvodů (například pokus o čtení za koncem souboru), takže tyto metody již mohou selhat, a pokud nemáte prostředek, přidá se další možné selhání . Standardní způsob, jak to implementovat, je přidání logického pole do objektu s názvem zlikvidován, který je nastaven na zlikvidovata zkontrolováno a ochranná doložka na všechny metody (které používají prostředek), vyvolání výjimky (například ObjectDisposedException v .NET), pokud byl objekt vyřazen.[5]

Dále je možné volat zlikvidovat na objekt více než jednou. I když to může znamenat programovací chybu (každý objekt obsahující prostředek musí být odstraněn přesně jednou), je to jednodušší, robustnější, a tedy obvykle výhodnější pro zlikvidovat být idempotentní (což znamená „volání vícekrát je stejné jako volání jednou“).[5] To lze snadno implementovat pomocí stejného boolean zlikvidován pole a jeho kontrola v ochranné doložce na začátku zlikvidovat, v tom případě se vrací okamžitě, spíše než vyvoláním výjimky.[5] Java rozlišuje jednorázové typy (ty, které implementují Automatické uzavření ) z jednorázových typů, kde dispose je idempotentní (podtyp Uzavíratelné ).

Likvidace v přítomnosti dědičnosti a složení objektů, které obsahují prostředky, má obdobné problémy jako destrukce / finalizace (pomocí destruktorů nebo finalizátorů). Dále, protože vzor vyřazení pro to obvykle nemá jazykovou podporu, standardní kód je nutné. Za prvé, pokud odvozená třída přepíše a zlikvidovat Metoda v základní třídě, převažující metoda v odvozené třídě obecně musí volat zlikvidovat metoda v základní třídě, aby bylo možné správně uvolnit prostředky uchovávané v základní třídě. Zadruhé, pokud má objekt vztah „má“ s jiným objektem, který obsahuje prostředek (tj. Pokud objekt nepřímo používá prostředek prostřednictvím jiného objektu, který přímo používá prostředek), měl by být nepřímo využívající objekt použitelný? To odpovídá tomu, zda je vztah vlastnit (složení objektu ) nebo prohlížení (agregace objektů ), nebo dokonce jen komunikovat (sdružení ) a jsou nalezeny obě konvence (nepřímý uživatel je zodpovědný za zdroj nebo není odpovědný). Pokud je za prostředek zodpovědné nepřímé použití, musí být jednorázové a likvidovat vlastněné objekty, když je zlikvidováno (analogicky ke zničení nebo finalizaci vlastněných objektů).

Složení (vlastní) poskytuje zapouzdření (je třeba sledovat pouze použitý objekt), ale za cenu značné složitosti, když existují další vztahy mezi objekty, zatímco agregace (prohlížení) je podstatně jednodušší, za cenu chybějícího zapouzdření. v .SÍŤ Konvence spočívá v odpovědnosti pouze přímého uživatele prostředků: "IDisposable byste měli implementovat pouze v případě, že váš typ používá nespravované prostředky přímo."[6] Vidět řízení zdrojů pro podrobnosti a další příklady.

Viz také

Poznámky

  1. ^ v třídní programování, metody jsou definovány ve třídě pomocí implicitního tento nebo parametr, spíše než jako funkce přijímající explicitní parametr.

Reference

  1. ^ stdio.h - Referenční příručka základních definic, Specifikace Single UNIX, Vydání 7 od Otevřená skupina
  2. ^ Microsoft MSDN: pomocí příkazu (C # reference)
  3. ^ Guido van Rossum, Nick Coghlan (13. června 2011). „PEP 343: The“ with „Statement“. Softwarová nadace Python.
  4. ^ Výukový program Oracle Java: Prohlášení try-with-resources
  5. ^ A b C „Dispose Pattern“.
  6. ^ „IDisposable Interface“. Citováno 2016-04-03.

Další čtení