0

I'm refactoring a codebase and stumbled upon a factory class that created objects based on the subtype passed into the method.

The class basically has one public method with one parameter of which it is a descendant from a base class. Within this method is a switch statement that determines which subtype is passed and conditionally calls different methods to produce the result.

I'm trying to tidy up a bit and figured a strategy pattern might suit the requirements since the code violates the open-closed principle.

Since Autofac is being used, I figured that the transition would be straight forward, however I've hit a bump in the road.

The problem isn't related to Autofac, but rather to the choice of design.

The following code illustrates the class composition, but it is lacking.

public abstract class Parent { }
public class ChildA : Parent { }
public class ChildB : Parent { }

public interface IChildStrategy<T> where T:Parent
{
    IEnumerable<object> CreateObjects(Parent child);
}

public class ChildAStrategy : IChildStrategy<ChildA>
{
    private IEnumerable<object> CreateObjects(ChildA child)
    {
        yield return "child A";
    }

    public IEnumerable<object> CreateObjects(Parent child) => 
        CreateObjects(child as ChildA);
}

public class ChildBStrategy : IChildStrategy<ChildB>
{
    private IEnumerable<object> CreateObjects(ChildB child)
    {
        yield return "child b";
        yield return "child b's pet";
    }

    public IEnumerable<object> CreateObjects(Parent child) =>
        CreateObjects(child as ChildB);         
}

[TestMethod]
public void TestStrategyPattern()
{
    var container = builder.Build();

    Parent child = new ChildA();
    var type = child.GetType();
    var strategy = container.Resolve(typeof(IChildStrategy<>)
        .MakeGenericType(type));

    // strategy.CreateObjects(child);
    // Assert.AreEqual("child A", fromDict);

    var dict = new Dictionary<Type, Func<Parent, IEnumerable<object>>>();

    dict.Add(typeof(ChildA), x => new ChildAStrategy().CreateObjects(x));
    dict.Add(typeof(ChildB), x => new ChildBStrategy().CreateObjects(x));

    var fromDict = dict[type](child);

    Assert.AreEqual("child A", fromDict);
}

I've tried both registering the interface with the generic type itself, like so:

public interface IChildStrategy<T> where T:Parent
{
    IEnumerable<object> CreateObjects(T child);
}

But it doesn't really change the difficulties.

Are there any good alternatives to a design pattern for sub-classes?

Updated

Here's what I ended up with. The changes are basically removing the parameter from the CreateObjects method and rather inject it into the constructor as a dependency and registering the strategies as Keyed<T> registrations.

public abstract class Parent { }
public class ChildA : Parent { }
public class ChildB : Parent { }

public interface IChildStrategy
{
    IEnumerable<object> CreateObjects();
}

public class ChildAStrategy : IChildStrategy
{
    private readonly ChildA childA;

    public ChildAStrategy(ChildA childA)
    {
        this.childA = childA;
    }

    public IEnumerable<object> CreateObjects()
    {
        yield return childA;
    }
}

public class ChildBStrategy : IChildStrategy
{
    private readonly ChildB childB;

    public ChildBStrategy(ChildB childB)
    {
        this.childB = childB;
    }

    public IEnumerable<object> CreateObjects()
    {
        yield return childB;
        yield return "child b's pet";
    }
}

[TestMethod]
public void TestStrategyPattern()
{
    var builder = new ContainerBuilder();
    builder.RegisterType<ChildAStrategy>().Keyed<IChildStrategy>(typeof(ChildA));
    builder.RegisterType<ChildBStrategy>().Keyed<IChildStrategy>(typeof(ChildB));

    var container = builder.Build();

    IChildStrategy resolve(Parent x) => container.ResolveKeyed<IChildStrategy>(x.GetType(), new TypedParameter(x.GetType(), x));

    Parent root;
    IChildStrategy strategy;
    root = new ChildA();
    strategy = resolve(root);
    Assert.IsInstanceOfType(strategy, typeof(ChildAStrategy));
    Assert.AreSame(root, strategy.CreateObjects().Single());
    root = new ChildB();
    strategy = resolve(root);
    Assert.IsInstanceOfType(strategy, typeof(ChildBStrategy));
    Assert.AreSame(root, strategy.CreateObjects().First());
    Assert.AreEqual("child b's pet", strategy.CreateObjects().Last());
}
Vincent
  • 1,119
  • 11
  • 25
  • 2
    For refactoring, you will probably have better chance getting an answer at: https://codereview.stackexchange.com/ – Xiaoy312 Feb 05 '19 at 20:19

1 Answers1

0

Updated Answer

Original answer is included, below

I think the pattern you're looking for is a Mediator.

Here's an example implementation that fits your needs, as I understand them. This implementation strengthens the typing of the handlers, but makes even heavier use of dynamic in the mediator itself. If performance is a concern, you might want to run some tests--this implementation will probably be slower than your existing code (see: How does having a dynamic variable affect performance?)

