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
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
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
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:
- 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.
- 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
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
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
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
# 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
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
- Másolás írás közben
- Memoization
- Többke
Jegyzetek
- ↑ 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
- ↑ 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.
- ↑ 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.