Pehelysúlyú programtervezési minta

A számítástudományban a pehelysúlyú programtervezési minta, pehelysúlyú tervezési minta, vagy pehelysúlyú minta egy programtervezési minta. A pehelysúlyú objektum egy olyan objektum, amely minimalizálja memóriahasználatot azzal, hogy annyi adatot oszt meg, amennyi csak lehetséges más hasonló objektumokkal. Ez a nagyszámú objektumok használatának az a módja, mikor egy egyszerű ismételt reprezentáció használna fel el nem fogadható mennyiségű memóriát. Gyakran az objektum állapotának egyes részei megoszthatók, gyakorlatilag külső adatstruktúrákban tároljuk őket, és csak ideiglenesen adjuk át a pehelysúlyú objektumoknak a felhasználás során.

Egy klasszikus példa a pehelysúlyú minta használatára egy szövegszerkesztőben a karakterek grafikus reprezentációja. Kívánatos lenne, hogy egy dokumentumban minden karakter egy olyan írásjel objektum lenne, amely tartalmazza a font típusát, méretét, és más a kinézetével kapcsolatos adatot, de ez akár száz vagy ezer bájt is lehet karakterenként. E helyett minden karakterhez lenne egy referencia egy megosztott pehelysúlyú írásjel objektum, egy fajta karakternek minden példánya ugyanarra az objektumra mutatna a dokumentumban; plusz még minden karakter elhelyezkedését (a dokumentumban vagy az oldalon) kellene tárolni belsőleg az objektumban.

Egy másik példa a string internálás.

Más szövegkörnyezetben az identikus adatstruktúrák ötletét hash consing-nak is hívják.

Története[szerkesztés]

A Programtervezési minták: Újrafelhasználható objektumorientált szoftver elemei tankönyv szerint[1] a pehelysúlyú mintát először Paul Calder és Mark Linton 1990-ben alkotta meg és vizsgálta teljeskörűen, hogy hatékonyan tudja kezelni az írásjel információkat egy WYSIWYG szövegszerkesztőben,[2] habár hasonló technikákat már használtak más rendszerekben pl. a Weinand alkalmazás keretrendszerben (1988).[3]

Állandóság és egyenlőség[szerkesztés]

Azért, hogy lehetővé tegyük a pehelysúlyú objektumok biztonságos megosztást a kliensek és szálak között az objektumoknak megváltozhatatlannak kell lenniük. A pehelysúlyú objektumok definíció szerint értékkel rendelkező objektumok (angolul value objects). Ugyanannak az értéknek a két pehelysúlyú példányát azonosnak lehet tekinteni.

Lássuk például a C#-ban a következőt (operátor override ill. overloading):

public class CoffeeFlavour {     private readonly string _flavour;      public CoffeeFlavour(string flavour) {         _flavour = flavour;     }      public string Flavour {         get { return _flavour; }     }      public override bool Equals(object obj) {         if (ReferenceEquals(null, obj)) return false;         return obj is CoffeeFlavour && Equals((CoffeeFlavour)obj);     }      public bool Equals(CoffeeFlavour other) {         return string.Equals(_flavour, other._flavour);     }      public override int GetHashCode() {         return (_flavour != null ? _flavour.GetHashCode() : 0);     }      public static bool operator ==(CoffeeFlavour a, CoffeeFlavour b) {         return Equals(a, b);     }      public static bool operator !=(CoffeeFlavour a, CoffeeFlavour b) {         return !Equals(a, b);     } } 

Párhuzamosság[szerkesztés]

Külön figyelmet kell fordítani a több szálon létrejövő pehelysúlyú objektumok esetére.

Ha az értékek listája előre ismert és véges, a pehelysúlyú komponensek idő előtt példányosíthatók és elkérhetők egy többszálú konténertől a versenyhelyzet nélkül. Ebben az esetben két lehetőségünk van:

  1. A pehelysúlyú komponens példányosítása egyszálú, bevezetve a versenyhelyzetet és biztosítva az egy példányonkénti egy értéket.
  2. A párhuzamos szálaknak megengedni, hogy készítsenek számos pehelysúlyú példányt, amely megszünteti a versenyhelyzetet és engedélyez több példányt értékenként. Ez a lehetőség csak akkor életképes, ha az egyenlőség kritériuma biztosított.

C# példa[szerkesztés]

using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading;  public interface ICoffeeFlavourFactory {     CoffeeFlavour GetFlavour(string flavour); }  public class ReducedMemoryFootprint : ICoffeeFlavourFactory {     private readonly object _cacheLock = new object();     private readonly IDictionary<string, CoffeeFlavour> _cache = new Dictionary<string, CoffeeFlavour>();      public CoffeeFlavour GetFlavour(string flavour) {         if (_cache.ContainsKey(flavour)) return _cache[flavour];         var coffeeFlavour = new CoffeeFlavour(flavour);         ThreadPool.QueueUserWorkItem(AddFlavourToCache, coffeeFlavour);         return coffeeFlavour;     }      private void AddFlavourToCache(object state) {         var coffeeFlavour = (CoffeeFlavour)state;         if (!_cache.ContainsKey(coffeeFlavour.Flavour)) {             lock (_cacheLock) {                 if (!_cache.ContainsKey(coffeeFlavour.Flavour)) _cache.Add(coffeeFlavour.Flavour, coffeeFlavour);             }         }     } }  public class MinimumMemoryFootprint : ICoffeeFlavourFactory {     private readonly ConcurrentDictionary<string, CoffeeFlavour> _cache = new ConcurrentDictionary<string, CoffeeFlavour>();      public CoffeeFlavour GetFlavour(string flavour) {         return _cache.GetOrAdd(flavour, flv => new CoffeeFlavour(flv));     } } 

