Factory to create different objects of same interface
Asked Answered
S

3

5

I have 1 interface:

public interface ISummary
{
   int EventId {get; set;}
}

And many concrete classes that implement this interface:

public class EmployeeSummary : ISummary
{
   public int EventId {get; set;},
   public int TotalUniqueCount {get; set;}
   public int Location {get; set;}
}

public class CarSummary : ISummary
{
   public int EventId {get; set;}
   public int TotalMiles {get; set;}
   public int TotalHours {get; set;}
}

etc....

The only shared property is the EventId. Is there a way to have 1 factory method that creates all of these summary objects? I want 1 entry point which decides which objects to create.

So something like:

public ISummary CreateSummary(ConcreteObjectType with properties)
{
if EmployeeSummary
 --Call this method to create and return EmployeeSummary

if CarSummary
 --Call this method create and return CarSummary
}

I want all calls within other classes to call this method rather than creating the objects themselves.

The part I am struggling with is how do I pass the properties to assign to the objects to this CreateSummary method since all the properties on the objects will be different?

I am open to changing the objects at this point at well if there is a better design pattern I should be using here.

Sankaran answered 21/9, 2016 at 15:31 Comment(1)
You a generic factory method with an action delegate to populate the properties you wantDesmoid
P
12

Well, that's exactly why Factory Method pattern exists :

public class SummaryFactory
{        
    // new instance with values assigned by action delegate or default
    public T Create<T>(Action<T> action = null) where T : ISummary, new()
    {
        var result = new T();
        action?.Invoke(result);             
        return result;
    }

    // with object to assign value from (map) 
    public T Create<T>(object map) where T : ISummary, new()
    {
        var result = new T();
        PropertyInfo[] props = map.GetType().GetProperties();
        PropertyInfo[] tProps = typeof(T).GetProperties();

        foreach (var prop in props)
        {
            var upperPropName = prop.Name.ToUpper();
            var foundProperty = tProps.FirstOrDefault(p => p.Name.ToUpper() == upperPropName);
            foundProperty?.SetValue(result, prop.GetValue(map));
        }
        return result;
    }

    // new instance without generic parameters
    public object Create(Type type)
    {
        var instance = Activator.CreateInstance(type);

        // add some other logic that changes instance
        return instance;
    }
}

And now you can use this factory :

var factory = new SummaryFactory();
var carSummary = factory.Create<CarSummary>();
var carSummary2 = factory.Create<CarSummary>(car => { car.TotalMiles = 50; });
var carSummary3 = factory.Create<CarSummary>(new { TotalMiles = 50 });
var employeeSummary = factory.Create(typeof(EmployeeSummary));
Ptyalin answered 21/9, 2016 at 15:35 Comment(3)
You can merge the first two methods using an optional parameter and also checking for null on the actionDesmoid
It should be noted that generic methods with a new() constraint are not factory methods. The only thing such a method can return is an instance of the exact concrete type you passed in, limited to types which have a parameterless constructor. In terms of OOP principles (like loose coupling) you gain absolutely nothing.Adhesion
I cannot see any huge advantege of the Factory Pattern for this particular example - you still have to pass a concrete type of the object that you want to create - and additionally you have to create an instance of factory itself - createing a requiered object with a new would have exactly the same benefit here with much less effort.Giannagianni
A
3

Generics with a new() constraint are only making tighter coupling. If you already need know which type to pass as the generic parameter T, and you know what the method will return, what's the point in using it then?

// This is a *helper method* which will create a car summary.
// No abstraction, no polymorphism, just a template helper method.
var summary = CreateSummary<CarSummary>(carData);

If you need an abstract factory, then it means your calling method only knows the return interface, and it's the factory which is supposed to decide on the actual implementation.

In your case, (I believe) you have several types of data classes, and you want the method to create the appropriate summary for each type. I.e. something like:

class EmployeesData : IEmployeesData
{ ... }

class CarsData : ICarsData
{ ... }

// at this point we don't know what `data` actually is
IData data = GetDataFromSomewhere();

// so we cannot pass the concrete generic parameter here
ISummary summary = GetSummary(data);

Then what you need is some sort of a strategy which will choose the right implementation at runtime. Strategies for different input types should be registered at program startup (somewhere inside the composition root, or injected through DI), so that you have something like:

static readonly Dictionary<Type, Func<object, ISummary>> _strategies =
        new Dictionary<Type, Func<object, ISummary>>();

public static void Register<T>(Func<T, ISummary> strategy)
{
    _strategies[typeof(T)] = input => strategy((T)input);
}

public ISummary CreateSummary(object input)
{
    var type = input.GetType();

    Func<object, ISummary> strategy;
    if (!_strategies.TryGetValue(type, out strategy))
        throw new ArgumentException($"No strategy registered for type {type}");

    return strategy(input);
}

So, somewhere at your composition root you would have the concrete methods:

ISummary CreateEmployeeSummary(EmployeesData data)
{ ... }

ISummary CreateCarSummary(CarsData data)
{ ... }

// and finally register all concrete methods:
Register<IEmployeesData>(d => CreateEmployeeSummary(d));
Register<ICarsData>(d => CreateCarsSummary(d));
Adhesion answered 21/9, 2016 at 18:17 Comment(0)
S
1

Depending on the constructors for the objects, you could do this:

public interface ISummary
{ }

public class Bar : ISummary
{ }

public class SummaryFactory
{
        public static TSummary CreateSummary<TSummary>()
            where TSummary : new()
    {
        return new TSummary();
    }
}

public class Foo
{
    public void AMethod()
    {
        var bar = SummaryFactory.CreateSummary< Bar >();
    }
}

But I don't know what that'll really buy you unless the constructor was empty.

If your objects do have constructor parameters, you can use the strategy pattern to create them instead of the factory pattern, something like this:

public interface ISummary
{ }

public class CarSummary : ISummary
{

    public CarSummary(int someParam)
    {

    }
}

public interface ISummaryStrategy
{
    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    ISummary Create();
}

public class CarSummaryStrategy
    : ISummaryStrategy
{
    public ISummary Create()
    {
        return new CarSummary(42); //The Answer to the Ultimate Question of Life, the Universe, and Everything
    }
}



public class Foo
{
    private Dictionary< Type, ISummaryStrategy > _summaryStrategies;
    public Foo()
    {
        this._summaryStrategies = new Dictionary< Type, ISummaryStrategy >
        {
            {typeof( CarSummary ), new CarSummaryStrategy()}
        };

    }
    public void UseSummaries(Type summary)
    {
        var summaryImpl = this._summaryStrategies[summary].Create();
        // use the summary
    }
}
Scent answered 21/9, 2016 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.