Flyweight pattern: Difference between revisions
→Example in Python: basic grammar+punctuation |
|||
Line 10: | Line 10: | ||
== Immutability & Equality == |
== Immutability & Equality == |
||
To enable sharing, between clients and threads, Flyweight objects must be [[Immutable object|immutable]]. Flyweight objects are by definition value objects. The identity of the object instance is of no consequence therefore two Flyweight instances of the same value are considered equal. |
To enable safe sharing, between clients and threads, Flyweight objects must be [[Immutable object|immutable]]. Flyweight objects are by definition value objects. The identity of the object instance is of no consequence therefore two Flyweight instances of the same value are considered equal. |
||
Example in C# (note Equals and GetHashCode overrides as well as == and != operator overloads): |
Example in C# (note Equals and GetHashCode overrides as well as == and != operator overloads): |
Revision as of 20:44, 13 November 2013
This article includes a list of general references, but it lacks sufficient corresponding inline citations. (May 2008) |
In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. Often some parts of the object state can be shared, and it is common practice to hold them in external data structures and pass them to the flyweight objects temporarily when they are used.
A classic example usage of the flyweight pattern is the data structures for graphical representation of characters in a word processor. It might be desirable to have, for each character in a document, a glyph object containing its font outline, font metrics, and other formatting data, but this would amount to hundreds or thousands of bytes for each character. Instead, for every character there might be a reference to a flyweight glyph object shared by every instance of the same character in the document; only the position of each character (in the document and/or the page) would need to be stored internally.
Another example is string interning.
In other contexts the idea of sharing identical data structures is called hash consing.
Immutability & Equality
To enable safe sharing, between clients and threads, Flyweight objects must be immutable. Flyweight objects are by definition value objects. The identity of the object instance is of no consequence therefore two Flyweight instances of the same value are considered equal.
Example in C# (note Equals and GetHashCode overrides as well as == and != operator overloads):
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);
}
}
Concurrency
Special consideration must be made in scenarios where Flyweight objects are created on multiple threads. If the list of values is finite and known in advance the Flyweights can be instantiated ahead of time and retrieved from a container on multiple threads with no contention. If Flyweights are instantiated on multiple threads there are two options:
- Make Flyweight instantiation single threaded thus introducing contention and ensuring one instance per value.
- Allow concurrent threads to create multiple Flyweight instances thus eliminating contention and allowing multiple instances per value. This option is only viable if the equality criterion is met.
Example in C#
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));
}
}
History
According to a textbook Design Patterns: Elements of Reusable Object-Oriented Software,[1] the flyweight pattern was first coined and extensively explored by Paul Calder and Mark Linton in 1990[2] to efficiently handle glyph information in a WYSIWYG document editor, although similar techniques were already used in other systems, e.g., an application framework by Weinand et al. (1988).[3]
Example in Java
import java.util.HashMap;
import java.util.Map;
// Flyweight object interface
interface CoffeeOrder {
public void serveCoffee(CoffeeOrderContext context);
}
// ConcreteFlyweight object that creates ConcreteFlyweight
class CoffeeFlavor implements CoffeeOrder {
private final String flavor;
public CoffeeFlavor(String newFlavor) {
this.flavor = newFlavor;
}
public String getFlavor() {
return this.flavor;
}
public void serveCoffee(CoffeeOrderContext context) {
System.out.println("Serving Coffee flavor " + flavor + " to table number " + context.getTable());
}
}
class CoffeeOrderContext {
private final int tableNumber;
public CoffeeOrderContext(int tableNumber) {
this.tableNumber = tableNumber;
}
public int getTable() {
return this.tableNumber;
}
}
//FlyweightFactory object
class CoffeeFlavorFactory {
private Map<String, CoffeeFlavor> flavors = new HashMap<String, CoffeeFlavor>();
public CoffeeFlavor getCoffeeFlavor(String flavorName) {
CoffeeFlavor flavor = flavors.get(flavorName);
if (flavor == null) {
flavor = new CoffeeFlavor(flavorName);
flavors.put(flavorName, flavor);
}
return flavor;
}
public int getTotalCoffeeFlavorsMade() {
return flavors.size();
}
}
class TestFlyweight {
/** The flavors ordered. */
private static CoffeeFlavor[] flavors = new CoffeeFlavor[100];
/** The tables for the orders. */
private static CoffeeOrderContext[] tables = new CoffeeOrderContext[100];
private static int ordersMade = 0;
private static CoffeeFlavorFactory flavorFactory;
public static void takeOrders(String flavorIn, int table) {
flavors[ordersMade] = flavorFactory.getCoffeeFlavor(flavorIn);
tables[ordersMade++] = new CoffeeOrderContext(table);
}
public static void main(String[] args) {
flavorFactory = new CoffeeFlavorFactory();
takeOrders("Cappuccino", 2);
takeOrders("Cappuccino", 2);
takeOrders("Frappe", 1);
takeOrders("Frappe", 1);
takeOrders("Espresso", 1);
takeOrders("Frappe", 897);
takeOrders("Cappuccino", 97);
takeOrders("Cappuccino", 97);
takeOrders("Frappe", 3);
takeOrders("Espresso", 3);
takeOrders("Cappuccino", 3);
takeOrders("Espresso", 96);
takeOrders("Frappe", 552);
takeOrders("Cappuccino", 121);
takeOrders("Espresso", 121);
for (int i = 0; i < ordersMade; ++i) {
flavors[i].serveCoffee(tables[i]);
}
System.out.println(" ");
System.out.println("total CoffeeFlavor objects made: " + flavorFactory.getTotalCoffeeFlavorsMade());
}
}
Example in Ruby
# Flyweight Object
class Lamp
def initialize(color)
@color = color
end
def get_color
@color
end
end
class TreeBranch
def initialize(branch_number)
@branch_number = branch_number
end
def hang(lamp)
p "Hang #{lamp.get_color} lamp on branch #{@branch_number}"
end
end
# Flyweight Factory
class LampFactory
def initialize
@lamps = {}
end
def get_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 get_total_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.get_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)
p "Made #{@lamp_factory.get_total_lamps_made} total lamps"
end
end
ChristmasTree.new
Example in Python
In python just adding __slots__ attribute to the class will make automatically enable the Flyweight pattern for that class. Defining __slots__ with the list of attributes tells python interpreter that these are the only attributes ever needed for all the instance of this class. This automatically disables dynamic dictionary for every instance of the class. This saves lots of memory when someone is creating lots of instances of this class.
# Flyweight pattern
class Lamp(object):
__slots__ = ['color']
def __init__(self,color):
self.color = color
# Flyweight pattern
class TreeBranch(object):
__slots__ = ['branch_number']
def __init__(self,branch_number):
self.branch_number = branch_number
def hang(self,lamp):
print("Hang {} lamp on branch {}".format(lamp.color,self.branch_number))
class ChristmasTree(object):
def __init__(self):
self.lamps_hung = 0
self.dress_up_the_tree()
def hang_lamp(self,color, branch_number):
TreeBranch(branch_number).hang(Lamp(color))
self.lamps_hung += 1
def dress_up_the_tree(self):
self.hang_lamp('red', 1)
self.hang_lamp('blue', 1)
self.hang_lamp('yellow', 1)
self.hang_lamp('red', 2)
self.hang_lamp('blue', 2)
self.hang_lamp('yellow', 2)
self.hang_lamp('red', 3)
self.hang_lamp('blue', 3)
self.hang_lamp('yellow', 3)
self.hang_lamp('red', 4)
self.hang_lamp('blue', 4)
self.hang_lamp('yellow', 4)
self.hang_lamp('red', 5)
self.hang_lamp('blue', 5)
self.hang_lamp('yellow', 5)
self.hang_lamp('red', 6)
self.hang_lamp('blue', 6)
self.hang_lamp('yellow', 6)
self.hang_lamp('red', 7)
self.hang_lamp('blue', 7)
self.hang_lamp('yellow', 7)
if __name__ == '__main__':
ChristmasTree()
See also
References
- ^ *Gamma, Erich (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. pp. 205–206. ISBN 0-201-63361-2.
{{cite book}}
: Unknown parameter|coauthors=
ignored (|author=
suggested) (help) - ^
Calder, Paul R.; Linton, Mark A. (1990). Glyphs: Flyweight Objects for User Interfaces. The 3rd Annual ACM SIGGRAPH Symposium on User Interface Software and Technology. Snowbird, Utah, United States. pp. 92–101. doi:10.1145/97924.97935. ISBN 0-89791-410-4.
{{cite conference}}
: Unknown parameter|month=
ignored (help) - ^ Weinand, Andre; Gamma, Erich; Marty, Rudolf (1988). ET++—an object oriented application framework in C++. OOPSLA (Object-Oriented Programming Systems, Languages and Applications). San Diego, California, United States. pp. 46–57. doi:10.1145/62083.62089. ISBN 0-89791-284-5.
External links
- Flyweight in UML and in LePUS3 (a formal modelling language)
- Article "Enhancing Web Application Performance with Caching" by Neal Ford
- Section "Flyweight Text Entry Fields (archive.org)" from the RIDES Reference Manual by Allen Munro and Quentin A. Pizzini
- Description from Portland's Pattern Repository
- Sourdough Design
- Class::Flyweight - implement the flyweight pattern in OO perl
- Boost.Flyweight - A generic C++ implementation