Kasta mönstret
I objektorienterad programmering är avyttringsmönstret ett designmönster för resurshantering . I det här mönstret hålls en resurs av ett objekt och frigörs genom att anropa en konventionell metod – vanligtvis kallad close
, dispose
, free
, release
beroende på språket – som frigör alla resurser som objektet håller på. Många programmeringsspråk erbjuder språkkonstruktioner för att undvika att behöva anropa dispose-metoden uttryckligen i vanliga situationer.
Avfallsmönstret används främst i språk vars körmiljö har automatisk sophämtning (se motivering nedan).
Motivering
Slå in resurser i objekt
Att linda in resurser i objekt är den objektorienterade formen av inkapsling och ligger till grund för avyttringsmönstret.
Resurser representeras vanligtvis av handtag (abstrakta referenser), konkret vanligtvis heltal, som används för att kommunicera med ett externt system som tillhandahåller resursen. Till exempel tillhandahålls filer av operativsystemet ( särskilt filsystemet ), som i många system representerar öppna filer med en filbeskrivning (ett heltal som representerar filen).
Dessa handtag kan användas direkt genom att lagra värdet i en variabel och skicka det som ett argument till funktioner som använder resursen. Det är dock ofta användbart att abstrahera från själva handtaget (till exempel om olika operativsystem representerar filer på olika sätt) och att lagra ytterligare hjälpdata med handtaget, så handtag kan lagras som ett fält i en post, tillsammans med andra data; om detta är i en ogenomskinlig datatyp , så ger detta informationsdöljning och användaren abstraheras från den faktiska representationen.
Till exempel, i C-filinmatning/utdata representeras filer av objekt av typen FILE
(förvirrande kallade " filhandtag ": dessa är en abstraktion på språknivå), som lagrar ett (operativsystem) handtag i filen (t.ex. en filbeskrivning ), tillsammans med hjälpinformation som I/O-läge (läsning, skrivning) och position i strömmen. Dessa objekt skapas genom att anropa fopen
(i objektorienterade termer, en konstruktor ), som förvärvar resursen och returnerar en pekare till den; resursen frigörs genom att anropa fclose
på en pekare till FILE
-objektet. I koden:
FIL * f = fopen ( filnamn , läge ); // Gör något med f. fstäng ( f );
Observera att fclose
är en funktion med parametern FILE * .
I objektorienterad programmering är detta istället en instansmetod på ett filobjekt, som i Python:
f = öppen ( filnamn ) # Gör något med f. f . stäng ()
Detta är just avyttringsmönstret och skiljer sig bara i syntax och kodstruktur från traditionell filöppning och stängning. Andra resurser kan hanteras på exakt samma sätt: förvärvas i en konstruktör eller fabrik och släpps genom en explicit stängnings-
eller avyttringsmetod
.
Snabb släpp
Det grundläggande problemet som frigörande av resurser syftar till att lösa är att resurser är dyra (till exempel kan det finnas en gräns för antalet öppna filer), och därför bör släppas omgående. Vidare krävs ibland en del avslutningsarbete, särskilt för I/O, såsom tömning av buffertar för att säkerställa att all data faktiskt skrivs.
Om en resurs är obegränsad eller i praktiken obegränsad, och ingen explicit slutbehandling är nödvändig, är det inte viktigt att släppa den, och i själva verket släpper kortlivade program ofta inte explicit resurser: på grund av kort driftstid är det osannolikt att de tar ut resurser , och de förlitar sig på körtidssystemet eller operativsystemet för att slutföra.
Men i allmänhet måste resurser hanteras (särskilt för långlivade program, program som använder många resurser, eller för säkerhets skull, för att säkerställa att data skrivs ut). Explicit bortskaffande innebär att resursslutförande och frigörande är deterministiskt och snabbt: avyttringsmetoden slutförs
inte förrän dessa är gjorda.
Ett alternativ till att kräva explicit förfogande är att koppla resurshantering till objektets livslängd : resurser förvärvas under objektskapandet och frigörs under objektdestruktion . Detta tillvägagångssätt är känt som Resource Acquisition Is Initialization (RAII) idiom, och används i språk med deterministisk minneshantering (t.ex. C++ ). I det här fallet, i exemplet ovan, förvärvas resursen när filobjektet skapas, och när omfånget för variabeln f avslutas förstörs
filobjektet som f
refererar till, och som en del av detta är resursen släppte.
RAII förlitar sig på att objektets livslängd är deterministisk; men med automatisk minneshantering objektets livslängd inte ett problem för programmeraren: objekt förstörs någon gång efter att de inte längre används, men när de abstraheras. Faktum är att livslängden ofta inte är deterministisk, även om den kan vara det, särskilt om referensräkning används. I vissa fall finns det faktiskt ingen garanti för att objekt någonsin kommer att slutföras: när programmet avslutas kanske det inte slutför objekten, utan istället låter operativsystemet återta minnet; om slutförande krävs (t.ex. för att spola buffertar) kan dataförlust inträffa.
Genom att således inte koppla resurshantering till objektets livslängd tillåter avyttringsmönstret att resurser frigörs snabbt, samtidigt som det ger implementeringsflexibilitet för minneshantering. Kostnaden för detta är att resurser måste hanteras manuellt, vilket kan vara tråkigt och felbenäget.
Tidig utgång
Ett nyckelproblem med kasseringsmönstret är att om kasseringsmetoden inte
anropas så läcker resursen. En vanlig orsak till detta är tidig avgång från en funktion, på grund av en tidig återgång eller ett undantag.
Till exempel:
def func ( filnamn ): f = öppen ( filnamn ) om a : returnerar x f . stäng () returnera y
Om funktionen returnerar vid den första returen stängs filen aldrig och resursen läcker.
def func ( filnamn ): f = öppen ( filnamn ) g ( f ) # Gör något med f som kan skapa ett undantag. f . stäng ()
Om den mellanliggande koden ger upphov till ett undantag, avslutas funktionen tidigt och filen stängs aldrig, så resursen läcker.
Båda dessa kan hanteras av en try...finally
-konstruktion, som säkerställer att finally-satsen alltid exekveras vid exit:
def func ( filnamn ): prova : f = öppen ( filnamn ) # Gör något. slutligen : f . stäng ()
Mer generellt:
Resursresurs = getResurs ( ); försök { // Resursen har förvärvats; utföra åtgärder med resursen. ... } finally { // Släpp resurs, även om ett undantag har skapats. resurs . avyttra (); }
try ...finally
är nödvändig för korrekt undantagssäkerhet , eftersom finally
-blocket möjliggör exekvering av rensningslogik oavsett om ett undantag kastas eller inte i försöksblocket
.
En nackdel med detta tillvägagångssätt är att det kräver att programmeraren uttryckligen lägger till rensningskod i ett finalblock
. Detta leder till uppsvälld kodstorlek, och underlåtenhet att göra det kommer att leda till resursläckage i programmet.
Språkkonstruktioner
För att göra en säker användning av avyttringsmönstret mindre omfattande har flera språk något slags inbyggt stöd för resurser som lagras och släpps i samma kodblock .
C # -språket har användningssatsen som
automatiskt anropar Dispose
-metoden på ett objekt som implementerar IDisposable
-gränssnittet :
använder ( Resursresurs = GetResource ()) { // Utför åtgärder med resursen . ... }
som är lika med:
Resursresurs = GetResource ( ) försök { // Utför åtgärder med resursen. ... } finally { // Resursen kanske inte har förvärvats, eller redan frigivits if ( resource != null ) (( IDisposable ) resource ). Kasta (); }
På liknande sätt har Python -språket en with
-sats som kan användas på liknande sätt med ett context manager- objekt. Kontexthanterarens protokoll kräver implementering av metoderna __enter__
och __exit__
som automatiskt anropas av with
-satskonstruktionen, för att förhindra duplicering av kod som annars skulle inträffa med mönstret try
/ finally .
med resource_context_manager () som resurs : # Utför åtgärder med resursen. ... # Utför andra åtgärder där resursen garanterat kommer att deallokeras. ...
Java - språket introducerade en ny syntax som heter try
-with-resources i Java version 7. Det kan användas på objekt som implementerar AutoCloseable-gränssnittet (som definierar metoden close()):
try ( OutputStream x = new OutputStream (...)) { // Gör något med x } catch ( IOException ex ) { // Hantera undantag // Resursen x stängs automatiskt } // try
Problem
Utöver nyckelproblemet med korrekt resurshantering i närvaro av returer och undantag, och heap-baserad resurshantering (att kassera objekt i en annan omfattning än där de skapas), finns det många ytterligare komplexiteter förknippade med avyttringsmönstret. Dessa problem undviks till stor del av RAII . Men vid vanlig enkel användning uppstår inte dessa komplexiteter: skaffa en enskild resurs, gör något med den, släpp den automatiskt.
Ett grundläggande problem är att det inte längre är en klassinvariant att ha en resurs (resursen hålls kvar från objektet skapades tills den kasseras, men objektet är fortfarande live vid denna tidpunkt), så resursen kanske inte är tillgänglig när objektet försöker använda den, till exempel att försöka läsa från en stängd fil. Detta innebär att alla metoder på objektet som använder resursen potentiellt misslyckas, konkret vanligtvis genom att returnera ett fel eller göra ett undantag. I praktiken är detta mindre, eftersom användningen av resurser vanligtvis också kan misslyckas av andra skäl (till exempel att försöka läsa förbi slutet av en fil), så dessa metoder kan redan misslyckas, och att inte ha en resurs lägger bara till ytterligare ett möjligt misslyckande . Ett standardsätt att implementera detta är att lägga till ett booleskt fält till objektet, kallat disposed
, som är satt till true av dispose
, och kontrolleras av en guard-klausul för alla metoder (som använder resursen), vilket ger ett undantag (som ObjectDisposedException
i .NET) om objektet har kasserats.
Vidare är det möjligt att anropa avyttra
ett föremål mer än en gång. Även om detta kan indikera ett programmeringsfel (varje objekt som innehåller en resurs måste kasseras exakt en gång), är det enklare, mer robust och därför vanligtvis att föredra att avyttra
är idempotent (vilket betyder "att ringa flera gånger är samma sak som att ringa en gång") . Detta implementeras enkelt genom att använda samma booleska disponerade
fält och kontrollera det i en guard-klausul i början av dispose
, i så fall återvänder omedelbart, snarare än att ta upp ett undantag. Java skiljer engångstyper (de som implementerar AutoCloseable ) från engångstyper där dispose är idempotent (undertypen Closeable ).
Omhändertagande i närvaro av arv och sammansättning av objekt som innehåller resurser har liknande problem med förstörelse/slutföring (via destruktörer eller slutbehandlare). Dessutom, eftersom avfallsmönstret vanligtvis inte har språkstöd för detta, pannkod nödvändig. För det första, om en härledd klass åsidosätter en dispose-
metod i basklassen, behöver den åsidosättande metoden i den härledda klassen i allmänhet anropa dispose- metoden
i basklassen för att korrekt frigöra resurser som finns i basen. För det andra, om ett objekt har en "har en" relation med ett annat objekt som innehåller en resurs (dvs om ett objekt indirekt använder en resurs genom ett annat objekt som direkt använder en resurs), ska det indirekt använda objektet vara disponibelt? Detta motsvarar om förhållandet är ägande ( objektsammansättning ) eller visning ( objektaggregation ), eller till och med bara kommunicerande ( association ), och båda konventionerna hittas (indirekt användare är ansvarig för resursen eller är inte ansvarig). Om den indirekta användningen är ansvarig för resursen måste den vara engångsförbrukande och kassera de ägda föremålen när den kasseras (analogt med att förstöra eller slutföra ägda föremål).
Komposition (äga) ger inkapsling (endast objektet som används behöver spåras), men till priset av avsevärd komplexitet när det finns ytterligare relationer mellan objekt, medan aggregering (visning) är betydligt enklare, till priset av att inkapsling saknas. I .NET är konventionen att endast ha direktanvändare av resurser ansvariga: "Du bör implementera IDisposable endast om din typ använder ohanterade resurser direkt." Se resurshantering för detaljer och ytterligare exempel.
Se även
Anteckningar
Vidare läsning
- Microsoft Developer Network: Kasta mönster