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(); } } }