Flyweight pattern

From Wikipedia, the free encyclopedia

Jump to: navigation, search

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's common to put 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 are the data structures for graphical representation of characters in a word processor. It would be nice to have, for each character in a document, a glyph object containing its font outline, font metrics, and other formatting data, but it 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 externally.

Contents

[edit] Examples

The following programs illustrate the document example given above: the flyweights are called FontData in the Java example and GraphicChar in the C# example.

The examples illustrate the Flyweight pattern used to reduce memory by loading only the data necessary to perform some immediate task from a large Font object into a much smaller FontData (Flyweight) object.

[edit] Java

public enum FontEffect {
    BOLD, ITALIC, SUPERSCRIPT, SUBSCRIPT, STRIKETHROUGH
}
 
public final class FontData {
    /**
     * A weak hash map will drop unused references to FontData.
     * Values have to be wrapped in WeakReferences, 
     * because value objects in weak hash map are held by strong references.
     */
    private static final WeakHashMap<FontData, WeakReference<FontData>> FLY_WEIGHT_DATA =
        new WeakHashMap<FontData, WeakReference<FontData>>();
    private final int pointSize;
    private final String fontFace;
    private final Color color;
    private final Set<FontEffect> effects;
 
    private FontData(int pointSize, String fontFace, Color color, EnumSet<FontEffect> effects) {
        this.pointSize = pointSize;
        this.fontFace = fontFace;
        this.color = color;
        this.effects = Collections.unmodifiableSet(effects);
    }
 
    public static FontData create(int pointSize, String fontFace, Color color,
        FontEffect... effects) {
        EnumSet<FontEffect> effectsSet = EnumSet.noneOf(FontEffect.class);
        for (FontEffect fontEffect : effects) {
            effectsSet.add(fontEffect);
        }
        // We are unconcerned with object creation cost, we are reducing overall memory consumption
        FontData data = new FontData(pointSize, fontFace, color, effectsSet);
        if (!FLY_WEIGHT_DATA.containsKey(data)) {
            FLY_WEIGHT_DATA.put(data, new WeakReference<FontData> (data));
        }
        // return the single immutable copy with the given values
        return FLY_WEIGHT_DATA.get(data).get();
    }
 
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof FontData) {
            if (obj == this) {
                return true;
            }
            FontData other = (FontData) obj;
            return other.pointSize == pointSize && other.fontFace.equals(fontFace)
                && other.color.equals(color) && other.effects.equals(effects);
        }
        return false;
    }
 
    @Override
    public int hashCode() {
        return (pointSize * 37 + effects.hashCode() * 13) * fontFace.hashCode();
    }
 
    // Getters for the font data, but no setters. FontData is immutable.
}

[edit] C#

using System;
using System.Text;
using System.Collections.Generic;
 
 
class GraphicChar
{
    char c;
    string fontFace;
    public GraphicChar(char c, string fontFace) { this.c = c; this.fontFace = fontFace; }
    public static void printAtPosition(GraphicChar c, int x, int y)
    {
        Console.WriteLine("Printing '{0}' in '{1}' at position {2}:{3}.", c.c, c.fontFace, x, y);
    }
}
 
class GraphicCharFactory
{
    Dictionary<string, GraphicChar> pool = new Dictionary<string, GraphicChar>();
 
    public int getNum() { return pool.Count; }
 
    public GraphicChar get(char c, string fontFace)
    {
        GraphicChar gc;
        string key = c.ToString() + fontFace;
        if (!pool.TryGetValue(key, out gc)) {
            gc = new GraphicChar(c, fontFace);
            pool.Add(key, gc);
        }
        return gc;
    }
}
 