using System.Collections.Generic;
using System.Linq;
using Autofac;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace _54542354.MediatorExample
{
    /**
     * Example Input/Output types
     **/
    abstract class ActionBase { }
    class ExampleAction : ActionBase { public string Name { get; set; } }
    class ReturnType { public string Id { get; set; } }

    /**
     * Interfaces
     **/
    interface IActionHandler<TAction> where TAction : ActionBase
    {
        IEnumerable<ReturnType> Handle(TAction action);
    }

    interface IActionHandlerMediator
    {
        IEnumerable<ReturnType> Handle(ActionBase action);
    }

    /**
     * Example implementations
     **/
    class ExampleHandler : IActionHandler<ExampleAction>
    {
        public IEnumerable<ReturnType> Handle(ExampleAction action)
        {
            yield return new ReturnType{ Id = $"{action.Name}_First" };
            yield return new ReturnType{ Id = $"{action.Name}_Second" };
        }
    }

    class ActionHandlerMediator : IActionHandlerMediator
    {
        readonly ILifetimeScope container;

        public ActionHandlerMediator(ILifetimeScope container)
            => this.container = container;

        public IEnumerable<ReturnType> Handle(ActionBase action)
        {
            // TODO: Error handling. What if no strategy is registered for the provided type?
            dynamic handler = container.Resolve(typeof(IActionHandler<>)
                .MakeGenericType(action.GetType()));

            return (IEnumerable<ReturnType>)handler.Handle((dynamic)action);
        }
    }

    /**
     * Usage example
     **/
    [TestClass]
    public class Tests
    {
        [TestMethod]
        public void TestMediator()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<ExampleHandler>().As<IActionHandler<ExampleAction>>();
            builder.RegisterType<ActionHandlerMediator>().As<IActionHandlerMediator>();
            var container = builder.Build();

            var handler = container.Resolve<IActionHandlerMediator>();

            var result = handler.Handle(new ExampleAction() { Name = "MyName" });

            Assert.AreEqual("MyName_First", result.First().Id);
            Assert.AreEqual("MyName_Second", result.Last().Id);
        }
    }
}

Original Answer

I took a stab at running your sample code. I had to tweak some things out of the box, but I think it actually worked as you want it to (after my tweaks).

Here's what I ended up with:

[TestMethod]
public void TestStrategyPattern_Dict()
{
    // Define the available strategies
    var dict = new Dictionary<Type, Func<Parent, IEnumerable<object>>>();
    dict.Add(typeof(ChildA), x => new ChildAStrategy().CreateObjects(x));
    dict.Add(typeof(ChildB), x => new ChildBStrategy().CreateObjects(x));

    // Create the input object
    Parent child = new ChildA();

    // Invoke the strategy
    IEnumerable<object> enumerable = dict[child.GetType()](child);

    // Verify the results
    Assert.AreEqual("child A", enumerable.Single());
}

[TestMethod]
public void TestStrategyPattern_AutoFac()
{
    // Define the available strategies
    var builder = new ContainerBuilder();
    builder.RegisterType<ChildAStrategy>().As<IChildStrategy<ChildA>>();
    builder.RegisterType<ChildBStrategy>().As<IChildStrategy<ChildB>>();
    var container = builder.Build();

    // Create the input object
    Parent child = new ChildA();

    // Resolve the strategy
    // Because we don't know exactly what type the container will return,
    // we need to use `dynamic`
    dynamic strategy = container.Resolve(typeof(IChildStrategy<>)
        .MakeGenericType(child.GetType()));

    // Invoke the strategy
    IEnumerable<object> enumerable = strategy.CreateObjects(child);

    // Verify the results
    Assert.AreEqual("child A", enumerable.Single());
}

These tests both pass. I did not change any code outside of the tests.

The two main changes I introduced are:

  • Use of .Single() before asserting. This is necessary because the strategy returns an IEnumerable, but the assertion is expecting the first object from that enumerable.
  • Use of the dynamic type when resolving the strategy from AutoFac. This is necessary because the compiler can't tell what type AutoFac will return. In the original code, the returned type was object, which doesn't have a CreateObjects(Parent) method. dynamic lets us call any method we want--the compiler will just assume it exists. We'll get a runtime exception if the method doesn't exist, but because we know we just created an IChildStrategy<>, we can be confident that the method will exist.
xander
  • 1,689
  • 10
  • 18
  • I totally forgot about dynamic. Thanks! If you have any opinions on an alternative to this design, feel free to come with suggestions. The sole purpose of this code is to call different strategies based on the descendant class type. – Vincent Feb 06 '19 at 09:49
  • Can you clarify some points about the design first? 1) Is the return type actually `IEnumerable`, or was that an example placeholder? 2) Do the strategies actually need to take a `Parent` as an argument, or would `CreateObjects()` work as long as the strategy is selected per-type? – xander Feb 07 '19 at 02:41
  • The return type is an IEnumerable of a certain interface. The strategies should be passed the parent-descendant child so that it can create the correct result. To be more specific, I've got a set of 'Actions' which all are descended from a base class. These actions need to be converted to a different set of objects, all implementing the same base interface, and several actions can produce more than one object. – Vincent Feb 07 '19 at 08:45
  • I have updated my answer with a new design based on your feedback. – xander Feb 09 '19 at 00:28
  • @Vincent Does my updated answer help you? It got a downvote, and I'm not sure why. If the update isn't helpful, I'd like to revise or remove it. – xander Feb 09 '19 at 19:39
  • I havent had a chance to review it yet, but i havent downvoted anything. I’ll try to get around to reviewing your update, thanks! – Vincent Feb 10 '19 at 20:34
  • Thanks @xander, your code looks sound, however I don't see any benefits of using the mediator instead of the strategy implementation in this case. Thanks for your time! – Vincent Feb 11 '19 at 07:26