Explicit Interfaces for Versioning (a basic example)

For a few of the '##csharp' guys:

See the code for comments and implementation...

namespace ExplicitInterfacesForVersioning
{
    using System;

    public interface ISomeClass_Version2
    {
        string DoSomethingElse();
    }

    public interface ISomeClass_Version3
    {
        string Something { get; }

        string DoSomethingElse();

        string DoSomethingElseAgain();
    }

    public class SomeClass : ISomeClass_Version2,
                             ISomeClass_Version3
    {
        // 0=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=O Original O=~~~~~~~~~~~~~~~~~~~~~~~~~~~=O
        // These various members represent an original implementation.
        //        *** these of course should never be altered unless it's a bug.
        //        *** If it is no longer valid, it should be marked with the 
        //        *** ObsoleteAttribute, it's not until the ObsoleteAttribute is 
        //        *** marked so that it disallows compile should the code within be 
        //        *** COMMENTED OUT and replaced with a 
        //        ***            'throw new NotSupportedException()' 
        //        *** incase of casual late-binding or reflection access.  
        //        ***        The actual method stubs should not be removed!
        public string Something
        {
            get { return "DoSomething - Version 1"; }
        }

        public void DoSomething()
        {
            Console.WriteLine(this.DoSomethingElse());
        }

        public string DoSomethingElse()
        {
            return this.Something;
        }

        // 0=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=O Version 2 O=~~~~~~~~~~~~~~~~~~~~~~~~~~=O
        // This represents a subsequent version that requires a slightly different
        // functionality requirement from a method.  This method is only visible 
        // though the explicit interface definition used below.  this also shows that
        // the original implementation is of course still available.
        //
        //    of course, after formal release, the same no-touch philosophy applies 
        //  and any future work must be part of a version 3 interface.
        string ISomeClass_Version2.DoSomethingElse()
        {
            return this.Something.Replace('1', '2');
        }

        // 0=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=O Version 3 O=~~~~~~~~~~~~~~~~~~~~~~~~~~=O
        // This represents yet another subsequent version. This shows further 
        // requirement differences from the members within the class.
        //
        string ISomeClass_Version3.Something
        {
            get { return "DoSomething - Version 3"; }
        }

        string ISomeClass_Version3.DoSomethingElse()
        {
            // To access the newer version of the property, use this...
            return ( this as ISomeClass_Version3 ).Something;
        }

        string ISomeClass_Version3.DoSomethingElseAgain()
        {
            // this demonstrates that it is still ok to reference to previous 
            // explicitly interfaced methods... again using the interface cast.
            return ( this as ISomeClass_Version2 ).DoSomethingElse() 
                   + " Called from V3!";
        }
    }

    internal class Program
    {
        private static void Main()
        {
            SomeClass original1 = new SomeClass();

            Console.WriteLine(original1.Something);
            original1.DoSomething();
            Console.WriteLine(original1.DoSomethingElse());

            ISomeClass_Version2 iV2A = original1;

            Console.WriteLine(iV2A.DoSomethingElse());

            ISomeClass_Version3 iV3A = original1;

            Console.WriteLine(iV3A.DoSomethingElse());
            Console.WriteLine(iV3A.DoSomethingElseAgain());

            // above is same as below. It shows no difference 
            // even if individually instantiated...

            SomeClass original2 = new SomeClass();

            Console.WriteLine(original2.Something);
            original2.DoSomething();
            Console.WriteLine(original2.DoSomethingElse());

            ISomeClass_Version2 iV2B = new SomeClass();

            Console.WriteLine(iV2B.DoSomethingElse());

            ISomeClass_Version3 iV3B = new SomeClass();

            Console.WriteLine(iV3B.DoSomethingElse());
            Console.WriteLine(iV3B.DoSomethingElseAgain());

            Console.ReadLine();
        }
    }
}


[2/24/2007] Anon: hmmm.

never thought of using interfaces this way.

thanks for the tip...

[3/7/2007] 123P0P!: awesome...

Neva realized ya cud do dis wid intafaces.

thnx bnchz 'cos ya helpd me fix a naztee ish.

peesse bro ,,\/.(^_^)

[3/14/2007] n00b: Ref: ExplicitInterfacesforVersioning

What do you think to using partial classes to seperate the version code, so you can have the class files? e.g:

ClassA_Version1.cs

ClassB_Version2.cs, etc

This way it means that the developer is only ever looking at the version they need to work with rather that using a single code file with all the versions.  I know this is simple, but I can imagine people accidentally modifying the wrong version etc.

[3/15/2007] Moridin8: In Reply to n00b

hi N00b,

The use of partial classes can be useful in separating out classes into convenient member grouping for various reasons, including the use you are advocating.  However this methodology may come apart if the newer versions require a fundamental change internally. 

Interfaces are used as an attempt to help formalise public use contracts to the objects you create, it does not however enforce how the classes themselves work internally.  Semantically no matter what the internal implementation of your class the input/output should never change from what is expected.

As applications evolve over time, things do change.  A recent example I can highlight here is a situation where an object important to the running of several key systems would no longer be viable because the system behind it was obsolete.  To ensure that the systems reliant on this object would not break, the object was completely altered internally to become a façade.  The façade in combination with other patterns ensured that the newer system always returned the data the other systems expected without breaking them, while allowing them over time to adjust to the newer system via newer versioned interfaces.

So, the use of partial classes IMHO should be used as another tool in the kit, but shouldn’t be solely relied upon.

=O)  Hope this helps.