2007 | 2007 |
Section 6, Chapter 4 | Section 2, Chapter 3 |
Flyweight Pattern
Concept
Put all the content from both books together, the highlights of Flyweight Pattern are listed as below:
- The objects are small, which means the volume of the information they carry is not big;
- The amount of such objects is big, which rises a concern of memory, and that is exactly the issue this pattern is meant to solve;
- The information carried by those small objects can be categorized into two groups: intrinsic & extrinsic;
- Intrinsic information can be reused again and again, and it therefore should not be specific to any scenario, that is why Christopher described it as independent of context, and this piece of info is usually referred as intrinsic state of the object;
- Context is defined by Chris as the particular placement of each object in code flow, with the particular values and states associated with that context;
- Extrinsic information are still be carried by those objects, but it is meant to be context-related, and in the pattern,it is computed on the fly at the expense of CPU power in order to save memory resource, so, it is a trade-off.
- Extrinsic state can be computed either by the objects themselves, or by the clients, with the latter manner, you will need to provide the methods to accept those extrinsic data.
- In Christopher's demonstration, there is no clear boundary between intrinsic info and extrinsic one, they can overlap, and intrinsic info can be used as default state, or overridden by extrinsic info to calculate a result in some cases as needed, but their values in the objects won't be overwritten by the extrinsic info.
- In Christopher's design, there is a component called unshared flyweight, and in his example of text processing, the position of a character is stored as intrinsic information, and it is context related, so it cannot be shared as it only makes sense in some specific situations. Such info is passed through constructor by the context, and will not be changed. How this could be helpful is not revealed clearly in his demonstration, and he also gives following comments: "Since there is one unshared flyweight instance per context location, having intrinsic methods that associate with the context do not interfere with any other implementation of this object within the program...This gives us both the option of sharing objects or allowing many instances of objects to be created. This might be useful if we had a character that was only used rarely or needed special context-related conditions to be useful in the document, such as a paragraph element...The particular context of the unshared character object gives us back the intrinsic values from that context." However, I don't get it, the benefit is not so apparent without a concrete example.
Use
- There is a very large number of objects (thousands) that may not fit in memory.
- Most of the state can be kept on disk or calculated at runtime.
- The remaining state can be factored into a much smaller number of objects with shared state.
There are:
- Many objects to deal with in memory
- Different kinds of state, which can be handled differently to achieve space savings
- Groups of objects that share state
- Ways of computing some of the state at runtime
You want to:
- Implement a system despite severe memory constraints
Design
Photo Group Example by Judith:
Text Processing Example by Chris:
Illustration
Photo Gallery Example:
namespace FlyweightPattern {
// Flyweight Pattern Judith Bishop Sept 2007
public interface IFlyweight {
void Load (string filename);
void Display (PaintEventArgs e, int row, int col);
}
public struct Flyweight : IFlyweight {
// Intrinsic state
Image pThumbnail;
public void Load(string filename) {
pThumbnail = new Bitmap("images/"+filename).GetThumbnailImage(100, 100, null, new IntPtr());
}
public void Display(PaintEventArgs e, int row, int col) {
e.Graphics.DrawImage(pThumbnail, col*100+10, row*130+40, pThumbnail.Width, pThumbnail.Height);
}
}
public class FlyweightFactory {
// Keeps an indexed list of IFlyweight objects in existence
Dictionary<string, IFlyweight> flyweights = new Dictionary<string, IFlyweight>();
public FlyweightFactory() {
flyweights.Clear( );
}
public IFlyweight this[string index] {
get {
if (!flyweights.ContainsKey(index))
flyweights[index] = new Flyweight();
return flyweights[index];
}
}
}
class Client {
// Shared state - the images
static FlyweightFactory album = new FlyweightFactory();
// Unshared state - the groups
static Dictionary<string, List<string>> allGroups = new Dictionary<string, List<string>>();
public void LoadGroups() {
var myGroups = new [] {
new {
Name = "Garden",
Members = new [] {"pot.jpg", "spring.jpg", "barbeque.jpg", "flowers.jpg"}
},
new {
Name = "Italy",
Members = new [] {"cappucino.jpg","pasta.jpg", "restaurant.jpg", "church.jpg"}
},
new {
Name = "Food",
Members = new [] {"pasta.jpg", "veggies.jpg", "barbeque.jpg","cappucino.jpg","lemonade.jpg" }
},
new {
Name = "Friends",
Members = new [] {"restaurant.jpg", "dinner.jpg"}
}
};
// Load the Flyweights, saving on shared intrinsic state
foreach (var g in myGroups) { // implicit typing
allGroups.Add(g.Name, new List<string>());
foreach (string filename in g.Members) {
allGroups[g.Name].Add(filename);
album[filename].Load(filename);
}
}
}
public void DisplayGroups(Object source, PaintEventArgs e) {
// Display the Flyweights, passing the unshared state
int row;
foreach(string g in allGroups.Keys) {
int col;
e.Graphics.DrawString(g, new Font("Arial", 16), new SolidBrush(Color.Black), new PointF(0, row*130+10));
foreach (string filename in allGroups[g]) {
album[filename].Display(e, row, col);
col++;
}
row++;
}
}
}
class Window : Form {
Window() {
this.Height = 600;
this.Width = 600;
this.Text = "Picture Groups";
Client client = new Client();
client.LoadGroups();
this.Paint += new PaintEventHandler(client.DisplayGroups);
}
static void Main() {
Application.Run(new Window());
}
}
}
And in this example:
The Client class -> client.
Interface IFlyweight -> specification of an image view drawer.
Class Flyweight -> the thumbnail drawer.
The intrinsic state -> thumbnail.
The extrinsic state -> full image.
The unshared information -> the data of a group.