Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using dynamic to set disparate properties of uncontrolled (third party) sealed types

Given the following program:

using System;
using System.Collections.Generic;

namespace ConsoleApplication49
{
    using FooSpace;

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<FooBase> foos = FooFactory.CreateFoos();

            foreach (var foo in foos)
            {             
                HandleFoo(foo);
            }
        }

        private static void HandleFoo(FooBase foo)
        {
            dynamic fooObject = foo;
            ApplyFooDefaults(fooObject);
        }

        private static void ApplyFooDefaults(Foo1 foo1)
        {
            foo1.Name = "Foo 1";

            Console.WriteLine(foo1);
        }

        private static void ApplyFooDefaults(Foo2 foo2)
        {
            foo2.Name        = "Foo 2";
            foo2.Description = "SomeDefaultDescription";

            Console.WriteLine(foo2);
        }

        private static void ApplyFooDefaults(Foo3 foo3)
        {
            foo3.Name    = "Foo 3";
            foo3.MaxSize = Int32.MaxValue;

            Console.WriteLine(foo3);
        }

        private static void ApplyFooDefaults(Foo4 foo4)
        {
            foo4.Name        = "Foo 4";
            foo4.MaxSize     = 99999999;
            foo4.EnableCache = true;

            Console.WriteLine(foo4);
        }

        private static void ApplyFooDefaults(FooBase unhandledFoo)
        {
            unhandledFoo.Name = "Unhandled Foo";
            Console.WriteLine(unhandledFoo);
        }
    }    
}

/////////////////////////////////////////////////////////
// Assume this namespace comes from a different assembly
namespace FooSpace
{
    ////////////////////////////////////////////////
    // these cannot be changed, assume these are 
    // from the .Net framework or some 3rd party
    // vendor outside of your ability to alter, in
    // another assembly with the only way to create
    // the objects is via the FooFactory and you
    // don't know which foos are going to be created
    // due to configuration.

    public static class FooFactory
    {
        public static IEnumerable<FooBase> CreateFoos()
        {
            List<FooBase> foos = new List<FooBase>();
            foos.Add(new Foo1());
            foos.Add(new Foo2());
            foos.Add(new Foo3());
            foos.Add(new Foo4());
            foos.Add(new Foo5());

            return foos;
        }
    }

    public class FooBase
    {
        protected FooBase() { }

        public string Name { get; set; }

        public override string ToString()
        {
            return String.Format("Type = {0}, Name=\"{1}\"", this.GetType().FullName, this.Name);
        }
    }

    public sealed class Foo1 : FooBase
    {
        internal Foo1() { }
    }

    public sealed class Foo2 : FooBase
    {
        internal Foo2() { }

        public string Description { get; set; }

        public override string ToString()
        {
            string baseString =  base.ToString();
            return String.Format("{0}, Description=\"{1}\"", baseString, this.Description);
        }
    }

    public sealed class Foo3 : FooBase
    {
        internal Foo3() { }

        public int MaxSize { get; set; }

        public override string ToString()
        {
            string baseString =  base.ToString();
            return String.Format("{0}, MaxSize={1}", baseString, this.MaxSize);
        }
    }

    public sealed class Foo4 : FooBase
    {
        internal Foo4() { }

        public int MaxSize { get; set; }
        public bool EnableCache { get; set; }

        public override string ToString()
        {
            string baseString =  base.ToString();
            return String.Format("{0}, MaxSize={1}, EnableCache={2}", baseString,
                                                                      this.MaxSize,
                                                                      this.EnableCache);
        }
    }

    public sealed class Foo5 : FooBase
    {
        internal Foo5() { }
    }
    ////////////////////////////////////////////////
}

Which produces the following output:

Type = ConsoleApplication49.Foo1, Name="Foo 1"
Type = ConsoleApplication49.Foo2, Name="Foo 2", Description="SomeDefaultDescription"
Type = ConsoleApplication49.Foo3, Name="Foo 3", MaxSize=2147483647
Type = ConsoleApplication49.Foo4, Name="Foo 4", MaxSize=99999999, EnableCache=True
Type = ConsoleApplication49.Foo5, Name="Unhandled Foo"
Press any key to continue . . .

I chose to use dynamic here to avoid the following:

  1. using switch/if/else statements e.g. switch(foo.GetType().Name)
  2. explicit type checking statements e.g. foo is Foo1
  3. explicit casting statements e.g. (Foo1)foo

Because of the dynamic conversion, the correct ApplyFooDefaults method gets invoked based on the type of the object passed into HandleFoo(FooBase foo). Any objects that do not have an appropriate ApplyFooDefaults handler method, fall into the "catch all" method, ApplyFooDefaults(FooBase unhandledFoo).

One key part here is that FooBase and the derived classes represent types that are outside of our control and cannot be derived from to add additional interfaces.

Is this a "good" use for dynamic or can this problem be solved in an OOP way without adding additional complexity given the constraints and the fact that this is just to set default property values on these objects?

*UPDATED*

After Bob Horn's answer, I realized that my scenario wasn't complete. Additional constraints:

  1. You can't create the Foos directly, you have to use the FooFactory.
  2. You can't assume the Foo type because the Foo type is specified in configuration and created reflectively.

.

like image 248
Jim Avatar asked Nov 14 '22 05:11

Jim


1 Answers

Well, individual object initialization should happen in the constructors of the types. If the factory fails to do that and outputs an object with a base type only then clearly it is beyond OOP patterns to initialize the objects based on type.

Alas, runtime type detection is the way to go and dynamic just does that, so yes, your solution is very pretty. (but the third party lib is not, because it forces you to use dynamic types)

like image 87
M.Stramm Avatar answered Dec 07 '22 23:12

M.Stramm