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:
- 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é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]- ↑ 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.