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ésA 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ésAzé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ésKü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
szerkesztésusing 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ésA 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ésimport 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ésEz 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ésJegyzetek
szerkesztés- ↑ 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.