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 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, habár hasonló technikákat már használtak más rendszerekben pl. a Weinand alkalmazás keretrendszerben (1988).
Á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
Kapcsolódó szócikkek Másolás írás közben Memoization Többke
Jegyzetek
Kategória:Programtervezési minták Kategória:Java-kódpéldák Kategória:C Sharp-kódpéldák