Egyszerű megvalósítás[szerkesztés]

A pehelysúlyú minta lehetővé teszi azon nagyméretű adatok megosztását, amelyek közösek minden objektumban. Más szavakkal, ha azt gondoljuk, hogy ugyanaz az adat ismétlődik minden objektumban, akkor érdemes használni ezt a mintát, egy mutatóval egy egyszerű objektumra mellyel egyszerűen helyet takarítunk meg. Jelen esetben a FlyweightPointer létrehoz egy statikus Company tagot, amely a MyObject minden példányában használható.

//IVSR: simple flyweight example in C#     // Defines Flyweight object which repeats itself.     public class FlyWeight     {         public string Company { get; set; }         public string CompanyLocation { get; set; }         public string CompanyWebSite { get; set; }         //Bulky Data         public byte[] CompanyLogo { get; set; }      }     public static class FlyWeightPointer     {         public static FlyWeight Company = new FlyWeight         {             Company = "Abc",             CompanyLocation = "XYZ",             CompanyWebSite = "www.abc.com"         };     }     public class MyObject     {         public string Name { get; set; }         public FlyWeight Company         {             get             {                 return FlyWeightPointer.Company;             }         }     } 

Java példa[szerkesztés]

import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;  // Instances of CoffeeFlavour will be the Flyweights class CoffeeFlavour {   private final String name;    CoffeeFlavour(String newFlavor) {     this.name = newFlavor;   }    @Override   public String toString() {     return name;   } }  // Menu acts as a factory and cache for CoffeeFlavour flyweight objects class Menu {   private Map<String, CoffeeFlavour> flavours = new HashMap<String, CoffeeFlavour>();    CoffeeFlavour lookup(String flavorName) {     if (!flavours.containsKey(flavorName))       flavours.put(flavorName, new CoffeeFlavour(flavorName));     return flavours.get(flavorName);   }    int totalCoffeeFlavoursMade() {     return flavours.size();   } }  class Order {   private final int tableNumber;   private final CoffeeFlavour flavour;    Order(int tableNumber, CoffeeFlavour flavor) {     this.tableNumber = tableNumber;     this.flavour = flavor;   }    void serve() {     System.out.println("Serving " + flavour + " to table " + tableNumber);   } }  public class CoffeeShop {   private final List<Order> orders = new ArrayList<Order>();   private final Menu menu = new Menu();    void takeOrder(String flavourName, int table) {     CoffeeFlavour flavour = menu.lookup(flavourName);     Order order = new Order(table, flavour);     orders.add(order);   }      void service() {     for (Order order : orders)       order.serve();       }      String report() {     return "\ntotal CoffeeFlavour objects made: "         + menu.totalCoffeeFlavoursMade();   }    public static void main(String[] args) {     CoffeeShop shop = new 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.service();     System.out.println(shop.report());   } } 

Ruby példa[szerkesztés]

# Flyweight Object class Lamp   attr_reader :color   #attr_reader makes color attribute available outside    #of the class by calling .color on a Lamp instance    def initialize(color)     @color = color   end end  class TreeBranch   def initialize(branch_number)     @branch_number = branch_number   end    def hang(lamp)     puts "Hang #{lamp.color} lamp on branch #{@branch_number}"   end end  # Flyweight Factory class LampFactory   def initialize     @lamps = {}   end    def find_lamp(color)     if @lamps.has_key?(color)       # if the lamp already exists, reference it instead of creating a new one       lamp = @lamps[color]     else       lamp = Lamp.new(color)       @lamps[color] = lamp     end     lamp   end    def total_number_of_lamps_made     @lamps.size   end end  class ChristmasTree   def initialize     @lamp_factory = LampFactory.new     @lamps_hung = 0      dress_up_the_tree   end    def hang_lamp(color, branch_number)     TreeBranch.new(branch_number).hang(@lamp_factory.find_lamp(color))     @lamps_hung += 1   end    def dress_up_the_tree     hang_lamp('red', 1)     hang_lamp('blue', 1)     hang_lamp('yellow', 1)     hang_lamp('red', 2)     hang_lamp('blue', 2)     hang_lamp('yellow', 2)     hang_lamp('red', 3)     hang_lamp('blue', 3)     hang_lamp('yellow', 3)     hang_lamp('red', 4)     hang_lamp('blue', 4)     hang_lamp('yellow', 4)     hang_lamp('red', 5)     hang_lamp('blue', 5)     hang_lamp('yellow', 5)     hang_lamp('red', 6)     hang_lamp('blue', 6)     hang_lamp('yellow', 6)     hang_lamp('red', 7)     hang_lamp('blue', 7)     hang_lamp('yellow', 7)     puts "Made #{@lamp_factory.total_number_of_lamps_made} total lamps"   end end 

Fordítás[szerkesztés]

Ez a szócikk részben vagy egészben a Flyweight pattern című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.

Kapcsolódó szócikkek[szerkesztés]

Jegyzetek[szerkesztés]

  1. Gamma, Erich, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 205–206. o. (1995). ISBN 0-201-63361-2 
  2. Calder, Paul R. (1990. október 1.). „Glyphs: Flyweight Objects for User Interfaces”. The 3rd Annual ACM SIGGRAPH Symposium on User Interface Software and Technology: 92–101. doi:10.1145/97924.97935. 
  3. Weinand, Andre (1988). „ET++—an object oriented application framework in C++”. OOPSLA (Object-Oriented Programming Systems, Languages and Applications): 46–57. doi:10.1145/62083.62089.