Factory to create different objects of same interface

痴心易碎 提交于 2020-01-02 04:06:07

问题


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.


回答1:


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));



回答2:


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));



回答3:


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
    }
}


来源:https://stackoverflow.com/questions/39620828/factory-to-create-different-objects-of-same-interface

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!