Object Oriented Programming Patterns for Geeks
The Flyweight Pattern
namespace ooppatterns
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
internal class Program
{
private static void Main( string[] args )
{
// This could represent a CSV supplied from an external system.
// We can assume that this is usually exceptionally large.
string stockCsv = @"carrot,carrot,potato,potato,carrot,turnip,"
+ @"carrot,turnip,turnip,carrot,turnip,turnip,"
+ @"potato,carrot,potato,turnip";
// Due to the possibility of the source being massive, loading it
// totally into memory would create errors. As such we should
// stream it in and process it that way...
using ( CsvStreamReader SR = new CsvStreamReader( stockCsv ) )
{
StockDescriberAndCounter DD = new StockDescriberAndCounter();
int stockNumber = 0;
while ( !SR.IsEOF )
DD.GetDescription( SR.ReadValue() ).Describe( ++stockNumber );
}
Console.ReadLine();
}
}
class StockDescriberAndCounter
{
private Dictionary<string, Vegetable> _veg =
new Dictionary<string, Vegetable>();
public Vegetable GetDescription( string vegType )
{
// this ensures only one description (or flyweight)
// is ever created per permutation.
if ( !this._veg.ContainsKey( vegType ) )
// Creates meta description object on need only.
switch ( vegType )
{
case "carrot":
{
this._veg.Add( "carrot", new Carrot() );
break;
}
case "turnip":
{
this._veg.Add( "turnip", new Turnip() );
break;
}
case "potato":
{
this._veg.Add( "potato", new Potato() );
break;
}
}
return this._veg[vegType];
}
}
enum GeneralShape
{
Round,
Long,
Irregular
}
[Flags]
enum BestCooked
{
Raw = 0x01,
Boiled = 0x02,
Roasted = 0x04,
Steamed = 0x08,
Fried = 0x10
}
// the 'Flyweight' class
abstract class Vegetable
{
protected GeneralShape _shape;
protected BestCooked _cook;
protected string _colour;
// this is used to prove instance identity
protected Guid _myId = Guid.NewGuid();
public virtual void Describe( int itemNumber )
{
Console.WriteLine( new string( '-', 55 ) );
Console.WriteLine( "I am a {0} [{1}]", this.GetType().Name,
this._myId );
Console.WriteLine( "I am {0}", this._shape.ToString() );
Console.WriteLine( "I am {0} in colour", this._colour );
Console.WriteLine( "I am best prepared : {0}",
this._cook.ToString() );
Console.WriteLine( "I am Stock Item: {0}", itemNumber );
}
}
// Concrete 'Flyweights'
// (essentially these are shared meta data)
class Turnip : Vegetable
{
public Turnip()
{
base._shape = GeneralShape.Round;
base._cook = BestCooked.Boiled;
base._colour = "Purple/Cream";
}
}
class Carrot : Vegetable
{
public Carrot()
{
base._shape = GeneralShape.Long;
base._cook = BestCooked.Raw | BestCooked.Steamed;
base._colour = "Orange";
}
}
class Potato : Vegetable
{
public Potato()
{
base._shape = GeneralShape.Irregular;
base._cook = BestCooked.Boiled | BestCooked.Roasted
| BestCooked.Fried;
base._colour = "Brownish";
}
}
//============================================================================
// NON pattern specific...
// Pretend this derived from something like a filestream...
class CsvStreamReader : StringReader
{
public CsvStreamReader( string source )
: base( source ) { }
// this would be a handy feature on all streams...
public bool IsEOF
{
get { return base.Peek() == -1; }
}
private bool CheckDelim( char delim )
{
return ( delim == '\0' ) ? base.Peek() == '\"' || base.Peek() == '\''
: delim == (char)base.Peek();
}
public string ReadValue()
{
if ( this.IsEOF )
throw new InvalidOperationException();
if ( base.Peek() == ',' )
base.Read();
StringBuilder SB = new StringBuilder();
char currentDelim = '\0';
// Check for initial delimiter
if ( this.CheckDelim( '\0' ) )
{
// handle potential escape confusion at start of value
int count;
char delim = (char)base.Read();
for ( count = 1; this.CheckDelim( '\0' ); count++ )
base.Read();
if ( ( count % 2 ) == 0 )
// no delimiter, just escapes
SB.Append( new string( delim, count / 2 ) );
else if ( count > 1 )
// this is an escape AND a delimiter
SB.Append( new string( currentDelim = delim,
( count - 1 ) / 2 ) );
else
// this is just a delimiter
currentDelim = delim;
}
while ( base.Peek() != ',' && !this.IsEOF )
{
if ( currentDelim != '\0' && this.CheckDelim( currentDelim ) )
{
base.Read(); // skip the delimiter
// Check to see if previous delimiter was an escape
if ( !this.CheckDelim( currentDelim ) )
break; // Nope? break out, we're done here
}
SB.Append( (char)base.Read() );
while ( currentDelim != '\0' && base.Peek() == ',' )
SB.Append( (char)base.Read() );
}
return SB.ToString();
}
}
}