vendredi 6 juillet 2018

Understanding flyweight design pattern (with Java) as explained by Gang of Four book

I am trying to understand flyweight design pattern. I found confusing explanation and examples on various sites. So I went back to gang of four book. It explains the concept as follows:

Basic Concept

  • A flyweight is a shared object that can be used in multiple contexts simultaneously. 
  • The key concept here is the distinction between intrinsic and extrinsic state. Intrinsic state is stored in the flyweight; it consists of information that's independent of the flyweight's context, thereby making it sharable. Extrinsic state depends on and varies with the flyweight's context and therefore can't be shared. 
  • Client objects are responsible for passing extrinsic state to the flyweight when it needs it.

Components

  • Flyweight: an interface through which flyweights can receive and act on extrinsic state.
  • ConcreteFlyweight (Character class): implements Flyweight contains intrinsic storage state; must be sharable / shared 
  • UnsharedConcreteFlyweight (Row, Column classes): implements Flyweight may not be shared; usually contains ConcreteFlyweight objects as children when used with Composite pattern
  • FlyweightFactory: ensures that flyweights are shared properly; When a client requests a flyweight, the FlyweightFactory object supplies an existing instance or creates one, if none exists.
  • Clients: maintains a reference to flyweight(s); computes or stores the extrinsic state of flyweight(s)

Example of text editor implementing flyweight pattern

  • Logically there is an object for every occurrence of a given character in the document. Physically, however, there is one shared flyweight object per character, and it appears in different contexts in the document structure. Each occurrence of a particular character object refers to the same instance in the shared pool of flyweight objects: enter image description here
  • Glyph is the abstract class for graphical objects, some of which may be flyweights. Operations that may depend on extrinsic state have it passed to them as a parameter. For example, Draw and Intersects must know which context the glyph is in before they can do their job. enter image description here
  • A flyweight representing the letter "a" only stores the corresponding character code; it doesn't need to store its location or font. Clients supply the context-dependent information that the flyweight needs to draw itself. For example, a Row glyph knows where its children should draw themselves so that they are tiled horizontally. Thus it can pass each child its location in the draw request.
  • Because the number of different character objects is far less than the number of characters in the document, the total number of objects is substantially less than what a naive implementation would use. A document in which all characters appear in the same font and color will allocate on the order of 100 character objects (roughly the size of the ASCII character set) regardless of the document's length.

The code looks like this:

class Glyph
{
public:
  virtual ~ Glyph ();
  virtual void Draw (Window *, GlyphContext &);
  virtual void SetFont (Font *, GlyphContext &);
  virtual Font *GetFont (GlyphContext &);
  virtual void First (GlyphContext &);
  virtual void Next (GlyphContext &);
  virtual bool IsDone (GlyphContext &);
  virtual Glyph *Current (GlyphContext &);
  virtual void Insert (Glyph *, GlyphContext &);
  virtual void Remove (GlyphContext &);
protected:
    Glyph ();
};

class Character:public Glyph
{
public:
  Character (char);
  virtual void Draw (Window *, GlyphContext &);
private:
  char _charcode;
};

class GlyphContext
{
public:
  GlyphContext ();
  virtual ~ GlyphContext ();
  virtual void Next (int step = 1);
  virtual void Insert (int quantity = 1);
  virtual Font *GetFont ();
  virtual void SetFont (Font *, int span = 1);
private:
  int _index;
  BTree *_fonts;
};

GlyphContext gc;
Font *times12 = new Font ("Times-Roman-12");
Font *timesItalic12 = new Font ("Times-Italic-12");
// ...
gc.SetFont (times12, 6);
gc.Insert (6);
gc.SetFont (timesItalic12, 6);

const int NCHARCODES = 128;
class GlyphFactory
{
public:
  GlyphFactory ();
  virtual ~ GlyphFactory ();
  virtual Character *CreateCharacter (char);
  virtual Row *CreateRow ();
  virtual Column *CreateColumn ();
// ...
private:
    Character * _character[NCHARCODES];
};


GlyphFactory::GlyphFactory ()
{
  for (int i = 0; i < NCHARCODES; ++i)
    {
      _character[i] = 0;
    }
}

Character * GlyphFactory::CreateCharacter (char c)
{
  if (!_character[c])
    {
      _character[c] = new Character (c);
    }
  return _character[c];
}

Row * GlyphFactory::CreateRow ()
{
  return new Row;
}

Column * GlyphFactory::CreateColumn ()
{
  return new Column;
}

Can someone help me covnert this to java in order to better understand? My doubt is, how in Java, Glyph have some methods which are not defined by its subclass Character, Row, Column. These classes only implement intrinsic behavior Draw(). Rest of the methods seem to be implemented by GlyphContext. I feel I understand the concept, but somehow lack of code in familiar language is making me uncomfortable and confusing me.

Aucun commentaire:

Enregistrer un commentaire