Flugviktsmönster

A screenshot of LibreOffice's Writer package.
Textredigerare, som LibreOffice Writer , använder ofta flugviktsmönstret.

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

Ett exempel på UML-klass och sekvensdiagram för flugviktsdesignmönstret.

Ovanstående UML klassdiagram visar:

  • klientklassen , som använder flugviktsmönstret
  • FlyweightFactory , som skapar och delar Flyweight- objekt
  • flugviktsgränssnittet , som tar i yttre tillstånd och utför en operation
  • klassen Flyweight1 , som implementerar Flyweight och lagrar inneboende tillstånd

Sekvensdiagrammet visar följande körtidsinteraktioner :

  1. Klientobjektet anropar getFlyweight(key) FlyweightFactory , vilket returnerar ett Flyweight1- objekt .
  2. Efter att ha anropat operation(extrinsicState) på det returnerade Flyweight1 -objektet, anropar klienten igen getFlyweight(key) FlyweightFactory .
  3. 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:

  1. Gör flugviktsförekomsten enkeltrådig, och introducera på så sätt konflikt och säkerställ en instans per värde.
  2. 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  ); 

Se även