Object Oriented Programming Patterns for Geeks

The Prototype Pattern

In .NET this pattern is catered for in some respects via the 'ICloneable' interface.  In C++ the use of copy constructors can achieve a similar result.  However, with .NET there is a caveat in respect to the protected object method 'MemberwiseClose()'.  The example below demonstrates the .NET version of this pattern and highlights the issue.

namespace ooppatterns
{
    using System;

    internal class Program
    {
        private static void Main( string[] args )
        {
            // Using .NET's standard 'Shallow copy' Memberwise close
            Prototype PT = new Prototype();

            Prototype PT2 = PT.Clone() as Prototype;

            Console.WriteLine( PT.SomeData );
            Console.WriteLine( PT.SomeObj.Test );

            // Notice this is assigning to 'PT' and NOT 'PT2'
            PT.SomeObj.Test = "food!";

            Console.WriteLine( PT2.SomeData );
            Console.WriteLine( PT2.SomeObj.Test );


            // Using an explicit implementation...
            PrototypeDeep PTD = new PrototypeDeep();

            PrototypeDeep PTD2 = PTD.Clone() as PrototypeDeep;

            Console.WriteLine( PTD.SomeData );
            Console.WriteLine( PTD.SomeObj.Test );

            PTD.SomeObj.Test = "food!";

            Console.WriteLine( PTD2.SomeData );
            Console.WriteLine( PTD2.SomeObj.Test );

            Console.ReadLine();
        }
    }

    class Prototype : ICloneable
    {
        private int _someData = 100;
        private SomeObject _someObj = new SomeObject();

        public int SomeData
        {
            get { return this._someData; }
            set { this._someData = value; }
        }

        public SomeObject SomeObj
        {
            get { return this._someObj; }
            set { this._someObj = value; }
        }

        public object Clone()
        {
            // This performs a shallow copy, which means
            // values are copied 'as is' - this includes
            // references, so cloned objects will share
            // reference objects - not clever!
            return base.MemberwiseClone();
        }
    }

    class PrototypeDeep : ICloneable
    {
        private int _someData = 100;
        private SomeObject _someObj = new SomeObject();

        public int SomeData
        {
            get { return this._someData; }
            set { this._someData = value; }
        }

        public SomeObject SomeObj
        {
            get { return this._someObj; }
            set { this._someObj = value; }
        }

        public object Clone()
        {
            // This ignores the .NET shallow-copy and
            // is more explicit.  
            PrototypeDeep PTD = new PrototypeDeep();
            PTD._someData = this._someData;
            PTD._someObj = new SomeObject();        // Notice new instantiation.
            PTD._someObj.Test = this._someObj.Test; // assign data to new object

            return PTD;
        }
    }

    class SomeObject
    {
        private string _test = "hello";

        public string Test
        {
            get { return this._test; }
            set { this._test = value; }
        }
    }
}