Flugviktsmönster
Inom datorprogrammering hänvisar designmönstret för flugviktsprogram till ett objekt som minimerar minnesanvändningen genom att dela en del av dess data med andra liknande objekt. Flugviktsmönstret är ett av tjugotre välkända GoF-designmönster . Dessa mönster främjar flexibel objektorienterad mjukvarudesign, som är lättare att implementera, ändra, testa och återanvända.
I andra sammanhang kallas idén om att dela datastrukturer hash consing .
Termen myntades först, och idén utforskades flitigt, av Paul Calder och Mark Linton 1990 för att effektivt hantera glyfinformation i en WYSIWYG-dokumentredigerare . Liknande tekniker användes redan i andra system, dock så tidigt som 1988.
Översikt
Flugviktsmönstret är användbart när man hanterar ett stort antal objekt med enkla upprepade element som skulle använda en stor mängd minne om de lagras individuellt. Det är vanligt att hålla delad data i externa datastrukturer och överföra den till objekten tillfälligt när de används.
Ett klassiskt exempel är de datastrukturer som används som representerar tecken i en ordbehandlare . Naivt kan varje tecken i ett dokument ha ett glyfobjekt som innehåller dess teckensnittskontur, teckensnittsmått och andra formateringsdata. Detta skulle dock använda hundratals eller tusentals byte minne för varje tecken. Istället kan varje tecken ha en referens till ett glyfobjekt som delas av varje instans av samma tecken i dokumentet. På så sätt behöver endast positionen för varje karaktär lagras internt.
Som ett resultat kan flugviktsobjekt:
- lagra inneboende tillstånd som är invariant, kontextoberoende och delbart (till exempel koden för tecknet 'A' i en given teckenuppsättning)
- tillhandahålla ett gränssnitt för att passera i yttre tillstånd som är variant, kontextberoende och inte kan delas (till exempel positionen för tecknet "A" i ett textdokument)
Klienter kan återanvända Flyweight-
objekt och passera i yttre tillstånd vid behov, vilket minskar antalet fysiskt skapade objekt.
Strukturera
Ovanstående UML klassdiagram visar:
- klientklassen
,
som använder flugviktsmönstret - FlyweightFactory
,
som skapar och delarFlyweight-
objekt - flugviktsgränssnittet
, som tar i yttre tillstånd och utför
en operation - klassen
Flyweight1
, som implementerarFlyweight
och lagrar inneboende tillstånd
Sekvensdiagrammet visar följande körtidsinteraktioner :
- Klientobjektet anropar
getFlyweight(key)
påFlyweightFactory
, vilket returnerar ettFlyweight1-
objekt
. - Efter att ha anropat
operation(extrinsicState)
på det returneradeFlyweight1
-objektet, anroparklienten igen
getFlyweight(key)
påFlyweightFactory
. - FlyweightFactory returnerar det redan existerande
Flyweight1-
objektet
.
Genomförande detaljer
Det finns flera sätt att implementera flugviktsmönstret. Ett exempel är föränderlighet: om objekten som lagrar extrinsisk flugviktstillstånd kan förändras.
Oföränderliga objekt delas lätt, men kräver att nya yttre objekt skapas när en förändring i tillståndet inträffar. Däremot kan föränderliga objekt dela tillstånd. Föränderlighet möjliggör bättre återanvändning av objekt via cachelagring och återinitiering av gamla, oanvända objekt. Delning är vanligtvis inte lönsamt när tillståndet är mycket varierande.
Andra primära problem inkluderar hämtning (hur slutklienten kommer åt flugvikten), cachning och samtidighet .
Hämtning
Fabriksgränssnittet för att skapa eller återanvända flugviktsobjekt är ofta en fasad för ett komplext underliggande system . Till exempel är fabriksgränssnittet vanligtvis implementerat som en singleton för att ge global åtkomst för att skapa flugvikter.
Generellt sett börjar hämtningsalgoritmen med en begäran om ett nytt objekt via fabriksgränssnittet.
Begäran vidarebefordras vanligtvis till en lämplig cache baserat på vilken typ av objekt det är. Om begäran uppfylls av ett objekt i cachen kan det återinitieras och returneras. Annars instansieras ett nytt objekt. Om objektet är uppdelat i flera yttre delkomponenter, kommer de att sättas ihop innan objektet returneras.
Cachning
Det finns två sätt att cache flugviktsobjekt: underhållna och ounderhållna cacher.
Objekt med mycket varierande tillstånd kan cachelagras med en FIFO- struktur. Denna struktur bibehåller oanvända objekt i cachen, utan att behöva söka i cachen.
Däremot har ej underhållna cachar mindre omkostnader i förväg: objekt för cacharna initieras i bulk vid kompilering eller vid uppstart. När objekt väl fyllt i cachen, kan objekthämtningsalgoritmen ha mer overhead associerad än push/pop-operationerna för en underhållen cache.
När man hämtar yttre objekt med oföränderligt tillstånd måste man helt enkelt söka i cachen efter ett objekt med det tillstånd man önskar. Om inget sådant objekt hittas måste ett med det tillståndet initieras. Vid hämtning av yttre objekt med föränderligt tillstånd måste cachen sökas efter ett oanvänt objekt för att återinitieras om inget använt objekt hittas. Om det inte finns något oanvänt objekt tillgängligt måste ett nytt objekt instansieras och läggas till i cachen.
Separata cacher kan användas för varje unik underklass av extrinsiskt objekt. Flera cachar kan optimeras separat, associera en unik sökalgoritm med varje cache. Detta objektcachesystem kan kapslas in med ansvarskedjans mönster, vilket främjar lös koppling mellan komponenter.
Samtidighet
Särskild hänsyn måste tas när flugviktsobjekt skapas på flera trådar. Om listan med värden är ändlig och känd i förväg, kan flugvikterna instansieras i förväg och hämtas från en behållare på flera trådar utan tvivel. Om flugvikter instansieras på flera trådar finns det två alternativ:
- Gör flugviktsförekomsten enkeltrådig, och introducera på så sätt konflikt och säkerställ en instans per värde.
- Tillåt samtidiga trådar att skapa flera instanser med flugvikt, vilket eliminerar konflikter och tillåter flera instanser per värde.
För att möjliggöra säker delning mellan klienter och trådar kan flygviktsobjekt göras till oföränderliga värdeobjekt , där två instanser anses lika om deras värden är lika.
Det här exemplet från C# 9 använder poster för att skapa ett värdeobjekt som representerar smaker av kaffe:
offentligt register CoffeeFlavours ( smak av strängar );
Exempel i C#
använder varje instans av klassen MyObject en
Pointer-
klass för att tillhandahålla data.
// Definierar flugviktsobjekt som upprepar sig. public class Flyweight { public string Name { get ; set ; } offentlig sträng Plats { get ; set ; } public string Webbplats { get ; set ; } public byte [] Logo { get ; set ; } } public static class Pointer { public static readonly Flyweight Company = new Flyweight { "Abc" , "XYZ" , "www.example.com" }; } public class MyObject { public string Name { get ; set ; } offentlig sträng Företag => Pekare . Företag . Namn ; }
Exempel i Python
Attribut kan definieras på klassnivå istället för endast för instanser i Python eftersom klasser är förstklassiga objekt i språket – vilket innebär att det inte finns några begränsningar för deras användning eftersom de är samma som alla andra objekt. Klassinstanser i ny stil lagrar instansdata i en speciell attributordboksinstans .__dict__
. Som standard slås åtkomliga attribut först upp i denna __dict__
och faller sedan tillbaka till instansens klassattribut. På så sätt kan en klass effektivt vara en slags flugviktsbehållare för sina instanser.
Även om Python-klasser är föränderliga som standard, kan oföränderlighet emuleras genom att åsidosätta klassens __setattr__-
metod så att den inte tillåter ändringar av några flugviktsattribut.
0
0
# Förekomster av CheeseBrand kommer att vara flugviktsklassen CheeseBrand : def __init__ ( self , brand : str , cost : float ) - > None : self . varumärke = varumärkesjag . _ kostnad = självkostnad . _ _immutable = True # Inaktiverar framtida attributioner def __setattr__ ( self , name , value ): if getattr ( self , "_immutable" , False ): # Tillåt initial attribution raise RuntimeError ( "Detta objekt är oföränderligt" ) else : super () . __setattr__ ( namn , värde ) klass CheeseShop : menu = {} # Delad behållare för att komma åt Flugviktarna def __init__ ( själv ) -> Ingen : själv . order = {} # behållare per instans med privata attribut def stock_cheese ( själv , varumärke : str , kostnad : flytande ) -> Ingen : ost = OstMärke ( varumärke , kostnad ) själv . menu [ märke ] = ost # Delad flugvikt def sell_cheese ( self , brand : str , units : int ) -> None : self . beställningar . setdefault ( märke , ) själv . orders [ brand ] += units # Instansattribut def total_units_sold ( self ): return summa ( self . orders . values ()) def total_income ( self ): inkomst = för varumärke , enheter i själv . beställningar . poster (): inkomst += själv . meny [ märke ] . kostnad * enheter returintäkter shop1 = CheeseShop ( ) shop2 = CheeseShop () shop1 . stock_cheese ( "vit" , 1.25 ) shop1 . stock_cheese ( "blue" , 3,75 ) # Nu har varje CheeseShop "vit" och "blå" på inventariet # SAMMA "vita" och "blå" ostvarumärkebutik1 . sell_cheese ( "blå" , 3 ) # Båda kan sälja shop2 . sell_cheese ( "blå" , 8 ) # Men enheterna som säljs lagras per instans hävda shop1 . total_units_sold () == 3 hävda butik1 . total_income () == 3,75 * 3 hävda butik2 . total_units_sold () == 8 hävda butik2 . total_income () == 3,75 * 8
Exempel i C++
C++ Standard Template Library tillhandahåller flera behållare som gör att unika objekt kan mappas till en nyckel. Användningen av behållare hjälper till att ytterligare minska minnesanvändningen genom att ta bort behovet av att skapa tillfälliga objekt.
0
#include <iostream> #include <map> #include <string> // Instanser av Tenant kommer att vara flugviktsklassen Tenant { public : Tenant ( const std :: string & name = " " ) : m_name ( name ) {} std :: strängnamn ( ) const { return m_name ; } privat : std :: sträng m_namn ; }; // Registry fungerar som en fabrik och cache för hyresgästens flygviktsobjekt klass Registry { public : Registry () : hyresgäster () {} Tenant & findByName ( const std :: string & name ) { if ( ! hyresgäster . innehåller ( namn )) { hyresgäster [ namn ] = Hyresgäst { namn }; } återvändande hyresgäster [ namn ]; } privat : std :: karta < std :: sträng , Hyresgäst > hyresgäster ; }; // Lägenhet mappar en unik hyresgäst till sitt rumsnummer. klass Lägenhet { public : Apartment () : m_occupants (), m_registry () {} void addOccupant ( const std :: string & name , int room ) { m_occupants [ room ] = & m_registry . findByName ( namn ); } void hyresgäster () { for ( const auto & i : m_occupants ) { const int & room = i . först ; const auto & hyresgäst = i . andra ; std :: cout << hyresgäst -> namn () << " upptar rum " << rum << std :: endl ; } } privat : std :: map < int , Hyresgäst *> m_occupants ; Registry m_registry ; }; int main () { Lägenhet lägenhet ; lägenhet . addOccupant ( "David" , 1 ); lägenhet . addOccupant ( "Sarah" , 3 ); lägenhet . addOccupant ( "George" , 2 ); lägenhet . addOccupant ( "Lisa" , 12 ); lägenhet . addOccupant ( "Michael" , 10 ); lägenhet . hyresgäster (); återvända ; }
Exempel i PHP
<?php class CoffeeFlavour { private static array $CACHE = []; privat funktion __construct ( privat sträng $name ) {} offentlig statisk funktion praktikant ( sträng $name ) : self { self :: $CACHE [ $name ] ??= nytt jag ( $name ); returnera själv :: $CACHE [ $namn ]; } offentlig statisk funktion flavoursInCache () : int { return count ( self :: $CACHE ); } offentlig funktion __toString () : string { return $this -> name ; } } class Order { private function __construct ( privat CoffeeFlavour $flavour , privat int $tableNumber ) {} public static function create ( sträng $flavourName , int $tableNumber ) : self { $flavour = CoffeeFlavour :: praktikant ( $flavourName ); returnera nytt jag ( $flavour , $tableNumber ); } public function __toString () : string { returnera "Serving { $this -> flavor } till tabellen { $this -> tableNumber } " ; } } klass CoffeeShop { privat array $orders = []; public function takeOrder ( sträng $flavour , int $tableNumber ) { $this -> orders [] = Order :: create ( $flavour , $tableNumber ); } public function service () { print ( implode ( PHP_EOL , $this -> orders ) . PHP_EOL ); } } $shop = ny CoffeeShop (); $shop -> takeOrder ( "Cappuccino" , 2 ); $shop -> takeOrder ( "Frappe" , 1 ); $shop -> takeOrder ( "Espresso" , 1 ); $shop -> takeOrder ( "Frappe" , 897 ); $shop -> takeOrder ( "Cappuccino" , 97 ); $shop -> takeOrder ( "Frappe" , 3 ); $shop -> takeOrder ( "Espresso" , 3 ); $shop -> takeOrder ( "Cappuccino" , 3 ); $shop -> takeOrder ( "Espresso" , 96 ); $shop -> takeOrder ( "Frappe" , 552 ); $shop -> takeOrder ( "Cappuccino" , 121 ); $shop -> takeOrder ( "Espresso" , 121 ); $shop -> tjänst (); print ( "CoffeeFlavor-objekt i cache: " . CoffeeFlavour :: flavoursInCache () . PHP_EOL );