class FlyWeightExample
{
    public static void Main(string[] args)
    {
        GraphicCharFactory cf = new GraphicCharFactory();
 
        // Compose the text by storing the characters as objects.
        List<GraphicChar> text = new List<GraphicChar>();
        text.Add(cf.get('H', "Arial"));    // 'H' and "Arial" are called intrinsic information
        text.Add(cf.get('e', "Arial"));    // because it is stored in the object itself.
        text.Add(cf.get('l', "Arial"));
        text.Add(cf.get('l', "Arial"));
        text.Add(cf.get('o', "Times"));
        text.Add(cf.get(' ', "Times"));
        text.Add(cf.get('w', "Times"));
        text.Add(cf.get('o', "Times"));
        text.Add(cf.get('r', "Times"));
        text.Add(cf.get('l', "Times"));
        text.Add(cf.get('d', "Times"));
 
        // See how the Flyweight approach is beginning to save space:
        Console.WriteLine("CharFactory created only {0} objects for {1} characters.", cf.getNum(), text.Count);
 
        int x = 0, y = 0;
        foreach (GraphicChar c in text)
        {             // Passing position as extrinsic information to the objects,
            GraphicChar.printAtPosition(c, x++, y);   // as a top-left 'A' is not different from a top-right one.
        }
 
        Console.ReadLine();
    }
}

[edit] C++

Please note this is just a simple demo of flyweight concept in c++. You could easily use the Boost Flyweight library instead.

#include <iostream>
#include <string>
#include <map>
#include <list>
#include <memory>
#include <algorithm>
using namespace std;
 
class GraphicChar
{
public:
	friend class Character;
	GraphicChar(char c, const string fontFace):m_c(c), m_fontFace(fontFace){}
protected:
private:
	char	m_c;
	string  m_fontFace;
};
 
class GraphicCharFactory
{
	typedef std::map<string, GraphicChar*>::iterator poolIter;
public:
	virtual ~GraphicCharFactory()
	{
		for (poolIter beg = m_pool.begin(); beg != m_pool.end(); ++beg)
			delete beg->second;
		m_pool.clear();
	}
 
	GraphicChar* get(char c, const string fontFace)
	{
		string key = c + fontFace;
		poolIter posit;
		if (( posit = m_pool.find(key))!= m_pool.end())
			return posit->second;
		else
			return (*((m_pool.insert(make_pair(key, new GraphicChar(c, fontFace)))).first)).second;
	}
 
	int count()
	{
		return m_pool.size();
	}
 
protected:
private:
	std::map<string, GraphicChar*>	m_pool;
};
 
class Character
{
public:
	Character(int a,int b,const GraphicChar* gra):m_x(a),m_y(b),m_Grap(gra){}
 
	void print()
	{
		cout << "Printing " << m_Grap->m_c << " in " << m_Grap->m_fontFace << " at position" << m_x << " : " << m_y << endl;
	}
 
protected:
private:
	int				   m_x, m_y;
	const GraphicChar* m_Grap;
};
 
int _tmain(int argc, _TCHAR* argv[])
{
	typedef std::list<Character> article;
	article simpArticle;
	std::auto_ptr<GraphicCharFactory> cf(new GraphicCharFactory);
	int x = 0, y = 0;
	simpArticle.push_back(Character(x,   y, cf->get('H', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('e', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('l', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('l', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('o', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get(' ', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('w', "Times")) );
	simpArticle.push_back(Character(++x, y, cf->get('o', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('r', "Times")) );
	simpArticle.push_back(Character(++x, y, cf->get('l', "Arial")) );
	simpArticle.push_back(Character(++x, y, cf->get('d', "Times")) );
	std::for_each(simpArticle.begin(),simpArticle.end(), std::mem_fun_ref(&Character::print));
	cout <<"CharFactory created only "<< cf->count() <<" objects for " << simpArticle.size() <<" characters."  << endl;
	return 0;
}

Console output:

Printing H in Arial at position0 : 0
Printing e in Arial at position1 : 0
Printing l in Arial at position2 : 0
Printing l in Arial at position3 : 0
Printing o in Arial at position4 : 0
Printing   in Arial at position5 : 0
Printing w in Times at position6 : 0
Printing o in Arial at position7 : 0
Printing r in Times at position8 : 0
Printing l in Arial at position9 : 0
Printing d in Times at position10 : 0
CharFactory created only 8 objects for 11 characters.

[edit] External links


Personal tools